Considere o seguinte quadro de dados:
A B C D
0 foo one 0.162003 0.087469
1 bar one -1.156319 -1.526272
2 foo two 0.833892 -1.666304
3 bar three -2.026673 -0.322057
4 foo two 0.411452 -0.954371
5 bar two 0.765878 -0.095968
6 foo one -0.654890 0.678091
7 foo three -1.789842 -1.130922
Os seguintes comandos funcionam:
> df.groupby('A').apply(lambda x: (x['C'] - x['D']))
> df.groupby('A').apply(lambda x: (x['C'] - x['D']).mean())
mas nenhum dos seguintes trabalhos:
> df.groupby('A').transform(lambda x: (x['C'] - x['D']))
ValueError: could not broadcast input array from shape (5) into shape (5,3)
> df.groupby('A').transform(lambda x: (x['C'] - x['D']).mean())
TypeError: cannot concatenate a non-NDFrame object
Por quê? O exemplo na documentação parece sugerir que a chamada transform
a um grupo permite executar o processamento de operações em linhas:
# Note that the following suggests row-wise operation (x.mean is the column mean)
zscore = lambda x: (x - x.mean()) / x.std()
transformed = ts.groupby(key).transform(zscore)
Em outras palavras, eu pensei que transformar é essencialmente um tipo específico de aplicação (aquele que não agrega). Onde eu estou errado?
Para referência, abaixo está a construção do quadro de dados original acima:
df = pd.DataFrame({'A' : ['foo', 'bar', 'foo', 'bar',
'foo', 'bar', 'foo', 'foo'],
'B' : ['one', 'one', 'two', 'three',
'two', 'two', 'one', 'three'],
'C' : randn(8), 'D' : randn(8)})
transform
deve retornar um número, uma linha ou a mesma forma que o argumento. se for um número, o número será definido para todos os elementos do grupo; se for uma linha, será transmitido para todas as linhas do grupo. No seu código, a função lambda retorna uma coluna que não pode ser transmitida para o grupo.zscore
),transform
recebe uma função lambda que assume que cadax
um é um item dentro dogroup
e também retorna um valor por item no grupo. o que estou perdendo?apply
passa em todo o df, mastransform
passa cada coluna individualmente como uma série. 2)apply
pode retornar qualquer saída de forma (escalar / Série / DataFrame / matriz / lista ...), enquantotransform
deve retornar uma sequência (1D Série / matriz / lista) do mesmo comprimento que o grupo. É por isso que o OPapply()
não precisatransform()
. Essa é uma boa pergunta, pois o documento não explicou claramente as duas diferenças. (semelhante à distinção entreapply/map/applymap
, ou outras coisas ...)Respostas:
Duas grandes diferenças entre
apply
etransform
Existem duas grandes diferenças entre os métodos groupby
transform
eapply
.apply
passa implicitamente todas as colunas para cada grupo como um DataFrame para a função personalizada.transform
passa cada coluna para cada grupo individualmente como uma série para a função personalizada.apply
pode retornar um escalar ou um Series ou DataFrame (ou matriz numpy ou mesmo lista) .transform
deve retornar uma sequência (uma série, matriz ou lista unidimensional) do mesmo comprimento que o grupo .Portanto,
transform
funciona apenas em uma série por vez eapply
funciona em todo o DataFrame de uma só vez.Inspecionando a Função Personalizada
Pode ajudar bastante inspecionar a entrada da sua função personalizada passada para
apply
outransform
.Exemplos
Vamos criar alguns dados de amostra e inspecionar os grupos para que você possa ver do que estou falando:
Vamos criar uma função personalizada simples que imprima o tipo do objeto transmitido implicitamente e, em seguida, gere um erro para que a execução possa ser interrompida.
Agora vamos passar essa função para o groupby
apply
etransform
métodos para ver qual objeto é passado para ele:Como você pode ver, um DataFrame é passado para a
inspect
função. Você pode estar se perguntando por que o tipo DataFrame foi impresso duas vezes. Pandas dirige o primeiro grupo duas vezes. Isso é feito para determinar se existe uma maneira rápida de concluir o cálculo ou não. Este é um detalhe menor com o qual você não deve se preocupar.Agora, vamos fazer a mesma coisa com
transform
É passada uma série - um objeto Pandas totalmente diferente.
Portanto,
transform
só é permitido trabalhar com uma única série de cada vez. É não impossível para ele para agir em duas colunas ao mesmo tempo. Portanto, se tentarmos subtrair a colunaa
deb
dentro da nossa função personalizada, obteremos um errotransform
. Ver abaixo:Nós obtemos um KeyError porque os pandas estão tentando encontrar o índice da série
a
que não existe. Você pode concluir esta operação comapply
ela, pois possui todo o DataFrame:A saída é uma série e um pouco confusa, pois o índice original é mantido, mas temos acesso a todas as colunas.
Exibindo o objeto pandas passado
Pode ajudar ainda mais a exibir todo o objeto pandas na função personalizada, para que você possa ver exatamente com o que está operando. Você pode usar as
print
instruções de que eu gosto de usar adisplay
função doIPython.display
módulo para que os DataFrames sejam gerados em HTML em um notebook jupyter:Captura de tela:
A transformação deve retornar uma sequência dimensional única do mesmo tamanho que o grupo
A outra diferença é que
transform
deve retornar uma sequência dimensional única do mesmo tamanho que o grupo. Nesse caso específico, cada grupo tem duas linhas, portanto,transform
deve retornar uma sequência de duas linhas. Caso contrário, será gerado um erro:A mensagem de erro não é realmente descritiva do problema. Você deve retornar uma sequência do mesmo tamanho que o grupo. Portanto, uma função como esta funcionaria:
Retornar um único objeto escalar também funciona para
transform
Se você retornar apenas um único escalar de sua função personalizada,
transform
use-o para cada uma das linhas do grupo:fonte
np
não está definido. Presumo que os iniciantes apreciariam se você incluísseimport numpy as np
em sua resposta.Como me senti igualmente confuso com a
.transform
operação vs..apply
, encontrei algumas respostas que esclareciam o assunto. Esta resposta, por exemplo, foi muito útil.Minha opinião até agora é que
.transform
funcionará (ou lidará) comSeries
(colunas) isoladamente um do outro . O que isso significa é que nas duas últimas chamadas:Você pediu
.transform
para pegar valores de duas colunas e 'it' na verdade não 'vê' as duas ao mesmo tempo (por assim dizer).transform
examinará as colunas do quadro de dados uma a uma e retornará uma série (ou grupo de séries) 'feita' de escalares que são repetidaslen(input_column)
vezes.Portanto, este escalar, que deve ser usado
.transform
para fazer isso,Series
é resultado de alguma função de redução aplicada em uma entradaSeries
(e somente em UMA série / coluna de cada vez).Considere este exemplo (no seu quadro de dados):
produzirá:
O que é exatamente o mesmo que se você o usasse apenas em uma coluna por vez:
produzindo:
Observe que
.apply
no último exemplo (df.groupby('A')['C'].apply(zscore)
) funcionaria exatamente da mesma maneira, mas falharia se você tentasse usá-lo em um dataframe:dá erro:
Então, onde mais é
.transform
útil? O caso mais simples é tentar atribuir resultados da função de redução ao quadro de dados original.produzindo:
Tentando o mesmo com
.apply
dariaNaNs
nosum_C
. Porque.apply
retornaria um reduzidoSeries
, que não sabe como transmitir de volta:dando:
Também há casos em que
.transform
é usado para filtrar os dados:Espero que isso adicione um pouco mais de clareza.
fonte
.transform()
também pode ser usado para preencher valores ausentes. Especialmente se você deseja transmitir a média do grupo ou a estatística do grupo para osNaN
valores desse grupo. Infelizmente, a documentação dos pandas também não foi útil para mim..groupby().filter()
faz a mesma coisa. Obrigado pela sua explicação,.apply()
e também.transform()
me deixa muito confuso.df.groupby().transform()
que não posso trabalhar para um subgrupo df, sempre recebo o erroValueError: transform must return a scalar value for each group
porquetransform
vê as colunas uma a umaVou usar um trecho muito simples para ilustrar a diferença:
O DataFrame fica assim:
Existem 3 IDs de clientes nesta tabela, cada cliente fez três transações e pagou 1,2,3 dólares por vez.
Agora, quero encontrar o pagamento mínimo feito por cada cliente. Existem duas maneiras de fazer isso:
Usando
apply
:grouping.min ()
O retorno é assim:
Usando
transform
:grouping.transform (min)
O retorno é assim:
Ambos os métodos retornam um
Series
objeto, mas olength
primeiro é 3 elength
o segundo é 9.Se você deseja responder
What is the minimum price paid by each customer
, oapply
método é o mais adequado para sua escolha.Se você quer responder
What is the difference between the amount paid for each transaction vs the minimum payment
, então quer usartransform
, porque:Apply
não funciona aqui simplesmente porque retorna uma série de tamanho 3, mas o comprimento do df original é 9. Você não pode integrá-lo novamente ao df original com facilidade.fonte
é como
ou
fonte