如何使用 SQL 中的“in”和“not in”过滤 Pandas 数据帧

Posted

技术标签:

【中文标题】如何使用 SQL 中的“in”和“not in”过滤 Pandas 数据帧【英文标题】:How to filter Pandas dataframe using 'in' and 'not in' like in SQL 【发布时间】:2020-12-24 18:39:25 【问题描述】:

如何实现 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'])]

但这似乎是一个可怕的组合。有人可以改进吗?

【问题讨论】:

相关(性能/pandas 内部):Pandas pd.Series.isin performance with set versus array Use a list of values to select rows from a pandas dataframe 类似,但否定 ~ 在 2019 年作为编辑添加。 【参考方案1】:

您可以使用pd.Series.isin

对于“IN”使用:something.isin(somewhere)

或“未加入”:~something.isin(somewhere)

作为一个工作示例:

import pandas as pd

>>> 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

【讨论】:

如果您实际上是在处理一维数组(就像在您的示例中一样),那么在您的第一行使用 Series 而不是 DataFrame,例如使用的@DSM:df = pd.Series('countries':['US','UK','Germany','China']) @TomAugspurger:像往常一样,我可能遗漏了一些东西。 df,我的和他的,都是DataFramecountries 是一个列表。 df[~df.countries.isin(countries)] 产生 DataFrame,而不是 Series,而且似乎在 0.11.0.dev-14a04dd 中也能正常工作。 这个答案令人困惑,因为您一直在重复使用 countries 变量。好吧,OP 做到了,这是继承的,但是以前做得不好并不能证明现在做得不好。 @ifly6 :同意,我犯了同样的错误,当我收到错误时意识到:“'DataFrame' object has no attribute 'countries' 对于被波浪号弄糊涂的人(比如我):***.com/questions/8305199/…【参考方案2】:

使用.query() 方法的替代解决方案:

In [5]: df.query("countries in @countries_to_keep")
Out[5]:
  countries
1        UK
3     China

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

【讨论】:

.query 更具可读性。特别是对于“不在”场景,而不是遥远的波浪号。谢谢! @countries 是什么?另一个数据框?一份清单? @FlorianCastelain 国家是您要检查的列,OP 称为此列 @FlorianCastelain,有人在原始问题中重命名了一个变量:countries -> countries_to_keep,所以我的答案已经失效。我已经相应地更新了我的答案。 countries_to_keep - 是一个列表。 确实是最易读的解决方案。我想知道是否存在避免创建countries_to_keep 的语法。是否可以直接在查询中指定值列表?【参考方案3】:

如何为 pandas DataFrame 实现 'in' 和 'not in'?

Pandas 提供了两种方法:Series.isinDataFrame.isin 分别用于 Series 和 DataFrames。


基于 ONE Column 过滤 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']

要将isin 条件应用于“A”和“B”列,请使用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.isinquery、列表推导(字符串数据)

除了上述方法之外,您还可以使用 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 例程通常是迭代的,因为字符串操作很难向量化。 There is a lot of evidence to suggest that list comprehensions will be faster here.。 我们现在求助于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 已在this answer 中介绍。 numexpr FTW!

【讨论】:

我喜欢它,但是如果我想比较 df3 中位于 df1 列中的列怎么办?那会是什么样子?【参考方案4】:

我通常对这样的行进行通用过滤:

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

【讨论】:

仅供参考,这比矢量化的@DSM soln 慢得多 @Jeff 我希望如此,但当我需要直接过滤 pandas 中不可用的内容时,我会采用这种方式。 (我正要说“像 .startwith 或正则表达式匹配,但刚刚发现 Series.str 拥有所有这些!)【参考方案5】:

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

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

对于不在:

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

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

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

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

【讨论】:

这主要重复来自其他答案的信息。使用logical_not 相当于~ 运算符。【参考方案6】:

我想过滤掉具有 BUSINESS_ID 且也在 dfProfilesBusIds 的 BUSINESS_ID 中的 dfbc 行

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

【讨论】:

您可以否定 isin(如已接受的答案中所做的那样),而不是与 False 进行比较【参考方案7】:
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])]

【讨论】:

【参考方案8】:

如果你想保持列表的顺序,一个技巧:

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

【讨论】:

【参考方案9】:

为什么没有人谈论各种过滤方法的性能?其实这里经常会弹出这个话题(见例子)。我对大型数据集进行了自己的性能测试。这很有趣,也很有启发性。

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() 方法是最慢的,这并不奇怪。

【讨论】:

【参考方案10】:

我的 2c 价值: 我需要一个数据框的 in 和 ifelse 语句的组合,这对我有用。

sale_method = pd.DataFrame(model_data["Sale Method"].str.upper())
sale_method["sale_classification"] = np.where(
    sale_method["Sale Method"].isin(["PRIVATE"]),
    "private",
    np.where(
        sale_method["Sale Method"].str.contains("AUCTION"), "auction", "other"
    ),
)

【讨论】:

【参考方案11】:

您也可以在.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')

【讨论】:

以上是关于如何使用 SQL 中的“in”和“not in”过滤 Pandas 数据帧的主要内容,如果未能解决你的问题,请参考以下文章

sql中的in与not in,exists与not exists的区别

sql中的in与not in,exists与not exists的区别

sql中的in与not in,exists与not exists的区别

浅谈sql中的in与not in,exists与not exists的区别

如何在 SQL 子查询中使用 NOT IN? [复制]

没有 Not In 和 In 使用连接的 SQL 查询优化