python - Los pandas transforman un comportamiento inconsistente para la lista
pandas-groupby (3)
Creo que es un error en los pandas. ¿Puedes abrir un boleto en su página de Github por favor?
Al principio pensé que podría serlo, porque la
list
simplemente no se maneja correctamente como argumento para
.transform
, pero si lo hago:
def create_list(obj):
print(type(obj))
return obj.to_list()
df.groupby([''label''])[[''wave'']].transform(create_list)
Obtengo el mismo resultado inesperado.
Sin embargo, si se usa el método
agg
, funciona directamente:
df.groupby([''label''])[''wave''].agg(list)
Out[179]:
label
a [1]
b [2, 3]
c [4]
Name: wave, dtype: object
No puedo imaginar que este sea un comportamiento intencionado.
Por cierto.
También encuentro sospechoso el comportamiento diferente, que se muestra si aplica tupla a una serie agrupada y un marco de datos agrupado.
Por ejemplo, si la
transform
se aplica a una serie en lugar de un DataFrame, el resultado tampoco es una serie que contenga listas, sino una serie que contenga
ints
(recuerde para
[[''wave'']]
que crea una
transform(tuple)
marco de datos de una sola columna
transform(tuple)
tuplas devueltas):
df.groupby([''label''])[''wave''].transform(tuple)
Out[177]:
0 1
1 2
2 3
3 4
Name: wave, dtype: int64
Si vuelvo a hacer eso con
agg
lugar de
transform
, funciona tanto para
[''wave'']
como
[[''wave'']]
Estaba usando la versión 0.25.0 en un sistema ubuntu X86_64 para mis pruebas.
Tengo un fragmento de muestra que funciona como se esperaba:
import pandas as pd
df = pd.DataFrame(data={''label'': [''a'', ''b'', ''b'', ''c''], ''wave'': [1, 2, 3, 4], ''y'': [0,0,0,0]})
df[''new''] = df.groupby([''label''])[[''wave'']].transform(tuple)
El resultado es:
label wave y new
0 a 1 0 (1,)
1 b 2 0 (2, 3)
2 b 3 0 (2, 3)
3 c 4 0 (4,)
Funciona de manera análoga, si en lugar de
tuple
en la transformación doy
set, frozenset, dict
, pero si doy la
list
obtengo un resultado completamente inesperado:
df[''new''] = df.groupby([''label''])[[''wave'']].transform(list)
label wave y new
0 a 1 0 1
1 b 2 0 2
2 b 3 0 3
3 c 4 0 4
Hay una solución alternativa para obtener el resultado esperado:
df[''new''] = df.groupby([''label''])[[''wave'']].transform(tuple)[''wave''].apply(list)
label wave y new
0 a 1 0 [1]
1 b 2 0 [2, 3]
2 b 3 0 [2, 3]
3 c 4 0 [4]
Pensé en la mutabilidad / inmutabilidad (lista / tupla) pero para set / frozenset es consistente.
La pregunta es ¿por qué funciona de esta manera?
Dado que los
DataFrames
están diseñados principalmente para manejar datos 2D, incluir matrices en lugar de valores escalares podría tropezar con una advertencia como esta.
pd.DataFrame.trasnform
se implementa originalmente sobre
.agg
:
# pandas/core/generic.py
@Appender(_shared_docs["transform"] % dict(axis="", **_shared_doc_kwargs))
def transform(self, func, *args, **kwargs):
result = self.agg(func, *args, **kwargs)
if is_scalar(result) or len(result) != len(self):
raise ValueError("transforms cannot produce " "aggregated results")
return result
Sin embargo,
transform
siempre devuelve un DataFrame que debe tener la misma longitud que self, que es esencialmente la entrada.
Cuando haces una función
.agg
en el
DataFrame
, funciona bien:
df.groupby(''label'')[''wave''].agg(list)
label
a [1]
b [2, 3]
c [4]
Name: wave, dtype: object
El problema se introduce cuando
transform
intenta devolver una
Series
con la misma longitud.
En el proceso para transformar un elemento
groupby
que es un segmento de
self
y luego concatenar esto nuevamente, las listas se descomprimen en la misma longitud de índice que @Allen mencionó.
Sin embargo, cuando no se alinean, no se desempaque:
df.groupby([''label''])[[''wave'']].transform(lambda x: list(x) + [1])
wave
0 [1, 1]
1 [2, 3, 1]
2 [2, 3, 1]
3 [4, 1]
Una solución alternativa a este problema podría ser evitar la
transform
:
df = pd.DataFrame(data={''label'': [''a'', ''b'', ''b'', ''c''], ''wave'': [1, 2, 3, 4], ''y'': [0,0,0,0]})
df = df.merge(df.groupby(''label'')[''wave''].agg(list).rename(''new''), on=''label'')
df
label wave y new
0 a 1 0 [1]
1 b 2 0 [2, 3]
2 b 3 0 [2, 3]
3 c 4 0 [4]
Me he encontrado con un problema similar antes. Creo que el problema subyacente es que cuando el número de elementos en la lista coincide con el número de registros en el grupo, intenta desempaquetar la lista para que cada elemento de la lista se asigne a un registro en el grupo.
Por ejemplo, esto hará que la lista se descomprima, ya que la longitud de la lista coincide con la longitud de cada grupo:
df.groupby([''label''])[[''wave'']].transform(lambda x: list(x))
wave
0 1
1 2
2 3
3 4
Sin embargo, si la longitud de la lista no es la misma que la de cada grupo, obtendrá el comportamiento deseado:
df.groupby([''label''])[[''wave'']].transform(lambda x: list(x)+[0])
wave
0 [1, 0]
1 [2, 3, 0]
2 [2, 3, 0]
3 [4, 0]
Creo que este es un efecto secundario de la funcionalidad de desempaquetado de la lista.