pandas: filtra linhas do DataFrame com encadeamento do operador


329

A maioria das operações em pandaspode ser realizada com o encadeamento de operador ( groupby, aggregate, apply, etc.), mas a única maneira que eu verificou-se linhas de filtro é através de indexação suporte normais

df_filtered = df[df['column'] == value]

Isso não é atraente, pois exige que eu atribua dfa uma variável antes de poder filtrar seus valores. Existe algo mais como o seguinte?

df_filtered = df.mask(lambda x: x['column'] == value)

df.querye pd.evalparece ser um bom ajuste para este caso de uso. Para obter informações sobre a pd.eval()família de funções, seus recursos e casos de uso, visite Avaliação de Expressão Dinâmica em pandas usando pd.eval () .
cs95

Respostas:


384

Não tenho certeza do que você deseja e sua última linha de código também não ajuda, mas de qualquer maneira:

A filtragem "encadeada" é feita "encadeando" os critérios no índice booleano.

In [96]: df
Out[96]:
   A  B  C  D
a  1  4  9  1
b  4  5  0  2
c  5  5  1  0
d  1  3  9  6

In [99]: df[(df.A == 1) & (df.D == 6)]
Out[99]:
   A  B  C  D
d  1  3  9  6

Se você deseja encadear métodos, pode adicionar seu próprio método de máscara e usá-lo.

In [90]: def mask(df, key, value):
   ....:     return df[df[key] == value]
   ....:

In [92]: pandas.DataFrame.mask = mask

In [93]: df = pandas.DataFrame(np.random.randint(0, 10, (4,4)), index=list('abcd'), columns=list('ABCD'))

In [95]: df.ix['d','A'] = df.ix['a', 'A']

In [96]: df
Out[96]:
   A  B  C  D
a  1  4  9  1
b  4  5  0  2
c  5  5  1  0
d  1  3  9  6

In [97]: df.mask('A', 1)
Out[97]:
   A  B  C  D
a  1  4  9  1
d  1  3  9  6

In [98]: df.mask('A', 1).mask('D', 6)
Out[98]:
   A  B  C  D
d  1  3  9  6

2
Ótima resposta! Então (df.A == 1) & (df.D == 6), o "&" é um operador sobrecarregado no Pandas?
Shawn


Essa é uma solução muito boa - eu nem sabia que você podia usar métodos como este em python. Uma função como essa seria muito boa de ter no próprio Pandas.
precisa saber é o seguinte

O único problema que tenho com isso é o uso de pandas.. Você deveria import pandas as pd.
Daisuke Aramaki

3
De fato, import pandas as pdé uma prática comum agora. Duvido que foi quando eu respondi a pergunta.
Wouter Overmeire

108

Os filtros podem ser encadeados usando uma consulta do Pandas :

df = pd.DataFrame(np.random.randn(30, 3), columns=['a','b','c'])
df_filtered = df.query('a > 0').query('0 < b < 2')

Os filtros também podem ser combinados em uma única consulta:

df_filtered = df.query('a > 0 and 0 < b < 2')

3
Se você precisar se referir a variáveis ​​python na sua consulta, a documentação diz: "Você pode se referir a variáveis ​​no ambiente prefixando-as com um caractere '@' como @a + b". Observe que o seguinte é válido: df.query('a in list([1,2])'), s = set([1,2]); df.query('a in @s').
user3780389

2
Por outro lado, parece que a avaliação da consulta falhará se o nome da sua coluna tiver certos caracteres especiais: por exemplo, "Place.Name".
user3780389

2
O encadeamento é para o qual a consulta foi projetada.
usar o seguinte código

66

A resposta do @lodagro é ótima. Eu o estenderia generalizando a função de máscara como:

def mask(df, f):
  return df[f(df)]

Então você pode fazer coisas como:

df.mask(lambda x: x[0] < 0).mask(lambda x: x[1] > 0)

8
Uma generalização útil! Eu gostaria que já estivesse integrado diretamente no DataFrames!
duckworthd

24

Desde a versão 0.18.1, o .locmétodo aceita uma chamada para seleção. Juntamente com as funções lambda, você pode criar filtros encadeáveis ​​muito flexíveis:

import numpy as np
import pandas as pd

df = pd.DataFrame(np.random.randint(0,100,size=(100, 4)), columns=list('ABCD'))
df.loc[lambda df: df.A == 80]  # equivalent to df[df.A == 80] but chainable

df.sort_values('A').loc[lambda df: df.A > 80].loc[lambda df: df.B > df.A]

Se tudo o que você está fazendo é filtrar, também pode omitir o .loc.


16

Eu ofereço isso para exemplos adicionais. Esta é a mesma resposta que https://stackoverflow.com/a/28159296/

Adicionarei outras edições para tornar esta postagem mais útil.

pandas.DataFrame.query
queryfoi feito exatamente para esse fim. Considere o quadro de dadosdf

import pandas as pd
import numpy as np

np.random.seed([3,1415])
df = pd.DataFrame(
    np.random.randint(10, size=(10, 5)),
    columns=list('ABCDE')
)

df

   A  B  C  D  E
0  0  2  7  3  8
1  7  0  6  8  6
2  0  2  0  4  9
3  7  3  2  4  3
4  3  6  7  7  4
5  5  3  7  5  9
6  8  7  6  4  7
7  6  2  6  6  5
8  2  8  7  5  8
9  4  7  6  1  5

Vamos usar querypara filtrar todas as linhas ondeD > B

df.query('D > B')

   A  B  C  D  E
0  0  2  7  3  8
1  7  0  6  8  6
2  0  2  0  4  9
3  7  3  2  4  3
4  3  6  7  7  4
5  5  3  7  5  9
7  6  2  6  6  5

Que nós encadeamos

df.query('D > B').query('C > B')
# equivalent to
# df.query('D > B and C > B')
# but defeats the purpose of demonstrating chaining

   A  B  C  D  E
0  0  2  7  3  8
1  7  0  6  8  6
4  3  6  7  7  4
5  5  3  7  5  9
7  6  2  6  6  5

Não é basicamente a mesma resposta que stackoverflow.com/a/28159296 Há algo faltando nessa resposta que você acha que deveria ser esclarecido?
bscan

9

Eu tinha a mesma pergunta, exceto que queria combinar os critérios em uma condição OR. O formato fornecido por Wouter Overmeire combina os critérios em uma condição AND, de modo que ambos devem ser satisfeitos:

In [96]: df
Out[96]:
   A  B  C  D
a  1  4  9  1
b  4  5  0  2
c  5  5  1  0
d  1  3  9  6

In [99]: df[(df.A == 1) & (df.D == 6)]
Out[99]:
   A  B  C  D
d  1  3  9  6

Mas descobri que, se você envolver cada condição (... == True)e associar os critérios a um pipe, os critérios serão combinados em uma condição OR, satisfeitos sempre que um deles for verdadeiro:

df[((df.A==1) == True) | ((df.D==6) == True)]

12
Não df[(df.A==1) | (df.D==6)]seria suficiente para o que você está tentando realizar?
eenblam

Não, não seria porque fornece resultados boleanos (True vs False), em vez de estar acima do qual filtrar todos os dados que satisfazem a condição. Espero que eu tenha deixado claro.
MGB.py

8

O pandas fornece duas alternativas à resposta de Wouter Overmeire, que não exigem nenhuma substituição. Um é .loc[.]com um callable, como em

df_filtered = df.loc[lambda x: x['column'] == value]

o outro é .pipe() , como em

df_filtered = df.pipe(lambda x: x['column'] == value)

7

Minha resposta é semelhante às outras. Se você não quiser criar uma nova função, poderá usar o que os pandas já definiram para você. Use o método de tubulação.

df.pipe(lambda d: d[d['column'] == value])

ISSO é o que você deseja se deseja encadear comandos comoa.join(b).pipe(lambda df: df[df.column_to_filter == 'VALUE'])
displayname 14/17

4

Se você deseja aplicar todas as máscaras booleanas comuns, bem como uma máscara de uso geral, pode agrupar o seguinte em um arquivo e simplesmente atribuir todas elas da seguinte maneira:

pd.DataFrame = apply_masks()

Uso:

A = pd.DataFrame(np.random.randn(4, 4), columns=["A", "B", "C", "D"])
A.le_mask("A", 0.7).ge_mask("B", 0.2)... (May be repeated as necessary

É um pouco hacky, mas pode tornar as coisas um pouco mais limpas se você estiver cortando e alterando continuamente os conjuntos de dados de acordo com os filtros. Há também um filtro de uso geral adaptado de Daniel Velkov acima na função gen_mask que você pode usar com funções lambda ou, se desejar.

Arquivo a ser salvo (eu uso masks.py):

import pandas as pd

def eq_mask(df, key, value):
    return df[df[key] == value]

def ge_mask(df, key, value):
    return df[df[key] >= value]

def gt_mask(df, key, value):
    return df[df[key] > value]

def le_mask(df, key, value):
    return df[df[key] <= value]

def lt_mask(df, key, value):
    return df[df[key] < value]

def ne_mask(df, key, value):
    return df[df[key] != value]

def gen_mask(df, f):
    return df[f(df)]

def apply_masks():

    pd.DataFrame.eq_mask = eq_mask
    pd.DataFrame.ge_mask = ge_mask
    pd.DataFrame.gt_mask = gt_mask
    pd.DataFrame.le_mask = le_mask
    pd.DataFrame.lt_mask = lt_mask
    pd.DataFrame.ne_mask = ne_mask
    pd.DataFrame.gen_mask = gen_mask

    return pd.DataFrame

if __name__ == '__main__':
    pass

3

Essa solução é mais imprudente em termos de implementação, mas acho muito mais limpa em termos de uso, e certamente é mais geral do que as outras propostas.

https://github.com/toobaz/generic_utils/blob/master/generic_utils/pandas/where.py

Você não precisa baixar o repositório inteiro: salvando o arquivo e fazendo

from where import where as W

deve ser suficiente. Então você o usa assim:

df = pd.DataFrame([[1, 2, True],
                   [3, 4, False], 
                   [5, 7, True]],
                  index=range(3), columns=['a', 'b', 'c'])
# On specific column:
print(df.loc[W['a'] > 2])
print(df.loc[-W['a'] == W['b']])
print(df.loc[~W['c']])
# On entire - or subset of a - DataFrame:
print(df.loc[W.sum(axis=1) > 3])
print(df.loc[W[['a', 'b']].diff(axis=1)['b'] > 1])

Um exemplo de uso um pouco menos estúpido:

data = pd.read_csv('ugly_db.csv').loc[~(W == '$null$').any(axis=1)]

A propósito: mesmo no caso em que você está apenas usando colunas booleanas,

df.loc[W['cond1']].loc[W['cond2']]

pode ser muito mais eficiente do que

df.loc[W['cond1'] & W['cond2']]

porque avalia cond2apenas onde cond1estáTrue .

AVISO LEGAL: Eu dei essa resposta pela primeira vez em outro lugar porque não tinha visto isso.


2

Só quero adicionar uma demonstração usando loc para filtrar não apenas por linhas, mas também por colunas e alguns méritos à operação em cadeia.

O código abaixo pode filtrar as linhas por valor.

df_filtered = df.loc[df['column'] == value]

Ao modificar um pouco, você também pode filtrar as colunas.

df_filtered = df.loc[df['column'] == value, ['year', 'column']]

Então, por que queremos um método encadeado? A resposta é que é fácil ler se você tem muitas operações. Por exemplo,

res =  df\
    .loc[df['station']=='USA', ['TEMP', 'RF']]\
    .groupby('year')\
    .agg(np.nanmean)

2

Isso não é atraente, pois exige que eu atribua dfa uma variável antes de poder filtrar seus valores.

df[df["column_name"] != 5].groupby("other_column_name")

parece funcionar: você também pode aninhar o []operador. Talvez eles tenham adicionado desde que você fez a pergunta.


1
Isso faz pouco sentido em uma cadeia, porque dfagora não necessariamente faz referência à saída da parte anterior da cadeia.
Daan Luttik 04/06/19

@ DaanLuttik: concordou, não é encadeamento, mas sim aninhamento. Melhor para você?
serv-inc

1

Se você configurar suas colunas para pesquisar como índices, poderá usar DataFrame.xs()uma seção transversal. Isso não é tão versátil quanto as queryrespostas, mas pode ser útil em algumas situações.

import pandas as pd
import numpy as np

np.random.seed([3,1415])
df = pd.DataFrame(
    np.random.randint(3, size=(10, 5)),
    columns=list('ABCDE')
)

df
# Out[55]: 
#    A  B  C  D  E
# 0  0  2  2  2  2
# 1  1  1  2  0  2
# 2  0  2  0  0  2
# 3  0  2  2  0  1
# 4  0  1  1  2  0
# 5  0  0  0  1  2
# 6  1  0  1  1  1
# 7  0  0  2  0  2
# 8  2  2  2  2  2
# 9  1  2  0  2  1

df.set_index(['A', 'D']).xs([0, 2]).reset_index()
# Out[57]: 
#    A  D  B  C  E
# 0  0  2  2  2  2
# 1  0  2  1  1  0

1

Você também pode aproveitar a biblioteca numpy para operações lógicas. É bem rápido.

df[np.logical_and(df['A'] == 1 ,df['B'] == 6)]
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.