如何使用'in'和'not in'在Pandas dataframe中进行类似于SQL的过滤。

844

如何实现SQL中INNOT IN的等效功能?

我有一个包含所需值的列表。以下是场景:

df = pd.DataFrame({'country': ['US', 'UK', 'Germany', 'China']})
countries_to_keep = ['UK', 'China']

# pseudo-code:
df[df['country'] not in countries_to_keep]

我目前的做法如下:

df = pd.DataFrame({'country': ['US', 'UK', 'Germany', 'China']})
df2 = pd.DataFrame({'country': ['UK', 'China'], 'matched': True})

# IN
df.merge(df2, how='inner', on='country')

# NOT IN
not_in = df.merge(df2, how='left', on='country')
not_in = not_in[pd.isnull(not_in['matched'])]

但这似乎是一个可怕的补救措施。有人能改进它吗?


3
相关(性能/ Pandas内部):Pandas pd.Series.isin性能分析:集合与数组 - jpp
使用值列表从pandas数据框中选择行的方法与使用值列表从pandas数据框中选择行类似,但在2019年的编辑中添加了否定符号~ - Trenton McKinney
12个回答

1467

你可以使用pd.Series.isin

要使用“IN”,请使用:something.isin(somewhere)

或者对于“NOT IN”: ~something.isin(somewhere)

以下是一个示例:

>>> df
    country
0        US
1        UK
2   Germany
3     China
>>> countries_to_keep
['UK', 'China']
>>> df.country.isin(countries_to_keep)
0    False
1     True
2    False
3     True
Name: country, dtype: bool
>>> df[df.country.isin(countries_to_keep)]
    country
1        UK
3     China
>>> df[~df.country.isin(countries_to_keep)]
    country
0        US
2   Germany

2
如果你实际上正在处理一维数组(就像在你的示例中一样),那么在你的第一行使用Series而不是DataFrame,就像@DSM所使用的:df = pd.Series({'countries':['US','UK','Germany','China']}) - TomAugspurger
4
通常情况下,我可能会漏掉一些内容。df是一个数据帧(DataFrame),包括我和他的数据帧。countries是一个列表。df [~ df.countries.isin(countries)] 生成的是一个数据帧(DataFrame),而不是一个系列(Series),并且在0.11.0.dev-14a04dd版本中也可以正常运行。 - DSM
10
这个答案让人困惑,因为你一直在重复使用countries变量。虽然这是从OP那里继承来的,但以前做得不好并不意味着现在也要做得不好。 - ifly6
2
@ifly6 :同意,我犯了同样的错误,并在遇到错误时意识到了这一点:“'DataFrame'对象没有'countries'属性”。 - le_llama
4
对于那些被波浪号符号(像我一样)弄糊涂的人,请参考以下链接:https://dev59.com/F2sy5IYBdhLWcg3w0RM4 - bmurauer
这个 .isin() 是一个矢量化函数吗? - Wenuka

159
使用.query()方法的替代解决方案:
In [5]: df.query("country in @countries_to_keep")
Out[5]:
  countries
1        UK
3     China

In [6]: df.query("country not in @countries_to_keep")
Out[6]:
  countries
0        US
2   Germany

11
".query 更易读,特别是对于“不在”这种情况,比遥远的波浪符号更好。谢谢!" - Mike Honey
3
@countries 是什么?另一个数据框还是列表? - Itération 122442
3
@FlorianCastelain,有人在原问题中将一个变量重新命名为:countries-> countries_to_keep,因此我的答案已经失效。我已经相应地更新了我的答案。countries_to_keep-是一个列表。 - MaxU - stand with Ukraine
2
确实是最易读的解决方案。我想知道是否存在语法来避免创建countries_to_keep。是否可以直接在查询中指定值列表? - Maxim.K
@Maxim.K,当然可以使用列表代替变量:df.query("countries in ['UK', 'China']") - MaxU - stand with Ukraine
显示剩余2条评论

93
如何在pandas DataFrame中实现“in”和“not in”?
Pandas提供了两种方法:Series.isinDataFrame.isin,分别用于Series和DataFrames。

根据一列过滤DataFrame(也适用于Series)

最常见的情况是在特定列上应用isin条件以过滤DataFrame中的行。

df = pd.DataFrame({'countries': ['US', 'UK', 'Germany', np.nan, 'China']})
df
  countries
0        US
1        UK
2   Germany
3     China

c1 = ['UK', 'China']             # list
c2 = {'Germany'}                 # set
c3 = pd.Series(['China', 'US'])  # Series
c4 = np.array(['US', 'UK'])      # array

Series.isin接受多种类型的输入。以下方式都是获取所需内容的有效方式:

df['countries'].isin(c1)

0    False
1     True
2    False
3    False
4     True
Name: countries, dtype: bool

# `in` operation
df[df['countries'].isin(c1)]

  countries
1        UK
4     China

# `not in` operation
df[~df['countries'].isin(c1)]

  countries
0        US
2   Germany
3       NaN

# Filter with `set` (tuples work too)
df[df['countries'].isin(c2)]

  countries
2   Germany

# Filter with another Series
df[df['countries'].isin(c3)]

  countries
0        US
4     China

# Filter with array
df[df['countries'].isin(c4)]

  countries
0        US
1        UK

多列过滤

有时,您需要对多个列应用“in”成员检查,并使用一些搜索词进行过滤。

df2 = pd.DataFrame({
    'A': ['x', 'y', 'z', 'q'], 'B': ['w', 'a', np.nan, 'x'], 'C': np.arange(4)})
df2

   A    B  C
0  x    w  0
1  y    a  1
2  z  NaN  2
3  q    x  3

c1 = ['x', 'w', 'p']

要对列"A"和"B"同时应用isin条件,请使用DataFrame.isin

df2[['A', 'B']].isin(c1)

      A      B
0   True   True
1  False  False
2  False  False
3  False   True

因此,要保留至少有一列为True的行,我们可以沿着第一个轴使用any

df2[['A', 'B']].isin(c1).any(axis=1)

0     True
1    False
2    False
3     True
dtype: bool

df2[df2[['A', 'B']].isin(c1).any(axis=1)]

   A  B  C
0  x  w  0
3  q  x  3

请注意,如果您想搜索每一列,只需省略列选择步骤并执行{{}}。
df2.isin(c1).any(axis=1)

同样地,要保留所有列都是True的行,使用与之前相同的方式使用all
df2[df2[['A', 'B']].isin(c1).all(axis=1)]

   A  B  C
0  x  w  0

值得一提的方法: numpy.isin, query, 列表推导式(字符串数据)

除了上面描述的方法之外,你还可以使用numpy中的等效方法:numpy.isin

# `in` operation
df[np.isin(df['countries'], c1)]

  countries
1        UK
4     China

# `not in` operation
df[np.isin(df['countries'], c1, invert=True)]

  countries
0        US
2   Germany
3       NaN

考虑它的价值在哪里?由于开销较低,NumPy函数通常比其pandas等效函数快一些。由于这是一个元素级操作,不依赖于索引对齐,在很少情况下,该方法不是pandas“isin”的适当替代品。
使用字符串时,Pandas例程通常是迭代的,因为字符串操作难以向量化。有很多证据表明在此处列表理解会更快。现在我们改用“in”检查。
c1_set = set(c1) # Using `in` with `sets` is a constant time operation... 
                 # This doesn't matter for pandas because the implementation differs.
# `in` operation
df[[x in c1_set for x in df['countries']]]

  countries
1        UK
4     China

# `not in` operation
df[[x not in c1_set for x in df['countries']]]

  countries
0        US
2   Germany
3       NaN

这种方法更难以指定,除非你知道自己在做什么,否则不要使用它。

最后,还有一个DataFrame.query方法,在这个答案中已经被介绍过了。numexpr FTW!


3
我喜欢它,但如果我想比较df3中与df1列isin的列呢?那会是什么样子? - Arthur D. Howland

19

我通常会按照以下方式对行进行通用过滤:

criterion = lambda row: row['countries'] not in countries
not_in = df[df.apply(criterion, axis=1)]

16
明确:这比 @DSM 的解决方案要慢得多,他的方案是矢量化的。 - Jeff
2
@Jeff 我也是这么想的,但当我需要过滤pandas中没有直接支持的内容时,我就会退而求其次。(我本来想说“像.startwith或正则匹配之类的”,但刚刚发现Series.str有所有这些功能!) - Kos

17

从答案中整理出可能的解决方案:

对于 IN 操作: df[df['A'].isin([3, 6])]

对于 NOT IN 操作:

  1. df[-df["A"].isin([3, 6])]

  2. df[~df["A"].isin([3, 6])]

  3. df[df["A"].isin([3, 6]) == False]

  4. df[np.logical_not(df["A"].isin([3, 6]))]


7
这段话的意思是:“这段话主要是重复其他回答中提到的信息。使用logical_not函数等同于使用~运算符,但前者比后者更啰嗦一些。” - cs95

12

我想筛选掉dfbc行中的BUSINESS_ID也在dfProfilesBusIds的BUSINESS_ID中的行。

dfbc = dfbc[~dfbc['BUSINESS_ID'].isin(dfProfilesBusIds['BUSINESS_ID'])]

6
可以像被接受的答案那样否定isin,而不是将其与False进行比较。 - OneCricketeer

12
为什么没有人谈论不同过滤方法的性能?事实上,这个话题经常在这里出现(见示例)。我为一组大数据集进行了自己的性能测试。结果非常有趣且富有教益。
df = pd.DataFrame({'animals': np.random.choice(['cat', 'dog', 'mouse', 'birds'], size=10**7), 
                   'number': np.random.randint(0,100, size=(10**7,))})

df.info()

<class 'pandas.core.frame.DataFrame'>
RangeIndex: 10000000 entries, 0 to 9999999
Data columns (total 2 columns):
 #   Column   Dtype 
---  ------   ----- 
 0   animals  object
 1   number   int64 
dtypes: int64(1), object(1)
memory usage: 152.6+ MB

%%timeit
# .isin() by one column
conditions = ['cat', 'dog']
df[df.animals.isin(conditions)]

367 ms ± 2.34 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

%%timeit
# .query() by one column
conditions = ['cat', 'dog']
df.query('animals in @conditions')

395 ms ± 3.9 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

%%timeit
# .loc[]
df.loc[(df.animals=='cat')|(df.animals=='dog')]

987 ms ± 5.17 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

%%timeit
df[df.apply(lambda x: x['animals'] in ['cat', 'dog'], axis=1)]

41.9 s ± 490 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

%%timeit
new_df = df.set_index('animals')
new_df.loc[['cat', 'dog'], :]

3.64 s ± 62.5 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

%%timeit
new_df = df.set_index('animals')
new_df[new_df.index.isin(['cat', 'dog'])]

469 ms ± 8.98 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

%%timeit
s = pd.Series(['cat', 'dog'], name='animals')
df.merge(s, on='animals', how='inner')

796 ms ± 30.9 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

因此,isin方法被证明是最快的方法,而apply()方法是最慢的,这并不奇怪。

7
您还可以在 .query() 中使用 .isin()
df.query('country.isin(@countries_to_keep).values')

# Or alternatively:
df.query('country.isin(["UK", "China"]).values')

为了否定你的查询,请使用~:
df.query('~country.isin(@countries_to_keep).values')

更新:

另一种方法是使用比较运算符:

df.query('country == @countries_to_keep')

# Or alternatively:
df.query('country == ["UK", "China"]')

如果要否定查询,请使用!=

df.query('country != @countries_to_keep')

很好知道,尽管这比使用query内部的innot in此答案不太易读。有趣的是,query支持这两种方法! - LondonRob

5
df = pd.DataFrame({'countries':['US','UK','Germany','China']})
countries = ['UK','China']

实现于:

df[df.countries.isin(countries)]

实现“不在”,就像其他国家一样:

df[df.countries.isin([x for x in np.unique(df.countries) if x not in countries])]

3

如果你想保持列表的顺序,可以使用以下技巧:

df = pd.DataFrame({'country': ['US', 'UK', 'Germany', 'China']})
countries_to_keep = ['Germany', 'US']


ind=[df.index[df['country']==i].tolist() for i in countries_to_keep]
flat_ind=[item for sublist in ind for item in sublist]

df.reindex(flat_ind)

   country
2  Germany
0       US

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