Tenho visto muitas respostas postadas para perguntas no Stack Overflow envolvendo o uso do método Pandas apply
. Eu também vi usuários comentando abaixo deles dizendo que " apply
é lento e deve ser evitado".
Eu li muitos artigos sobre o tópico de desempenho que explicam que apply
é lento. Também vi um aviso de isenção de responsabilidade nos documentos sobre como apply
é simplesmente uma função de conveniência para passar UDFs (não consigo encontrar isso agora). Portanto, o consenso geral é que apply
deve ser evitado, se possível. No entanto, isso levanta as seguintes questões:
- Se
apply
é tão ruim, por que está na API? - Como e quando devo tornar meu código
apply
grátis? - Existem situações em que
apply
é bom (melhor do que outras soluções possíveis)?
python
pandas
performance
apply
cs95
fonte
fonte
returns.add(1).apply(np.log)
vs.np.log(returns.add(1)
é um caso emapply
que geralmente será ligeiramente mais rápido, que é a caixa verde inferior direita no diagrama de jpp abaixo.Respostas:
apply
, a função de conveniência que você nunca precisouComeçamos abordando as questões do OP, uma a uma.
DataFrame.apply
eSeries.apply
são funções de conveniência definidas no objeto DataFrame e Series, respectivamente.apply
aceita qualquer função definida pelo usuário que aplica uma transformação / agregação em um DataFrame.apply
é efetivamente uma bala de prata que faz tudo o que as funções existentes dos pandas não podem fazer.Algumas das coisas
apply
podem fazer:axis=1
) quanto por coluna (axis=0
) em um DataFrameagg
outransform
, nesses casos)result_type
argumento)....Entre outros. Para obter mais informações, consulte Aplicação de função por linha ou coluna na documentação.
Então, com todos esses recursos, por que é
apply
ruim? É porqueapply
é lento . O Pandas não faz suposições sobre a natureza da sua função e, portanto, aplica iterativamente a sua função a cada linha / coluna conforme necessário. Além disso, lidar com todas as situações acima significa queapply
incorre em uma grande sobrecarga em cada iteração. Além disso,apply
consome muito mais memória, o que é um desafio para aplicativos limitados por memória.Existem muito poucas situações em
apply
que o uso é apropriado (mais sobre isso abaixo). Se você não tem certeza se deve usarapply
, provavelmente não deveria.Vamos abordar a próxima questão.
Para reformular, aqui estão algumas situações comuns em que você desejará se livrar de todas as chamadas para
apply
.Dados Numéricos
Se você estiver trabalhando com dados numéricos, provavelmente já existe uma função de cíton vetorizada que faz exatamente o que você está tentando fazer (se não, faça uma pergunta no Stack Overflow ou abra uma solicitação de recurso no GitHub).
Compare o desempenho de
apply
para uma operação de adição simples.Em termos de desempenho, não há comparação, o equivalente citonizado é muito mais rápido. Não há necessidade de gráfico, porque a diferença é óbvia até mesmo para dados de brinquedos.
Mesmo se você habilitar a passagem de matrizes brutas com o
raw
argumento, ainda será duas vezes mais lento.Outro exemplo:
Em geral, procure alternativas vetorizadas, se possível.
String / Regex
O Pandas fornece funções de string "vetorizadas" na maioria das situações, mas há casos raros em que essas funções não ... "se aplicam", por assim dizer.
Um problema comum é verificar se um valor em uma coluna está presente em outra coluna da mesma linha.
Isso deve retornar a segunda e a terceira linha, uma vez que "donald" e "minnie" estão presentes em suas respectivas colunas de "Título".
Usando aplicar, isso seria feito usando
No entanto, existe uma solução melhor usando as compreensões de lista.
O que se deve notar aqui é que as rotinas iterativas são mais rápidas do que
apply
, devido à menor sobrecarga. Se você precisa lidar com NaNs e dtypes inválidos, você pode construir sobre isso usando uma função personalizada que você pode chamar com argumentos dentro da compreensão da lista.Para obter mais informações sobre quando as compreensões de lista devem ser consideradas uma boa opção, consulte meu artigo: Para loops com pandas - Quando devo me importar? .
Uma armadilha comum: explodir colunas de listas
As pessoas são tentadas a usar
apply(pd.Series)
. Isso é horrível em termos de desempenho.A melhor opção é listar a coluna e passá-la para pd.DataFrame.
Por último,
Aplicar é uma função de conveniência, por isso não são situações em que a sobrecarga é bastante insignificante para perdoar. Realmente depende de quantas vezes a função é chamada.
Funções que são vetorizadas para séries, mas não DataFrames
E se você quiser aplicar uma operação de string em várias colunas? E se você quiser converter várias colunas em data e hora? Essas funções são vetorizadas apenas para séries, portanto, devem ser aplicadas em cada coluna que você deseja converter / operar.
Este é um caso admissível para
apply
:Observe que também faria sentido
stack
ou apenas usar um loop explícito. Todas essas opções são um pouco mais rápidas do que usarapply
, mas a diferença é pequena o suficiente para perdoar.Você pode fazer um caso semelhante para outras operações, como operações de string ou conversão para categoria.
v / s
E assim por diante...
Convertendo séries em
str
:astype
versusapply
Isso parece uma idiossincrasia da API. Usar
apply
para converter inteiros em uma série em string é comparável (e às vezes mais rápido) do que usarastype
.O gráfico foi traçado usando a
perfplot
biblioteca.Com os flutuadores, vejo que
astype
é consistentemente tão rápido ou ligeiramente mais rápido queapply
. Portanto, isso tem a ver com o fato de que os dados no teste são do tipo inteiro.GroupBy
operações com transformações encadeadasGroupBy.apply
não foi discutido até agora, masGroupBy.apply
também é uma função de conveniência iterativa para lidar com tudo o que asGroupBy
funções existentes não fazem.Um requisito comum é realizar um GroupBy e, em seguida, duas operações principais, como um "cumsum defasado":
Você precisaria de duas chamadas em grupo sucessivas aqui:
Usando
apply
, você pode encurtar isso para uma única chamada.É muito difícil quantificar o desempenho porque depende dos dados. Mas, em geral,
apply
é uma solução aceitável se o objetivo for reduzir umagroupby
chamada (porquegroupby
também é bastante caro).Outras advertências
Além das ressalvas mencionadas acima, também vale a pena mencionar que
apply
opera na primeira linha (ou coluna) duas vezes. Isso é feito para determinar se a função tem efeitos colaterais. Caso contrário,apply
pode ser capaz de usar um caminho rápido para avaliar o resultado, caso contrário, ele retorna para uma implementação lenta.Esse comportamento também é visto nas
GroupBy.apply
versões do pandas <0,25 (foi corrigido para 0,25, consulte aqui para obter mais informações ).fonte
%timeit for c in df.columns: df[c] = pd.to_datetime(df[c], errors='coerce')
certeza, após a primeira iteração, será muito mais rápido, já que você está convertendodatetime
para ...datetime
?to_datetime
strings é tão rápido quanto em ...datetime
objetos" .. sério? Incluí a criação de dataframe (custo fixo) em tempos de loopapply
vsfor
e a diferença é muito menor.Nem todos
apply
são iguaisO gráfico abaixo sugere quando considerar
apply
1 . Verde significa possivelmente eficiente; evitar vermelho.Parte disso é intuitivo:
pd.Series.apply
é um loop em nível de linha de Python, idem empd.DataFrame.apply
linha (axis=1
). Os abusos deles são muitos e abrangentes. A outra postagem trata deles com mais profundidade. As soluções populares são o uso de métodos vetorizados, compreensões de lista (assume dados limpos) ou ferramentas eficientes como opd.DataFrame
construtor (por exemplo, para evitarapply(pd.Series)
).Se você estiver usando a
pd.DataFrame.apply
linha, especificarraw=True
(quando possível) geralmente é benéfico. Nesta fase,numba
geralmente é uma escolha melhor.GroupBy.apply
: geralmente favorecidoRepetir
groupby
operações a serem evitadasapply
prejudicará o desempenho.GroupBy.apply
normalmente está bem aqui, desde que os métodos que você usa em sua função personalizada sejam vetorizados. Às vezes, não existe um método nativo do Pandas para uma agregação groupwise que você deseja aplicar. Nesse caso, um pequeno número de gruposapply
com uma função personalizada ainda pode oferecer um desempenho razoável.pd.DataFrame.apply
coluna: um saco mistopd.DataFrame.apply
coluna-wise (axis=0
) é um caso interessante. Para um pequeno número de linhas versus um grande número de colunas, quase sempre é caro. Para um grande número de linhas em relação às colunas, o caso mais comum, às vezes você pode ver melhorias significativas de desempenho usandoapply
:1 Existem exceções, mas geralmente são marginais ou incomuns. Alguns exemplos:
df['col'].apply(str)
pode ter um desempenho ligeiramente superiordf['col'].astype(str)
.df.apply(pd.to_datetime)
trabalhar em cordas não escalona bem com linhas em comparação com umfor
loop regular .fonte
apply
é significativamente mais rápida do que minha solução comany
. Alguma opinião sobre isso?any
é cerca de 100 vezes mais rápido do queapply
. Fiz meus primeiros testes com 2.000 linhas x 1.000 colunas e aquiapply
foi duas vezes mais rápido queany
Para
axis=1
(isto é, funções de linha), então você pode simplesmente usar a seguinte função no lugar deapply
. Eu me pergunto por que esse não é opandas
comportamento. (Não testado com índices compostos, mas parece ser muito mais rápido do queapply
)fonte
zip(df, row[1:])
é suficiente aqui; realmente, neste estágio, considerenumba
se func é um cálculo numérico. Veja esta resposta para uma explicação.numba
é mais rápido,faster_df_apply
destina-se a pessoas que querem apenas algo equivalente, mas mais rápido do que oDataFrame.apply
(o que é estranhamente lento).Existem situações em que
apply
é bom? Sim as vezes.Tarefa: decodificar strings Unicode.
Atualização que
eu não estava de forma alguma defendendo o uso de
apply
, apenas pensando, uma vezNumPy
que não pode lidar com a situação acima, poderia ter sido um bom candidato parapandas apply
. Mas eu estava esquecendo a simples compreensão da lista graças ao lembrete de @jpp.fonte
[unidecode.unidecode(x) for x in s]
oulist(map(unidecode.unidecode, s))
?apply
, apenas pensei que poderia ter sido uma boa caso de uso.