Como filtrar o dataframe do Pandas usando 'in' e 'not in' como no SQL

434

Como posso obter os equivalentes de SQL INeNOT IN ?

Eu tenho uma lista com os valores necessários. Aqui está o cenário:

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

# pseudo-code:
df[df['countries'] not in countries]

Minha maneira atual de fazer isso é a seguinte:

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

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

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

Mas isso parece um sentimento horrível. Alguém pode melhorar isso?

LondonRob
fonte
1
Eu acho que sua solução é a melhor solução. O seu pode cobrir IN, NOT_IN de várias colunas.
Bruce Jung
Deseja testar em uma única coluna ou em várias colunas?
smci 17/07/2015
1
Relacionados (de desempenho / pandas internos): desempenho pandas pd.Series.isin com o conjunto de contra matriz
jpp

Respostas:

821

Você pode usar pd.Series.isin.

Para "IN", use: something.isin(somewhere)

Ou para "NOT IN": ~something.isin(somewhere)

Como um exemplo trabalhado:

>>> df
  countries
0        US
1        UK
2   Germany
3     China
>>> countries
['UK', 'China']
>>> df.countries.isin(countries)
0    False
1     True
2    False
3     True
Name: countries, dtype: bool
>>> df[df.countries.isin(countries)]
  countries
1        UK
3     China
>>> df[~df.countries.isin(countries)]
  countries
0        US
2   Germany
DSM
fonte
1
Apenas um FYI, o @LondonRob tinha o dele como DataFrame e o seu é uma série. O DataFrame isinfoi adicionado em 0,13.
TomAugspurger
Alguma sugestão de como fazer isso com os pandas 0.12.0? É a versão atual lançada. (Talvez eu deva esperar 0,13 ?!)
LondonRob
Se você está lidando com matrizes unidimensionais (como no exemplo), na primeira linha, use uma série em vez de um DataFrame, como o @DSM usado: df = pd.Series({'countries':['US','UK','Germany','China']})
TomAugspurger
2
@ TomAugspurger: como sempre, provavelmente estou perdendo alguma coisa. df, tanto o meu quanto o dele, é um DataFrame. countriesé uma lista. df[~df.countries.isin(countries)]produz a DataFrame, não a Seriese parece funcionar mesmo em 0.11.0.dev-14a04dd.
DSM
7
Essa resposta é confusa porque você continua reutilizando a countriesvariável. Bem, o OP faz isso, e isso é herdado, mas que algo é feito mal antes, não justifica fazê-lo agora.
ifly6
63

Solução alternativa que usa o método .query () :

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

In [6]: df.query("countries not in @countries")
Out[6]:
  countries
0        US
2   Germany
MaxU
fonte
10
@LondonRob querynão é mais experimental.
Paul Rougieux 20/06/19
38

Como implementar 'in' e 'not in' para um DataFrame de pandas?

O Pandas oferece dois métodos: Series.isine DataFrame.isinpara Series e DataFrames, respectivamente.


Filtrar DataFrame com base em uma coluna (também se aplica a séries)

O cenário mais comum é aplicar uma isincondição em uma coluna específica para filtrar linhas em um 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.isinaceita vários tipos como entradas. A seguir, são apresentadas todas as formas válidas de obter o que você deseja:

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

Filtrar em MUITAS colunas

Às vezes, convém aplicar uma verificação de associação "com" com alguns termos de pesquisa em várias colunas,

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

Para aplicar a isincondição às duas colunas "A" e "B", use DataFrame.isin:

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

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

A partir disso, para manter as linhas onde há pelo menos uma colunaTrue , podemos usar anyao longo do primeiro eixo:

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

Observe que, se você deseja pesquisar todas as colunas, omite a etapa de seleção de colunas e faça

df2.isin(c1).any(axis=1)

Da mesma forma, para manter as linhas onde estão TODAS as colunasTrue , use allda mesma maneira que antes.

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

   A  B  C
0  x  w  0

Notável Menções: numpy.isin,query , compreensões lista (dados de cadeia)

Além dos métodos descritos acima, você também pode usar o equivalente 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

Por que vale a pena considerar? As funções do NumPy geralmente são um pouco mais rápidas que seus equivalentes de pandas, devido à menor sobrecarga. Como essa é uma operação elementar que não depende do alinhamento do índice, há muito poucas situações em que esse método não é um substituto apropriado para os pandas.isin .

As rotinas do Pandas geralmente são iterativas ao trabalhar com strings, porque é difícil vetorizar operações de strings. Há muitas evidências para sugerir que a compreensão da lista será mais rápida aqui. . Recorremos a um incheque agora.

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

No entanto, é muito mais difícil especificar, portanto, não o use, a menos que você saiba o que está fazendo.

Por fim, há também o DataFrame.queryque foi abordado nesta resposta . numexpr FTW!

cs95
fonte
Eu gosto, mas e se eu quiser comparar uma coluna no DF3 que está na coluna DF1? Como seria isso?
Arthur D. Howland
12

Eu costumo fazer filtragem genérica em linhas como esta:

criterion = lambda row: row['countries'] not in countries
not_in = df[df.apply(criterion, axis=1)]
Kos
fonte
10
FYI, este é muito mais lento do que soln @DSM que é vetorizado
Jeff
@ Jeff, eu esperaria isso, mas é nisso que eu recorro quando preciso filtrar algo indisponível nos pandas diretamente. (Eu estava prestes a dizer "como .startwith ou correspondência de regex, mas só descobri sobre Series.str que tem tudo isso!)
Kos
7

Eu queria filtrar as linhas dfbc que tinham um BUSINESS_ID que também estava no BUSINESS_ID de dfProfilesBusIds

dfbc = dfbc[~dfbc['BUSINESS_ID'].isin(dfProfilesBusIds['BUSINESS_ID'])]
Sam Henderson
fonte
5
Você pode negar o ISIN (como feito na resposta aceita) em vez de comparar para Falso
OneCricketeer
6

Reunindo possíveis soluções a partir das respostas:

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

Para 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]))]

Abhishek Gaur
fonte
3
Isso repete principalmente informações de outras respostas. Usar logical_noté um equivalente da boca do ~operador.
cs95
3
df = pd.DataFrame({'countries':['US','UK','Germany','China']})
countries = ['UK','China']

implementar em :

df[df.countries.isin(countries)]

implementar não nos países de descanso:

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