【问题标题】:Dynamically filtering a pandas dataframe动态过滤熊猫数据框
【发布时间】:2018-02-06 02:19:36
【问题描述】:

我正在尝试使用三列的阈值过滤熊猫数据框

import pandas as pd
df = pd.DataFrame({"A" : [6, 2, 10, -5, 3],
                   "B" : [2, 5, 3, 2, 6],
                   "C" : [-5, 2, 1, 8, 2]})
df = df.loc[(df.A > 0) & (df.B > 2) & (df.C > -1)].reset_index(drop = True)

df
    A  B  C
0   2  5  2
1  10  3  1
2   3  6  2

但是,我想在一个函数中执行此操作,其中列的名称及其阈值在字典中提供给我。这是我的第一次尝试,效果很好。本质上,我将过滤器放在 cond 变量中并运行它:

df = pd.DataFrame({"A" : [6, 2, 10, -5, 3],
                   "B" : [2, 5, 3, 2, 6],
                   "C" : [-5, 2, 1, 8, 2]})
limits_dic = {"A" : 0, "B" : 2, "C" : -1}
cond = "df = df.loc["
for key in limits_dic.keys():
    cond += "(df." + key + " > " + str(limits_dic[key])+ ") & "
cond = cond[:-2] + "].reset_index(drop = True)"
exec(cond)
df
    A  B  C
0   2  5  2
1  10  3  1
2   3  6  2

现在,最后我把所有东西都放在一个函数中,它停止工作(也许exec 函数不喜欢在函数中使用!):

df = pd.DataFrame({"A" : [6, 2, 10, -5, 3],
                   "B" : [2, 5, 3, 2, 6],
                   "C" : [-5, 2, 1, 8, 2]})
limits_dic = {"A" : 0, "B" : 2, "C" : -1}
def filtering(df, limits_dic):
    cond = "df = df.loc["
    for key in limits_dic.keys():
        cond += "(df." + key + " > " + str(limits_dic[key])+ ") & "
    cond = cond[:-2] + "].reset_index(drop = True)"
    exec(cond)
    return(df)

df = filtering(df, limits_dic)
df
    A  B  C
0   6  2 -5
1   2  5  2
2  10  3  1
3  -5  2  8
4   3  6  2

我知道exec 函数在函数内部使用时的行为有所不同,但不知道如何解决该问题。另外,我想知道在给定两个输入的情况下,必须有一种更优雅的方法来定义一个函数来进行过滤:1)df 和 2)limits_dic = {"A" : 0, "B" : 2, "C" : -1}。我将不胜感激。

【问题讨论】:

  • 如果您更改结果的名称(cond = "df2 = df.loc["return(locals()['df2'])),它会起作用。我试图将字典添加到exec 无济于事
  • 如需更多关于pd.eval()系列函数、它们的特性和用例的信息,请访问Dynamic Expression Evaluation in pandas using pd.eval()

标签: python pandas dataframe filter exec


【解决方案1】:

两者都张贴的替代品,可能更pythonic,也可能不是:

import pandas as pd
import operator
from functools import reduce

df = pd.DataFrame({"A": [6, 2, 10, -5, 3],
                   "B": [2, 5, 3, 2, 6],
                   "C": [-5, 2, 1, 8, 2]})

limits_dic = {"A": 0, "B": 2, "C": -1}

# equiv to [df['A'] > 0, df['B'] > 2 ...]
loc_elements = [df[key] > val for key, val in limits_dic.items()]

df = df.loc[reduce(operator.and_, loc_elements)]

【讨论】:

    【解决方案2】:

    如何在不创建字符串和 df.query 的情况下做到这一点:

    limits_dic = {"A" : 0, "B" : 2, "C" : -1}
    cond = None
    
    # Build the conjunction one clause at a time 
    for key, val in limits_dic.items():
        if cond is None:
            cond = df[key] > val
        else:
            cond = cond & (df[key] > val)
    
    df.loc[cond]
    
        A  B  C
    0   2  5  2
    1  10  3  1
    2   3  6  2
    

    请注意硬编码的 (>, &) 运算符(因为我想完全按照您的示例进行操作)。

    【讨论】:

      【解决方案3】:

      如果您尝试构建动态查询,有更简单的方法。这是一个使用列表理解和str.join

      query = ' & '.join(['{}>{}'.format(k, v) for k, v in limits_dic.items()])
      

      或者,将f-strings 与 python-3.6+ 一起使用,

      query = ' & '.join([f'{k}>{v}' for k, v in limits_dic.items()])
      

      print(query)
      
      'A>0 & C>-1 & B>2'
      

      将查询字符串传递给df.query,就是为了这个目的:

      out = df.query(query)
      print(out)
      
          A  B  C
      1   2  5  2
      2  10  3  1
      4   3  6  2
      

      如果我的列名有空格或其他奇怪的字符怎么办?

      从 pandas 0.25 开始,您可以将列名包含在反引号中,这样可以正常工作:

      query = ' & '.join([f'`{k}`>{v}' for k, v in limits_dic.items()])
      

      请参阅this Stack Overflow post 了解更多信息。


      如果您想为查询获取布尔掩码,也可以使用df.eval,然后索引变得简单:

      mask = df.eval(query)
      print(mask)
      
      0    False
      1     True
      2     True
      3    False
      4     True
      dtype: bool
      
      out = df[mask]
      print(out)
      
          A  B  C
      1   2  5  2
      2  10  3  1
      4   3  6  2
      

      字符串数据

      如果需要查询使用字符串数据的列,上面的代码需要稍作修改。

      考虑(来自this answer的数据):

      df = pd.DataFrame({'gender':list('MMMFFF'),
                         'height':[4,5,4,5,5,4],
                         'age':[70,80,90,40,2,3]})
      
      print (df)
        gender  height  age
      0      M       4   70
      1      M       5   80
      2      M       4   90
      3      F       5   40
      4      F       5    2
      5      F       4    3
      

      还有列、运算符和值的列表:

      column = ['height', 'age', 'gender']
      equal = ['>', '>', '==']
      condition = [1.68, 20, 'F']
      

      这里适当的修改是:

      query = ' & '.join(f'{i} {j} {repr(k)}' for i, j, k in zip(column, equal, condition))
      df.query(query)
      
         age gender  height
      3   40      F       5
      

      有关pd.eval() 系列函数、它们的特性和用例的信息,请访问Dynamic Expression Evaluation in pandas using pd.eval()

      【讨论】:

      • 在 f-strings 中,您可以使用上面的简写 {k!r},而不是 {repr(k)}...有助于像上面这样的长表达式。
      • 同一列有多个值如何处理
      • @Abhis 那应该是什么样子?
      • @cs95 如果我的列名本身有一些运算符,例如C > D,我想比较两个这样的列。我应该在每个列名周围添加双引号,并在单引号中添加整个查询吗?
      • 关于使用“掩码”的那部分回答了我在使用“loc”以避免链式索引时如何将“查询”与选择列子集结合起来的问题。谢谢!
      【解决方案4】:

      @coldspeed 版本的替代方案:

      conditions = None
      for key, val in limit_dic.items():
          cond = df[key] > val
          if conditions is None:
              conditions = cond
          else:
              conditions = conditions & cond
      print(df[conditions])
      

      【讨论】:

      • 谢谢。我找不到一种方法来使接受的答案将引用我的代码中定义的 python 列表的isin 条件结合在一起。
      猜你喜欢
      • 2019-04-13
      • 2015-05-28
      • 2018-09-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-12-15
      • 2014-12-02
      • 2014-02-25
      相关资源
      最近更新 更多