Eu tenho um cenário em que um usuário deseja aplicar vários filtros a um objeto Pandas DataFrame ou Series. Essencialmente, quero encadear com eficiência um conjunto de filtros (operações de comparação) que são especificados em tempo de execução pelo usuário.
Os filtros devem ser aditivos (ou seja, cada um aplicado deve restringir os resultados).
Atualmente, estou usando, reindex()
mas isso cria um novo objeto a cada vez e copia os dados subjacentes (se eu entender a documentação corretamente). Portanto, isso pode ser realmente ineficiente ao filtrar uma grande série ou DataFrame.
Estou pensando que o uso apply()
, map()
ou algo semelhante poderia ser melhor. Eu sou muito novo no Pandas, embora ainda esteja tentando entender tudo.
TL; DR
Eu quero pegar um dicionário do seguinte formulário e aplicar cada operação a um determinado objeto Series e retornar um objeto Series 'filtrado'.
relops = {'>=': [1], '<=': [1]}
Exemplo longo
Começarei com um exemplo do que tenho atualmente e apenas filtrarei um único objeto Series. Abaixo está a função que estou usando atualmente:
def apply_relops(series, relops):
"""
Pass dictionary of relational operators to perform on given series object
"""
for op, vals in relops.iteritems():
op_func = ops[op]
for val in vals:
filtered = op_func(series, val)
series = series.reindex(series[filtered])
return series
O usuário fornece um dicionário com as operações que deseja executar:
>>> df = pandas.DataFrame({'col1': [0, 1, 2], 'col2': [10, 11, 12]})
>>> print df
>>> print df
col1 col2
0 0 10
1 1 11
2 2 12
>>> from operator import le, ge
>>> ops ={'>=': ge, '<=': le}
>>> apply_relops(df['col1'], {'>=': [1]})
col1
1 1
2 2
Name: col1
>>> apply_relops(df['col1'], relops = {'>=': [1], '<=': [1]})
col1
1 1
Name: col1
Novamente, o "problema" da minha abordagem acima é que acho que há muitas cópias possivelmente desnecessárias dos dados para as etapas intermediárias.
Além disso, eu gostaria de expandir isso para que o dicionário transmitido possa incluir as colunas para o operador e filtrar um DataFrame inteiro com base no dicionário de entrada. No entanto, estou assumindo que tudo o que funciona para a série pode ser facilmente expandido para um DataFrame.
df.query
epd.eval
parece ser um bom ajuste para o seu caso de uso. Para obter informações sobre apd.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 () .Respostas:
Pandas (e numpy) permitem indexação booleana , que será muito mais eficiente:
Se você deseja escrever funções auxiliares para isso, considere algo como estas:
Atualização: o pandas 0.13 possui um método de consulta para esse tipo de casos de uso, assumindo que os nomes das colunas sejam identificadores válidos para os seguintes trabalhos (e podem ser mais eficientes para quadros grandes, pois usa o numexpr nos bastidores):
fonte
df[(ge(df['col1'], 1) & le(df['col1'], 1)]
. O problema para mim realmente é que o dicionário com os filtros pode conter muitos operadores e encaderná-los é complicado. Talvez eu possa adicionar cada matriz booleana intermediária a uma matriz grande e usar apenasmap
para aplicar oand
operador a elas?f()
precisa tomar em*b
vez de apenasb
? É assim que o usuáriof()
ainda pode usar oout
parâmetro opcionallogical_and()
? Isso leva a outra pequena questão paralela. Qual é o benefício / troca de desempenho da transmissão na matriz viaout()
vs. usando a retornadalogical_and()
? Obrigado novamente!*b
é necessário porque você está passando as duas matrizesb1
eb2
precisa descompactá-las ao ligarlogical_and
. No entanto, a outra questão ainda permanece. Existe um benefício de desempenho em passar uma matriz viaout
parâmetro paralogical_and()
vs apenas usando seu 'valor de retorno?As condições de encadeamento criam longas filas, que são desencorajadas pelo pep8. O uso do método .query força o uso de strings, o que é poderoso, mas não-tônico e não muito dinâmico.
Depois que cada um dos filtros estiver no lugar, uma abordagem será
O np.logical opera e é rápido, mas não recebe mais de dois argumentos, que são tratados pelo functools.reduce.
Observe que isso ainda possui alguns redundâncias: a) o atalho não ocorre em nível global b) Cada uma das condições individuais é executada em todos os dados iniciais. Ainda assim, espero que seja eficiente o suficiente para muitos aplicativos e seja muito legível.
Você também pode fazer uma disjunção (em que apenas uma das condições precisa ser verdadeira) usando
np.logical_or
:fonte
c_1
,c_2
,c_3
, ...c_n
em uma lista, e em seguida, passandodata[conjunction(conditions_list)]
, mas obter um erroValueError: Item wrong length 5 instead of 37.
também tentoudata[conjunction(*conditions_list)]
, mas eu recebo um resultado diferente do quedata[conjunction(c_1, c_2, c_3, ... c_n )]
, não sei o que está acontecendo.data[conjunction(*conditions_list)]
faz o trabalho após a embalagem os dataframes em uma lista, e desembalar a lista no lugardf[f_2 & f_3 & f_4 & f_5 ]
comf_2 = df["a"] >= 0
etc. Não há necessidade de que a função ... (bom uso da função de ordem superior embora ...)Mais simples de todas as soluções:
Usar:
Outro exemplo , para filtrar o dataframe por valores pertencentes a fevereiro de 2018, use o código abaixo
fonte
Desde a atualização do pandas 0.22 , as opções de comparação estão disponíveis como:
e muitos mais. Essas funções retornam matriz booleana. Vamos ver como podemos usá-los:
fonte
Por que não fazer isso?
Demo:
Resultado:
Você pode ver que a coluna 'a' foi filtrada em que a> = 2.
Isso é um pouco mais rápido (tempo de digitação, não desempenho) do que o encadeamento do operador. Obviamente, você pode colocar a importação na parte superior do arquivo.
fonte
Também é possível selecionar linhas com base nos valores de uma coluna que não estão em uma lista ou são iteráveis. Criaremos uma variável booleana como antes, mas agora negaremos a variável booleana colocando ~ na frente.
Por exemplo
fonte