基于条件表达式如何从pandas DataFrame中删除行

657

我有一个pandas DataFrame,并且我想从其中删除特定列中字符串长度大于2的行。

根据这个答案,我希望能够这样做:

df[(len(df['column name']) < 2)]
但是我只得到了这个错误:

KeyError: u'no item named False'

我错在哪里了?

(注意:我知道我可以使用 df.dropna() 来删除包含任何 NaN 的行,但我不知道如何根据条件表达式删除行。)

6个回答

1517
直接回答这个问题的原始标题“如何基于条件表达式从pandas DataFrame中删除行”(我理解这不一定是提问者的问题,但可能会帮助其他用户),其中一种方法是使用“drop”方法:drop
df = df.drop(some labels)
df = df.drop(df[<some boolean condition>].index)

例子

要删除所有列中'score'小于50的行:

df = df.drop(df[df.score < 50].index)

就地版本(如评论中所指出)

df.drop(df[df.score < 50].index, inplace=True)

多重条件

(参见 布尔索引)

操作符: | 表示或, & 表示且, ~ 表示非。这些操作符必须使用括号进行分组。

要删除所有列中“score”列小于 50 且大于 20 的行

df = df.drop(df[(df.score < 50) & (df.score > 20)].index)

57
我想强调一下,drop函数支持就地替换。也就是说,你的解决方案与df.drop(df[df.score < 50].index, inplace=True)相同。不过我之前并不知道使用"index"的技巧,这帮了我很多忙。 - Quickbeam2k1
39
想指出在使用索引技巧之前,需要确保索引值是唯一的(或调用 reset_index())。我通过错误的方式发现了这一点,当时我的数据框中有太多行被删除了。 - Jay
4
我该如何删除所有列类型为str的行?我只想保留列表列类型。我尝试了以下代码: test = df.drop(df[df['col1'].dtype == str].index) 但是却出现了错误KeyError: False 我还尝试了df.drop(df[df.col1.dtype == str].index)df.drop(df[type(df.cleaned_norm_email) == str].index),但都没有起作用。能否给予建议。谢谢! @User - PyRsquared
2
这是一个老问题,但是@aquatically-challenged-fish比这个快得多。请注意,您在计算答案时会使用df[(df.score < 50) & (df.score > 20)]。如果您将其反转为df = df[(df.score >= 50) | (df.score <= 20)],您将更快地获得答案。 - Roobie Nuby
2
@Nguaial 不,它们不是一样的。它们是相反的。所以,与上面建议的删除相反,我建议保留,如 df = df[... 而不是 df = df.drop(... - Roobie Nuby
显示剩余10条评论

272

当你执行 len(df['column name']) 时,你得到的只是一个数字,即DataFrame中该列的行数(也就是该列本身的长度)。如果你想将len应用于列中的每个元素,请使用df['column name'].map(len)。因此,请尝试:

df[df['column name'].map(len) < 2]

4
我用列表推导式想出了一种方法:df[[(len(x) < 2) for x in df['列名']]],但你的更好。感谢你的帮助! - sjs
32
如果有人需要更复杂的比较,可以始终使用lambda。 df[df['column name'].map(lambda x: str(x)!=".")] - 4lberto
1
出于某种原因,除了 @4lberto 发布的选项,其他选项对我都没有用。我使用的是 pandas 0.23.4 和 python 3.6 。 - goelakash
4
我建议在最后加上 .copy(),以防您稍后需要编辑此数据帧(例如,分配新列会引发“正在尝试为数据帧的一个切片设置值”的警告)。请注意,这里只是建议,具体情况要根据您的实际需求而定。 - PlasmaBinturong

183

您可以将DataFrame分配给其过滤版本:

df = df[df.score > 50]

这比drop更快:

%%timeit
test = pd.DataFrame({'x': np.random.randn(int(1e6))})
test = test[test.x < 0]
# 54.5 ms ± 2.02 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

%%timeit
test = pd.DataFrame({'x': np.random.randn(int(1e6))})
test.drop(test[test.x > 0].index, inplace=True)
# 201 ms ± 17.9 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

%%timeit
test = pd.DataFrame({'x': np.random.randn(int(1e6))})
test = test.drop(test[test.x > 0].index)
# 194 ms ± 7.03 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

如何使用或条件检查多列? - Piyush S. Wanare
https://dev59.com/YGYr5IYBdhLWcg3whKit - somesingsomsing
10
注意,如果原始未过滤的数据框(dataframe)仍然存在引用,则过滤后的版本是对原始数据框的视图(也称为切片(slice)),如果以后需要修改过滤后的数据框(例如添加列),则会造成麻烦。在这种情况下,您可能需要进行明确的复制,例如 df=df[mask].copy()。以下是一个代码示例,通过引发警告显示出这个问题:df = pd.DataFrame([(1,0),(2,3)]) df1 = df df = df[df[0]>1] df['b'] = 'some value' - Uwe Mayer

22

我将在@User的通用解决方案基础上进行扩展,提供一种无需使用drop的替代方案。这是为了那些根据问题标题(而不是OP的问题)被引导到这里的人准备的。

假设你想要删除所有带有负值的行。一行代码解决方案如下:

df = df[(df > 0).all(axis=1)]

逐步解释:--

让我们生成一个5x5的随机正态分布数据框

np.random.seed(0)
df = pd.DataFrame(np.random.randn(5,5), columns=list('ABCDE'))
      A         B         C         D         E
0  1.764052  0.400157  0.978738  2.240893  1.867558
1 -0.977278  0.950088 -0.151357 -0.103219  0.410599
2  0.144044  1.454274  0.761038  0.121675  0.443863
3  0.333674  1.494079 -0.205158  0.313068 -0.854096
4 -2.552990  0.653619  0.864436 -0.742165  2.269755

假设条件是删除负数。一个满足该条件的布尔数据框:

Let the condition be deleting negatives. A boolean df satisfying the condition:-


df > 0
      A     B      C      D      E
0   True  True   True   True   True
1  False  True  False  False   True
2   True  True   True   True   True
3   True  True  False   True  False
4  False  True   True  False   True

布尔值系列适用于满足条件的所有行请注意,如果行中的任何元素未满足条件,则该行标记为false。

(df > 0).all(axis=1)
0     True
1    False
2     True
3    False
4    False
dtype: bool

最后根据条件从数据框中筛选出行

df[(df > 0).all(axis=1)]
      A         B         C         D         E
0  1.764052  0.400157  0.978738  2.240893  1.867558
2  0.144044  1.454274  0.761038  0.121675  0.443863

您可以将其重新赋值给df以实际删除上述的过滤操作:
df = df[(df > 0).all(axis=1)]

这可以轻松地扩展为过滤掉包含NaN(非数字条目)的行:
df = df[(~df.isnull()).all(axis=1)]

对于诸如:删除所有列E为负数的行,也可以进行简化。

df = df[(df.E>0)]

我想用一些分析数据来结束关于为什么@User的drop方案比基于原始列筛选慢的讨论:

%timeit df_new = df[(df.E>0)]
345 µs ± 10.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
%timeit dft.drop(dft[dft.E < 0].index, inplace=True)
890 µs ± 94.9 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

一列基本上就是一个Series,即一个NumPy数组,它可以无任何成本地进行索引。对于那些对底层内存组织如何影响执行速度感兴趣的人,这里有一个很好的链接,可以加速Pandas


13

在pandas中,您可以使用 str.len 来确定边界,并使用布尔结果对其进行筛选。

df[df['column name'].str.len().lt(2)]

5
如果您想根据某些列值的复杂条件删除数据框的行,则按照上述方式编写可能会很复杂。我有以下更简单的解决方案,它总是有效的。假设您想删除具有“header”的列,请首先将该列放入列表中。
text_data = df['name'].tolist()

现在对列表中的每个元素应用一些函数,并将其放入panda系列中:

text_length = pd.Series([func(t) for t in text_data])

在我的情况下,我只是试图获取令牌的数量:

text_length = pd.Series([len(t.split()) for t in text_data])

现在在数据框中添加一个额外的列,该列包含上述系列数据:
df = df.assign(text_length = text_length .values)

现在我们可以对新列应用条件,例如:

df = df[df.text_length  >  10]

def pass_filter(df, label, length, pass_type):

    text_data = df[label].tolist()

    text_length = pd.Series([len(t.split()) for t in text_data])

    df = df.assign(text_length = text_length .values)

    if pass_type == 'high':
        df = df[df.text_length  >  length]

    if pass_type == 'low':
        df = df[df.text_length  <  length]

    df = df.drop(columns=['text_length'])

    return df

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