如何实现 SQL 的 IN
和 NOT 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'])]
但这似乎是一个可怕的组合。任何人都可以改进它吗?
~
是在 2019 年作为修改添加的。
您可以使用 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
使用 .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
countries
-> countries_to_keep
,所以我的回答无效。我已经相应地更新了我的答案。 countries_to_keep
- 是一个列表。
countries_to_keep
的语法。是否可以直接在查询中指定值列表?
如何为 pandas DataFrame 实现“in”和“not in”?
Pandas 提供了两种方法:Series.isin
和 DataFrame.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.isin、查询、列表推导(字符串数据)
除了上述方法之外,您还可以使用 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
但是,指定起来要麻烦得多,因此除非您知道自己在做什么,否则不要使用它。
最后,还有 this answer 中介绍的 DataFrame.query
。数字表达式 FTW!
我通常对这样的行进行通用过滤:
criterion = lambda row: row['countries'] not in countries
not_in = df[df.apply(criterion, axis=1)]
从答案中整理可能的解决方案:
对于 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
相当于 ~
运算符。
我想过滤掉具有 BUSINESS_ID 且也在 dfProfilesBusIds 的 BUSINESS_ID 中的 dfbc 行
dfbc = dfbc[~dfbc['BUSINESS_ID'].isin(dfProfilesBusIds['BUSINESS_ID'])]
为什么没有人谈论各种过滤方法的性能?其实这里经常会弹出这个话题(见例子)。我对大型数据集进行了自己的性能测试。这是非常有趣和有启发性的。
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()
的方法是最慢的,这并不奇怪。
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')
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])]
如果你想保持列表的顺序,一个技巧:
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
我的 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"
),
)
df = pd.Series({'countries':['US','UK','Germany','China']})
df
,我的和他的,都是DataFrame
。countries
是一个列表。df[~df.countries.isin(countries)]
产生DataFrame
,而不是Series
,而且似乎在 0.11.0.dev-14a04dd 中也能正常工作。countries
变量。好吧,OP做到了,这是继承的,但是以前做得不好的事情并不能证明现在做得不好。