Aquí hay dos operaciones groupby en un pandas.DataFrame :
import pandas d = pandas.DataFrame({"a": [1, 2, 3, 4, 5, 6], "b": [1, 2, 4, 3, -1, 5]}) grp1 = pandas.Series([1, 1, 1, 1, 1, 1]) ans1 = d.groupby(grp1).apply(lambda x: xa * xbiloc[0]) grp2 = pandas.Series([1, 1, 1, 2, 2, 2]) ans2 = d.groupby(grp2).apply(lambda x: xa * xbiloc[0]) print(ans1.reset_index(drop=True)) # a 0 1 2 3 4 5 # 0 1 2 3 4 5 6 print(ans2.reset_index(drop=True)) # 0 1 # 1 2 # 2 3 # 3 12 # 4 15 # 5 18 # Name: a, dtype: int64 Quiero la salida en el formato de ans2 . Si la Serie de agrupación tiene más de un grupo (como en grp2 ), entonces no hay problema con el formato de salida. Sin embargo, cuando la agrupación Series tiene solo un grupo (como en grp1 ), la salida es un DataFrame con una sola fila. ¿Por qué es esto?
¿Cómo puedo asegurarme de que la salida siempre será como ans2 independientemente del número de grupos en la serie de agrupación? ¿Hay un enfoque más rápido/mejor que
groupby si ese es el casoCreo que lo más fácil es evitar .apply() que, de hecho, hace cosas raras cuando se recombina. Probablemente esto se deba a que la semántica de esta función es muy vaga. Puedes devolver cualquier cosa y los pandas harán todo lo posible para adivinar lo que quisiste decir.
Si desea resultados consistentes con funciones que se aplican a todo el sub-marco de datos, es mejor que ejecute la función usted mismo:
>>> pd.concat({n: (lambda x: xa * xbiloc[0])(g) for n, g in d.groupby(grp1)}) 1 0 1 1 2 2 3 3 4 4 5 5 6 Name: a, dtype: int64 >>> pd.concat({n: (lambda x: xa * xbiloc[0])(g) for n, g in d.groupby(grp2)}) 1 0 1 1 2 2 3 2 3 12 4 15 5 18 Name: a, dtype: int64 Ahora, lo que recomendaría es usar una función con una forma de retorno bien definida. Aquí .transform() podría ser útil:
>>> d.groupby(grp1)['b'].transform('first') 0 1 1 1 2 1 3 1 4 1 5 1 Name: b, dtype: int64 >>> d.groupby(grp2)['b'].transform('first') 0 1 1 1 2 1 3 3 4 3 5 3 Name: b, dtype: int64Aquí hay un ejemplo de cómo podría usar para el mismo cálculo:
>>> ans1 = d.copy() >>> ans1['a'] *= d.groupby(grp1)['b'].transform('first') >>> ans1 ab 0 1 1 1 2 2 2 3 4 3 4 3 4 5 -1 5 6 5 >>> ans2 = d.copy() >>> ans2['a'] *= d.groupby(grp2)['b'].transform('first') >>> ans2 ab 0 1 1 1 2 2 2 3 4 3 12 3 4 15 -1 5 18 5Una solución simple es devolver un DataFrame desde apply :
import pandas d = pandas.DataFrame({"a": [1, 2, 3, 4, 5, 6], "b": [1, 2, 4, 3, -1, 5]}) grp1 = pandas.Series([1, 1, 1, 1, 1, 1]) ans1 = d.groupby(grp1).apply(lambda x: x[['a']] * xbiloc[0]) grp2 = pandas.Series([1, 1, 1, 2, 2, 2]) ans2 = d.groupby(grp2).apply(lambda x: x[['a']] * xbiloc[0]) print(ans1.reset_index(drop=True)) # a # 0 1 # 1 2 # 2 3 # 3 4 # 4 5 # 5 6 print(ans2.reset_index(drop=True)) # a # 0 1 # 1 2 # 2 3 # 3 12 # 4 15 # 5 18 Para entender por qué, la documentación de la función de apply es útil. Cuando la función dada para apply devuelve una Series , se convierten en una fila y el resultado final es un DataFrame de datos con una fila por grupo. Entonces, el comportamiento de grp1 es el esperado.
Esto plantea la pregunta de por qué el segundo caso que usa grp2 devuelve una Series . Creo que se debe a que los dos grupos devuelven Series con diferentes valores de índice. Por lo tanto, los resultados de los dos grupos se agregan en una sola fila con indexación multinivel (como se ve a continuación).
d = pandas.DataFrame({"a": [1, 2, 3, 4, 5, 6], "b": [1, 2, 4, 3, -1, 5]}) grp2 = pandas.Series([1, 1, 1, 2, 2, 2]) def func(x): z= xa * xbiloc[0] print(z.index) return z ans2 = d.groupby(grp2).apply(func) # Int64Index([0, 1, 2], dtype='int64') # Int64Index([3, 4, 5], dtype='int64') print(ans2) # 1 0 1 # 1 2 # 2 3 # 2 3 12 # 4 15 # 5 18 # Name: a, dtype: int64Esto sucede porque, como solo tiene un grupo con grp1 , el intermedio se convierte primero como Serie y luego como DataFrame con el grupo como índice, en lugar de agregar un nivel de MultiIndex como con grp2 .
Para evitar esto, puede dividir sus datos como un marco de datos en su lambda y subestablecer la columna en Serie solo después de la agregación. Esto asegura que el resultado intermedio permanecerá en la dimensión correcta:
>>> ans1 = d.groupby(grp1).apply(lambda x: x[['a']] * xbiloc[0])['a'] >>> ans1 0 1 1 2 2 3 3 4 4 5 5 6 Name: a, dtype: int64 >>> ans2 = d.groupby(grp2).apply(lambda x: x[['a']] * xbiloc[0])['a'] >>> ans2 0 1 1 2 2 3 3 12 4 15 5 18 Name: a, dtype: int64 Dicho esto, lo más fácil es usar transform :
>>> d.groupby(grp1)['a'].transform(lambda x: x*d.loc[x.index[0], 'b']) 0 1 1 2 2 3 3 4 4 5 5 6 Name: a, dtype: int64 >>> d.groupby(grp2)['a'].transform(lambda x: x*d.loc[x.index[0], 'b']) 0 1 1 2 2 3 3 12 4 15 5 18 Name: a, dtype: int64Otra forma, además de usar transform , para asegurarse de tener el mismo formato de salida es usar squeeze :
>>> ans1.squeeze().reset_index(drop=True) 0 1 1 2 2 3 3 4 4 5 5 6 Name: 1, dtype: int64 >>> ans2.squeeze().reset_index(drop=True) 0 1 1 2 2 3 3 12 4 15 5 18 Name: a, dtype: int64 Puede usar squeeze=True en groupby pero generará un FutureWarning :
FutureWarning: el parámetro de
squeezeestá en desuso y se eliminará en una versión futura.