Considere el siguiente marco de datos:
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
Los siguientes comandos funcionan:
> df.groupby('A').apply(lambda x: (x['C'] - x['D']))
> df.groupby('A').apply(lambda x: (x['C'] - x['D']).mean())
pero ninguno de los siguientes trabajos:
> 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é? El ejemplo en la documentación parece sugerir que llamar transform
a un grupo le permite a uno realizar el procesamiento de operaciones en filas:
# 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)
En otras palabras, pensé que transform es esencialmente un tipo específico de aplicación (el que no se agrega). Donde me equivoco
Como referencia, a continuación se muestra la construcción del marco de datos original anterior:
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
debe devolver un número, una fila o la misma forma que el argumento. si es un número, el número se establecerá en todos los elementos del grupo, si es una fila, se transmitirá a todas las filas del grupo. En su código, la función lambda devuelve una columna que no se puede transmitir al grupo.zscore
),transform
recibe una función lambda que supone que cada unox
es un elemento dentro delgroup
, y también devuelve un valor por elemento en el grupo. ¿Qué me estoy perdiendo?apply
pasa todo el df, perotransform
pasa cada columna individualmente como una Serie. 2)apply
puede devolver cualquier salida de forma (escalar / Serie / Marco de datos / matriz / lista ...), mientras quetransform
debe devolver una secuencia (Serie 1D / matriz / lista) de la misma longitud que el grupo. Es por eso que el OPapply()
no necesitatransform()
. Esta es una buena pregunta ya que el documento no explicó ambas diferencias claramente. (similar a la distinciónapply/map/applymap
u otras cosas ...)Respuestas:
Dos diferencias principales entre
apply
ytransform
Hay dos diferencias principales entre los métodos groupby
transform
yapply
.apply
implícitamente pasa todas las columnas para cada grupo como un DataFrame a la función personalizada.transform
pasa cada columna para cada grupo individualmente como una Serie a la función personalizada.apply
puede devolver un escalar, o una Serie o un Marco de datos (o una matriz numpy o incluso una lista) .transform
debe devolver una secuencia (una serie, matriz o lista unidimensional) de la misma longitud que el grupo .Por lo tanto,
transform
funciona solo en una serie a la vez yapply
funciona en todo el DataFrame a la vez.Inspeccionar la función personalizada
Puede ayudar bastante inspeccionar la entrada a su función personalizada que se pasa
apply
otransform
.Ejemplos
Creemos algunos datos de muestra e inspeccionemos los grupos para que puedan ver de qué estoy hablando:
Creemos una función personalizada simple que imprima el tipo del objeto pasado implícitamente y luego genere un error para que se pueda detener la ejecución.
Ahora pasemos esta función tanto al groupby
apply
como a lostransform
métodos para ver qué objeto se le pasa:Como puede ver, un DataFrame se pasa a la
inspect
función. Quizás se pregunte por qué el tipo, DataFrame, se imprimió dos veces. Pandas dirige el primer grupo dos veces. Hace esto para determinar si hay una manera rápida de completar el cálculo o no. Este es un detalle menor del que no debe preocuparse.Ahora, hagamos lo mismo con
transform
Se pasa una serie, un objeto Pandas totalmente diferente.
Por lo tanto,
transform
solo se permite trabajar con una sola serie a la vez. Es no imposible para que actúe en dos columnas al mismo tiempo. Entonces, si intentamos restar la columnaa
desde elb
interior de nuestra función personalizada, obtendríamos un errortransform
. Vea abajo:Obtenemos un KeyError cuando los pandas intentan encontrar el índice de la Serie
a
que no existe. Puede completar esta operaciónapply
ya que tiene todo el DataFrame:El resultado es una serie y un poco confuso ya que se mantiene el índice original, pero tenemos acceso a todas las columnas.
Mostrar el objeto pandas pasado
Puede ayudar aún más mostrar todo el objeto pandas dentro de la función personalizada, para que pueda ver exactamente con qué está operando. Puede usar
print
declaraciones de Me gusta usar ladisplay
función delIPython.display
módulo para que los marcos de datos se generen correctamente en HTML en un cuaderno jupyter:Captura de pantalla:
La transformación debe devolver una secuencia dimensional única del mismo tamaño que el grupo
La otra diferencia es que
transform
debe devolver una secuencia dimensional única del mismo tamaño que el grupo. En este caso particular, cada grupo tiene dos filas, por lo quetransform
debe devolver una secuencia de dos filas. Si no es así, se genera un error:El mensaje de error no es realmente descriptivo del problema. Debe devolver una secuencia de la misma longitud que el grupo. Entonces, una función como esta funcionaría:
Devolver un solo objeto escalar también funciona para
transform
Si devuelve un solo escalar de su función personalizada,
transform
lo usará para cada una de las filas del grupo:fuente
np
no está definido. Supongo que los principiantes agradecerían si incluyeraimport numpy as np
en su respuesta.Como me sentí igualmente confundido con la
.transform
operación vs..apply
encontré algunas respuestas que arrojan algo de luz sobre el tema. Esta respuesta, por ejemplo, fue muy útil.Mi conclusión hasta ahora es que
.transform
funcionará (o tratará) conSeries
(columnas) aisladas unas de otras . Lo que esto significa es que en tus dos últimas llamadas:Solicitó
.transform
tomar valores de dos columnas y 'it' en realidad no los 've' a ambos al mismo tiempo (por así decirlo).transform
mirará las columnas del marco de datos una por una y devolverá una serie (o grupo de series) 'hecha' de escalares que se repiten variaslen(input_column)
veces.Entonces, este escalar, que debería usarse
.transform
para hacer elSeries
resultado de alguna función de reducción aplicada en una entradaSeries
(y solo en UNA serie / columna a la vez).Considere este ejemplo (en su marco de datos):
rendirá:
Que es exactamente lo mismo que si lo usaras solo en una columna a la vez:
flexible:
Tenga
.apply
en cuenta que en el último ejemplo (df.groupby('A')['C'].apply(zscore)
) funcionaría exactamente de la misma manera, pero fallaría si intentara usarlo en un marco de datos:da error:
Entonces, ¿dónde más es
.transform
útil? El caso más simple es tratar de asignar los resultados de la función de reducción al marco de datos original.flexible:
Tratando el mismo con
.apply
daríaNaNs
ensum_C
. Porque.apply
devolvería un reducidoSeries
, que no sabe cómo transmitir de nuevo:dando:
También hay casos en los que
.transform
se usa para filtrar los datos:Espero que esto agregue un poco más de claridad.
fuente
.transform()
también podría usarse para completar valores perdidos. Especialmente si desea transmitir la media grupal o la estadística grupal a losNaN
valores de ese grupo. Desafortunadamente, la documentación de los pandas tampoco me fue útil..groupby().filter()
hace lo mismo. Gracias por tu explicación,.apply()
y también.transform()
me confunde mucho.df.groupby().transform()
no puede funcionar para un subgrupo df, siempre obtengo el errorValueError: transform must return a scalar value for each group
porquetransform
ve las columnas una por unaVoy a usar un fragmento muy simple para ilustrar la diferencia:
El DataFrame se ve así:
Hay 3 ID de clientes en esta tabla, cada cliente realizó tres transacciones y pagó 1,2,3 dólares cada vez.
Ahora, quiero encontrar el pago mínimo realizado por cada cliente. Hay dos formas de hacerlo:
Utilizando
apply
:grouping.min ()
El regreso se ve así:
Utilizando
transform
:grouping.transform (min)
El regreso se ve así:
Ambos métodos devuelven un
Series
objeto, pero ellength
del primero es 3 y ellength
del segundo es 9.Si desea responder
What is the minimum price paid by each customer
, entonces elapply
método es el más adecuado para elegir.Si quieres responder
What is the difference between the amount paid for each transaction vs the minimum payment
, entonces quieres usartransform
, porque:Apply
no funciona aquí simplemente porque devuelve una Serie de tamaño 3, pero la longitud del df original es 9. No puede integrarlo fácilmente al df original.fuente
es como
o
fuente