如何使用 Pandas 将查询与单个外部变量结合起来

Posted

技术标签:

【中文标题】如何使用 Pandas 将查询与单个外部变量结合起来【英文标题】:How to combine queries with a single external variable using Pandas 【发布时间】:2021-07-26 07:20:38 【问题描述】:

我正在尝试通过 html 表单 (@search) 接受由逗号分隔的许多搜索词的可变输入,并查询数据框的 2 列。

每个列查询都独立工作,但我无法让它们以和/或方式一起工作。

第一列查询:

filtered = df.query ('`Drug Name` in @search')

第二列查询:

filtered = df.query ('BP.str.contains(@search, na=False)', engine='python')

编辑 像这样组合:

filtered = df.query ("('`Drug Name` in @search') and ('BP.str.contains(@search, na=False)', engine='python')")

给出以下错误,在引擎参数中突出显示 python 标识符

SyntaxError: Python 关键字在 numexpr 查询中无效标识符

编辑 2

数据框是从 excel 文件中读取的,其中包含以下列: 药物名称(包含单个药物名称)、BP、U&E(带有长描述性文本条目)

搜索词将通过html表单输入:

search = request.values.get('searchinput').replace(" ","").split(',')

作为患者有时可能使用的药物列表,并添加与药物使用相关的特定条件。示例用户输入:

卡托普利、扑热息痛、肾脏疾病、慢性

我希望对照特定药物名称检查列表,并检查其他列(例如 BP 和 U&E)是否提及搜索词。

编辑 3

抱歉,但尝试实施给出的答案会给我带来一堆错误。我下面的内容是我所追求的 90%,让我搜索两个列,包括“BP”的全部内容。但是我只能通过终端搜索单个术语,如果我 #out 并交换收集使用输入的行(从与终端相对的 html 表单中获取)我得到:

TypeError: unhashable type: 'list'

@app.route('/', methods=("POST", "GET"))

    def html_table():
        searchterms = []
        #searchterms = request.values.get('searchinput').replace(" ","").split(',')
        searchterms = input("Enter drug...")   
        filtered = df.query('`Drug Name` in @searchterms | BP.str.contains(@searchterms, na=False)', engine='python')
        return render_template('drugsafety.html', tables=[filtered.to_html(classes='data')], titles=['na', 'Drug List'])

<form action="" method="post">
  <p><label for="search">Search</label>
  <input type="text" name="searchinput"></p>        
  <p><input type="submit"></p>
</form>

样本数据

BP 列的内容可能很长、描述性和可变性,但一个例子是:

每 12 个月 - CKD 患者每 3 至 6 个月一次。

Drug Name         BP                            U&E
Perindopril       Every 12 months               Not needed
Alendronic Acid   Not needed                    Every 12 months
Allopurinol       Whilst titrating - 3 months   Not needed

用这一行:

searchterms = request.values.get('searchinput')

在 html 表单输出中输入“月”:

1   Perindopril  Every 12 months                Not needed 
14  Allopurinol  Whilst titrating – 3 months    Not needed

一切都好。

在 html 表单输出中输入“阿仑膦酸”:

13  Alendronic Acid Not needed  Every 12 months

也不错,但输入“培哚普利,别嘌呤醇”没有返回任何结果。

如果我将行更改为:

searchterms = request.values.get('searchinput').replace(" ","").split(',')

当页面重新加载时,我得到 TypeError: unhashable type: 'list'。

但是 - 如果我再改变:

filtered = df.query('`Drug Name` in @searchterms | BP.str.contains(@searchterms, na=False)', engine='python')

到:

filtered = df.query('`Drug Name` in @searchterms')

然后出现不可哈希的类型错误并输入“培哚普利,别嘌醇” 返回:

1   Perindopril   Every 12 months                   Not needed
14  Allopurinol   Whilst titrating – Every 3 months Not needed

但我现在不再在 BP 列中搜索搜索词。

只是想可能是因为 searchterms 是一个列表 '[]' 将其更改为 o 元组 '()' 没有改变任何东西。

非常感谢任何帮助。

【问题讨论】:

这可能只是一个发布错误,但加入的过滤在查询中包含 engine 关键字...(即您的 " 没有在正确的位置结束)。 是否可以获取一些示例输入数据? .query 功能太强大,通常是执行此任务最慢的函数。如果我是你,我会使用 sets intersection 找出 X 中的值在 B 中。请参阅下面的答案。 @Dave 您能否提供一个示例输入,例如像这样:df = pd.DataFrame(data) 或者只是在此处复制数据框的前 10 行,以便我们可以复制粘贴它,如果您可以提供示例用户输入以及预期输出是什么,这也会很有帮助。否则,它更像是猜测而不是帮助。 @Dave 您要搜索所有列(药物名称、BP 和 U&E),还是只搜索药物名称和 BP?如果全部三个,那么搜索“months”将返回所有 3 行(因为“Alendronic Acid”在 U&E 中具有“每 12 个月”),对吗? 【参考方案1】:

我假设您要查询 2 列,并希望在任何查询匹配时返回该行。

在这一行中,问题在于 engine=python 在查询中。

filtered = df.query ("('`Drug Name` in @search') and ('BP.str.contains(@search, na=False)', engine='python')")

应该是

df.query("BP.str.contains(@search, na=False)", engine='python')

如果您执行searchterms = request.values.get('searchinput').replace(" ","").split(','),它会将您的字符串转换为单词列表,这将导致Unhashable type list 错误,因为 str.contains 需要 str 作为输入。

你可以做的是使用正则表达式在列表中搜索搜索词,它看起来像这样:

df.query("BP.str.contains('|'.join(@search), na=False, regex=True)", engine='python')

它的作用是使用正则表达式搜索所有单个单词。 ('|'.join(@search) 将是 "searchterm_1|search_term2|..." 并且 "|" 用于表示或在正则表达式中,因此它会在 BP 列值中查找 searchterm_1 或 searchterm_2)

要合并两个查询的输出,您可以分别运行它们并连接结果

pd.concat([df.query("`Drug Name` in @search", engine='python'),df.query("BP.str.contains('|'.join(@search), na=False, regex=True)", engine='python')])

此外,任何基于字符串的匹配都需要您的字符串完美匹配,包括大小写。因此您可以将数据框和查询中的所有内容小写。同样对于空格分隔的单词,这将删除空格。

如果您对Every 12 months 执行searchterms = request.values.get('searchinput').replace(" ","").split(','),它将转换为“每12 个月”。所以你也许可以删除 .replace() 部分并使用searchterms = request.values.get('searchinput').split(',')

【讨论】:

太棒了,非常感谢!也感谢对​​ unhashable 类型错误的解释。【参考方案2】:

如果要在所有列中搜索文本,可以先join所有列,然后使用str.contains和至少匹配其中一个词的正则表达式模式检查每一行中的搜索词( term1|term2|...|termN)。我还添加了flags=re.IGNORECASE 以使搜索不区分大小写:

# search function
def search(searchterms):
    return df.loc[df.apply(' '.join, axis=1)    # join text in all columns
        .str.contains(                          # check if it contains
            '|'.join([                          # regex pattern
                x.strip()                       # strip spaces
                for x in searchterms.split(',') # split by ','
            ]), flags=re.IGNORECASE)]           # case-insensitive

# test search terms
for s in ['Alendronic Acid', 'months', 'Perindopril, Allopurinol']:
    print(f'Search terms: "s"')
    print(search(s))
    print('-'*70)

输出:

Search terms: "Alendronic Acid"
         Drug Name          BP              U&E
1  Alendronic Acid  Not needed  Every 12 months
----------------------------------------------------------------------
Search terms: "months"
         Drug Name                           BP              U&E
0      Perindopril              Every 12 months       Not needed
1  Alendronic Acid                   Not needed  Every 12 months
2      Allopurinol  Whilst titrating - 3 months       Not needed
----------------------------------------------------------------------
Search terms: "Perindopril, Allopurinol"
     Drug Name                           BP         U&E
0  Perindopril              Every 12 months  Not needed
2  Allopurinol  Whilst titrating - 3 months  Not needed
----------------------------------------------------------------------

附:如果您想将搜索限制在特定列,这里有一个版本可以做到这一点(为方便起见,默认搜索所有列):

# search function
def search(searchterms, cols=None):
    
    # search columns (if None, searches in all columns)
    if cols is None:
        cols = df.columns
        
    return df.loc[df[cols].apply(' '.join, axis=1) # join text in cols
        .str.contains(                             # check if it contains
            '|'.join([                             # regex pattern
                x.strip()                          # remove spaces
                for x in searchterms.split(',')    # split by ','
            ]), flags=re.IGNORECASE)]              # make search case-insensitive

现在如果我只在Drug NameBP 中搜索months,它将不会返回Alendronic Acid,其中months 仅在U&amp;E 中找到:

search('months', ['Drug Name', 'BP'])

输出:

     Drug Name                           BP         U&E
0  Perindopril              Every 12 months  Not needed
2  Allopurinol  Whilst titrating - 3 months  Not needed

【讨论】:

【参考方案3】:

使用sets。您可以将文本列更改为集合并检查与输入的交集。其余的是纯pandas。我从不使用.query,因为它很慢。

# change your search from list to set
search = set(request.values.get('searchinput').replace(" ","").split(','))

filtered = df.loc[(df['Drug Name'].str.split().map(lambda x: set(x).intersection(search)))
                  & (df['BP'].str.split().map(lambda x: set(x).intersection(search)))]

print(filtered)

演示:

import pandas as pd

search = set(["apple", "banana", "orange"])
df = pd.DataFrame(
    "Drug Name": ["I am eating an apple", "We are banana", "nothing is here"],
    "BP": ["apple is good", "nothing is here", "nothing is there"],
    "Just": [1, 2, 3]
)


filtered = df.loc[(df['Drug Name'].str.split().map(lambda x: set(x).intersection(search)))
                  & (df['BP'].str.split().map(lambda x: set(x).intersection(search)))]

print(filtered)

#               Drug Name             BP  Just
# 0  I am eating an apple  apple is good     1

更新:

我希望结果也显示我们是香蕉,这里什么都没有,2

这需要 or 是 Pandas 的 | 而不是 and 是 Pandas 的 $

filtered = df.loc[(df['Drug Name'].str.split().map(lambda x: set(x).intersection(search)))
                  | (df['BP'].str.split().map(lambda x: set(x).intersection(search)))]

print(filtered)

#                Drug Name               BP  Just
# 0  I am eating an apple    apple is good       1
# 1          We are banana   nothing is here     2

【讨论】:

感谢您的回答,但如果用户输入了苹果、香蕉、橙子。我希望结果也显示我们是香蕉,这里什么都没有,2。 那么条件不是&amp;(和)而是|(或)。改变它,你也会有第二行。 我已经更新了答案以显示两个差异【参考方案4】:

在没有样本输入数据的情况下,我使用随机生成的数据集作为展示:

import pandas as pd
import numpy as np

df = pd.DataFrame('Drug_Name':['Drug1','Drug2','Drug3','Drug2','Drug5','Drug3']*4,
                  'Inv_Type': ['X', 'Y']*12,
                 'Quant': np.random.randint(2,20, size=24))

# Search 1
search = "Drug3"
df.query('Drug_Name==@search')

# Search 2
search2 = "Y"
df.query ('Inv_Type.str.contains(@search2, na=False)', engine='python')

# Combined (use booleans, such as & or | instead of and or or
df.query ('Drug_Name==@search & Inv_Type.str.contains(@search2, na=False)')

请注意engine='python' 应避免如文档中所述:

同样,您可以传递 engine='python' 以使用 Python 本身作为后端。不建议这样做,因为它是 与使用 numexpr 作为引擎相比效率低。

也就是说,如果你一心想要使用它,你可以这样做:

mask = df["Inv_Type"].str.contains(search2, na=False)
df.query('Drug_Name==@search & @mask')

或者,您可以完全不使用 .query() 来实现同样的目标:

df[(df['Drug_Name']==search) & df['Inv_Type'].str.contains(search2, na=False)]

【讨论】:

以上是关于如何使用 Pandas 将查询与单个外部变量结合起来的主要内容,如果未能解决你的问题,请参考以下文章

学习bash——变量

珠联璧合太香了,Pandas 与 lambda 合起来用才真棒

如何在 Python 中创建具有两列作为元组或 Pandas 数据框的单个变量?

Pandas:如何将 MultiIndex DataFrame 与单个索引 DataFrame 连接,以及自定义排序

Matplotlib与Pandas结合时对fig、ax、plt的理解

如何将多个列值连接到 Pandas 数据框中的单个列中