pandas:对DataFrame行进行复杂筛选

109

我想按每行的函数筛选行,例如:

def f(row):
  return sin(row['velocity'])/np.prod(['masses']) > 5

df = pandas.DataFrame(...)
filtered = df[apply_to_all_rows(df, f)]

或者以另一个更复杂、牵强的例子为例,

def g(row):
  if row['col1'].method1() == 1:
    val = row['col1'].method2() / row['col1'].method3(row['col3'], row['col4'])
  else:
    val = row['col2'].method5(row['col6'])
  return np.sin(val)

df = pandas.DataFrame(...)
filtered = df[apply_to_all_rows(df, g)]

我该如何做到这一点?

6个回答

158

您可以使用 DataFrame.apply 实现此功能,它沿着给定的轴应用一个函数。

In [3]: df = pandas.DataFrame(np.random.randn(5, 3), columns=['a', 'b', 'c'])

In [4]: df
Out[4]: 
          a         b         c
0 -0.001968 -1.877945 -1.515674
1 -0.540628  0.793913 -0.983315
2 -1.313574  1.946410  0.826350
3  0.015763 -0.267860 -2.228350
4  0.563111  1.195459  0.343168

In [6]: df[df.apply(lambda x: x['b'] > x['c'], axis=1)]
Out[6]: 
          a         b         c
1 -0.540628  0.793913 -0.983315
2 -1.313574  1.946410  0.826350
3  0.015763 -0.267860 -2.228350
4  0.563111  1.195459  0.343168

22
在这种情况下没有必要使用apply,普通的布尔索引就可以正常工作。df[df['b'] > df['c']]。实际上很少有需要使用apply的情况,即使是在axis=1的情况下也很少需要它。 - Ted Petrou
@TedPetrou 如果您不确定数据框中的每个元素是否是正确的类型,该怎么办?常规布尔索引支持异常处理吗? - D. Ror.

14

假设我有以下的DataFrame:

In [39]: df
Out[39]: 
      mass1     mass2  velocity
0  1.461711 -0.404452  0.722502
1 -2.169377  1.131037  0.232047
2  0.009450 -0.868753  0.598470
3  0.602463  0.299249  0.474564
4 -0.675339 -0.816702  0.799289

我可以使用sin和DataFrame.prod创建一个布尔蒙版:

In [40]: mask = (np.sin(df.velocity) / df.ix[:, 0:2].prod(axis=1)) > 0

In [41]: mask
Out[41]: 
0    False
1    False
2    False
3     True
4     True

然后使用掩码从数据框中进行选择:

In [42]: df[mask]
Out[42]: 
      mass1     mass2  velocity
3  0.602463  0.299249  0.474564
4 -0.675339 -0.816702  0.799289

3
实际上,这可能是一个不好的例子:np.sin自动广播到所有元素。如果我用一个只能一次处理一个输入的不太智能的函数来替换它会怎样? - duckworthd

9

我发现最好的方法是,在应用过滤器之前,检查df的大小是否大于0,而不是使用reduce=True来避免空df的错误(因为这个参数已经被弃用了):

def my_filter(row):
    if row.columnA == something:
        return True

    return False

if len(df.index) > 0:
    df[df.apply(my_filter, axis=1)]

6

5
我可以翻译 duckworthd's answer,但它并不完美。当数据框为空时会崩溃:
df = pandas.DataFrame(columns=['a', 'b', 'c'])
df[df.apply(lambda x: x['b'] > x['c'], axis=1)]

输出:

ValueError: Must pass DataFrame with boolean values only

对我来说,这看起来像是pandas中的一个bug,因为{ }绝对是有效的布尔值集合。有关解决方案,请参考Roy Hyunjin Han的答案

2

您可以使用 loc 属性来切片您的数据框。

根据 文档, loc 可以接受一个 可调用函数 作为参数。

In [3]: df = pandas.DataFrame(np.random.randn(5, 3), columns=['a', 'b', 'c'])

In [4]: df
Out[4]: 
          a         b         c
0 -0.001968 -1.877945 -1.515674
1 -0.540628  0.793913 -0.983315
2 -1.313574  1.946410  0.826350
3  0.015763 -0.267860 -2.228350
4  0.563111  1.195459  0.343168

# define lambda function
In [5]: myfilter = lambda x: x['b'] > x['c']

# use my lambda in loc
In [6]: df1 = df.loc[myfilter ]

如果你想将你的过滤函数myfilter与其他过滤条件结合起来

df1 = df.loc[myfilter ].loc[(df.b >= 0.5)]

这绝对是最好的答案,但代码中的一个打字错误可能是它被踩的原因。在[6]中,你应该写df1 = df.loc[myfilter]而不是df1 = df.loc[fif] - Olsgaard

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接