Aplicar múltiples funciones a múltiples columnas groupby

14 minutos de lectura

Aplicar multiples funciones a multiples columnas groupby
barbac

los documentos muestre cómo aplicar múltiples funciones en un objeto groupby a la vez usando un dict con los nombres de las columnas de salida como claves:

In [563]: grouped['D'].agg({'result1' : np.sum,
   .....:                   'result2' : np.mean})
   .....:
Out[563]: 
      result2   result1
A                      
bar -0.579846 -1.739537
foo -0.280588 -1.402938

Sin embargo, esto solo funciona en un objeto agrupado Serie. Y cuando un dict se pasa de manera similar a un DataFrame groupby, espera que las claves sean los nombres de las columnas a las que se aplicará la función.

Lo que quiero hacer es aplicar múltiples funciones a varias columnas (pero ciertas columnas se operarán varias veces). También, algunas funciones dependerán de otras columnas en el objeto groupby (como funciones sumif). Mi solución actual es ir columna por columna y hacer algo como el código anterior, usando lambdas para funciones que dependen de otras filas. Pero esto lleva mucho tiempo (creo que lleva mucho tiempo iterar a través de un objeto groupby). Tendré que cambiarlo para iterar a través de todo el objeto groupby en una sola ejecución, pero me pregunto si hay una forma integrada en pandas para hacer esto de manera algo limpia.

Por ejemplo, he intentado algo como

grouped.agg({'C_sum' : lambda x: x['C'].sum(),
             'C_std': lambda x: x['C'].std(),
             'D_sum' : lambda x: x['D'].sum()},
             'D_sumifC3': lambda x: x['D'][x['C'] == 3].sum(), ...)

pero como era de esperar, obtengo un KeyError (ya que las claves tienen que ser una columna si agg se llama desde un DataFrame).

¿Hay alguna forma integrada de hacer lo que me gustaría hacer, o existe la posibilidad de que se agregue esta funcionalidad, o solo tendré que iterar a través del grupo manualmente?

  • Si está llegando a esta pregunta en 2017+, consulte la respuesta a continuación para ver la forma idiomática de agregar varias columnas juntas. La respuesta seleccionada actualmente tiene múltiples desaprobaciones, a saber, que ya no puede usar un diccionario de diccionarios para cambiar el nombre de las columnas en el resultado de un grupo.

    –Ted Petrou

    3 de noviembre de 2017 a las 19:46

1646745857 438 Aplicar multiples funciones a multiples columnas groupby
Ted Petrou

La segunda mitad de la respuesta actualmente aceptada está desactualizada y tiene dos desaprobaciones. Primero y más importante, ya no se puede pasar un diccionario de diccionarios al agg método groupby. En segundo lugar, nunca use .ix.

Si desea trabajar con dos columnas separadas al mismo tiempo, le sugiero que use el apply método que implícitamente pasa un DataFrame a la función aplicada. Usemos un marco de datos similar al de arriba

df = pd.DataFrame(np.random.rand(4,4), columns=list('abcd'))
df['group'] = [0, 0, 1, 1]
df

          a         b         c         d  group
0  0.418500  0.030955  0.874869  0.145641      0
1  0.446069  0.901153  0.095052  0.487040      0
2  0.843026  0.936169  0.926090  0.041722      1
3  0.635846  0.439175  0.828787  0.714123      1

Un diccionario mapeado de nombres de columna a funciones de agregación sigue siendo una forma perfectamente buena de realizar una agregación.

df.groupby('group').agg({'a':['sum', 'max'], 
                         'b':'mean', 
                         'c':'sum', 
                         'd': lambda x: x.max() - x.min()})

              a                   b         c         d
            sum       max      mean       sum  <lambda>
group                                                  
0      0.864569  0.446069  0.466054  0.969921  0.341399
1      1.478872  0.843026  0.687672  1.754877  0.672401

Si no le gusta ese feo nombre de columna lambda, puede usar una función normal y proporcionar un nombre personalizado a la especial __name__ atributo como este:

def max_min(x):
    return x.max() - x.min()

max_min.__name__ = 'Max minus Min'

df.groupby('group').agg({'a':['sum', 'max'], 
                         'b':'mean', 
                         'c':'sum', 
                         'd': max_min})

              a                   b         c             d
            sum       max      mean       sum Max minus Min
group                                                      
0      0.864569  0.446069  0.466054  0.969921      0.341399
1      1.478872  0.843026  0.687672  1.754877      0.672401

Utilizando apply y devolver una Serie

Ahora, si tenía varias columnas que necesitaban interactuar juntas, entonces no puede usar agg, que implícitamente pasa una Serie a la función de agregación. Cuando usas apply todo el grupo como DataFrame se pasa a la función.

Recomiendo hacer una sola función personalizada que devuelva una serie de todas las agregaciones. Use el índice Serie como etiquetas para las nuevas columnas:

def f(x):
    d = {}
    d['a_sum'] = x['a'].sum()
    d['a_max'] = x['a'].max()
    d['b_mean'] = x['b'].mean()
    d['c_d_prodsum'] = (x['c'] * x['d']).sum()
    return pd.Series(d, index=['a_sum', 'a_max', 'b_mean', 'c_d_prodsum'])

df.groupby('group').apply(f)

         a_sum     a_max    b_mean  c_d_prodsum
group                                           
0      0.864569  0.446069  0.466054     0.173711
1      1.478872  0.843026  0.687672     0.630494

Si está enamorado de MultiIndexes, aún puede devolver una Serie con una como esta:

    def f_mi(x):
        d = []
        d.append(x['a'].sum())
        d.append(x['a'].max())
        d.append(x['b'].mean())
        d.append((x['c'] * x['d']).sum())
        return pd.Series(d, index=[['a', 'a', 'b', 'c_d'], 
                                   ['sum', 'max', 'mean', 'prodsum']])

df.groupby('group').apply(f_mi)

              a                   b       c_d
            sum       max      mean   prodsum
group                                        
0      0.864569  0.446069  0.466054  0.173711
1      1.478872  0.843026  0.687672  0.630494

  • esta es la única forma que he encontrado para agregar un marco de datos a través de múltiples entradas de columna simultáneamente (el ejemplo c_d anterior)

    – Blake

    23 de enero de 2018 a las 23:11

  • Estoy confundido por los resultados, tomando la suma de a dentro del grupo 0 ¿no debería ser así? 0.418500 + 0.446069 = 0.864569? Lo mismo ocurre con otras celdas, los números no parecen cuadrar. ¿Podría ser que se utilizó un marco de datos subyacente ligeramente diferente en los ejemplos posteriores?

    – línea floja

    14 de junio de 2018 a las 13:26

  • Con frecuencia uso .size() con un groupby para ver la cantidad de registros. ¿Hay alguna manera de hacer esto usando el método agg: dict? Entiendo que podría contar un campo en particular, pero preferiría que el conteo fuera independiente del campo.

    – Chris Decker

    7 de febrero de 2019 a las 19:28

  • @slackline sí. Lo acabo de probar y funciona bien. Ted debe haber creado el marco varias veces y, dado que se creó a través de la generación de números aleatorios, los datos df para generar realmente los datos fueron diferentes a los que finalmente se usaron en los cálculos.

    – Lucas H.

    15 de marzo de 2019 a las 2:04

  • En marcos de datos grandes, esto es muy lento. ¿Cuáles son las ideas para soluciones más eficientes?

    – Hauke

    30 de abril de 2019 a las 9:30

1646745858 425 Aplicar multiples funciones a multiples columnas groupby
Zelazny7

Para la primera parte, puede pasar un dictado de nombres de columna para claves y una lista de funciones para los valores:

In [28]: df
Out[28]:
          A         B         C         D         E  GRP
0  0.395670  0.219560  0.600644  0.613445  0.242893    0
1  0.323911  0.464584  0.107215  0.204072  0.927325    0
2  0.321358  0.076037  0.166946  0.439661  0.914612    1
3  0.133466  0.447946  0.014815  0.130781  0.268290    1

In [26]: f = {'A':['sum','mean'], 'B':['prod']}

In [27]: df.groupby('GRP').agg(f)
Out[27]:
            A                   B
          sum      mean      prod
GRP
0    0.719580  0.359790  0.102004
1    0.454824  0.227412  0.034060

ACTUALIZACIÓN 1:

Dado que la función de agregación funciona en Series, se pierden las referencias a los otros nombres de columna. Para evitar esto, puede hacer referencia al marco de datos completo e indexarlo usando los índices de grupo dentro de la función lambda.

Aquí hay una solución alternativa:

In [67]: f = {'A':['sum','mean'], 'B':['prod'], 'D': lambda g: df.loc[g.index].E.sum()}

In [69]: df.groupby('GRP').agg(f)
Out[69]:
            A                   B         D
          sum      mean      prod  <lambda>
GRP
0    0.719580  0.359790  0.102004  1.170219
1    0.454824  0.227412  0.034060  1.182901

Aquí, la columna ‘D’ resultante se compone de los valores ‘E’ sumados.

ACTUALIZACIÓN 2:

Aquí hay un método que creo que hará todo lo que le pidas. Primero haga una función lambda personalizada. A continuación, g hace referencia al grupo. Al agregar, g será una Serie. Paso g.index para df.ix[] selecciona el grupo actual de df. Luego pruebo si la columna C es inferior a 0,5. La serie booleana devuelta se pasa a g[] que selecciona solo aquellas filas que cumplen los criterios.

In [95]: cust = lambda g: g[df.loc[g.index]['C'] < 0.5].sum()

In [96]: f = {'A':['sum','mean'], 'B':['prod'], 'D': {'my name': cust}}

In [97]: df.groupby('GRP').agg(f)
Out[97]:
            A                   B         D
          sum      mean      prod   my name
GRP
0    0.719580  0.359790  0.102004  0.204072
1    0.454824  0.227412  0.034060  0.570441

  • Interesante, también puedo pasar un dictado de {funcname: func} como valores en lugar de listas para mantener mis nombres personalizados. Pero en cualquier caso no puedo pasar un lambda que usa otras columnas (como lambda x: x['D'][x['C'] < 3].sum() arriba: “KeyError: ‘D'”). ¿Alguna idea de si eso es posible?

    – barbac

    25 de enero de 2013 a las 20:56

  • He estado tratando de hacer exactamente eso, y me sale el error KeyError: 'D'

    – Zelazny7

    25 de enero de 2013 a las 20:57

  • Genial, lo tengo para trabajar con df['A'].ix[g.index][df['C'] < 0].sum(). Sin embargo, esto está empezando a complicarse bastante. Creo que, para mejorar la legibilidad, puede ser preferible el bucle manual, además, no estoy seguro de que haya una manera de darle mi nombre preferido en el agg argumento (en lugar de <lambda>). Mantendré la esperanza de que alguien pueda conocer una forma más directa…

    – barbac

    25 de enero de 2013 a las 21:24

  • Puede pasar un dict para el valor de la columna {'D': {'my name':lambda function}} y hará que la clave de dictado interna sea el nombre de la columna.

    – Zelazny7

    25 de enero de 2013 a las 21:27

  • Creo que pandas ahora admite múltiples funciones aplicadas a un marco de datos agrupado por: pandas.pydata.org/pandas-docs/stable/…

    – IanS

    20 de mayo de 2016 a las 8:44

Pandas >= 0.25.0agregaciones nombradas

Desde la versión pandas 0.25.0 o superior, nos estamos alejando de la agregación y el cambio de nombre basados ​​en diccionarios, y avanzando hacia agregaciones nombradas que acepta un tuple. Ahora podemos agregar + renombrar simultáneamente a un nombre de columna más informativo:

Ejemplo:

df = pd.DataFrame(np.random.rand(4,4), columns=list('abcd'))
df['group'] = [0, 0, 1, 1]

          a         b         c         d  group
0  0.521279  0.914988  0.054057  0.125668      0
1  0.426058  0.828890  0.784093  0.446211      0
2  0.363136  0.843751  0.184967  0.467351      1
3  0.241012  0.470053  0.358018  0.525032      1

Aplicar GroupBy.agg con agregación nombrada:

df.groupby('group').agg(
             a_sum=('a', 'sum'),
             a_mean=('a', 'mean'),
             b_mean=('b', 'mean'),
             c_sum=('c', 'sum'),
             d_range=('d', lambda x: x.max() - x.min())
)

          a_sum    a_mean    b_mean     c_sum   d_range
group                                                  
0      0.947337  0.473668  0.871939  0.838150  0.320543
1      0.604149  0.302074  0.656902  0.542985  0.057681

  • Me gustan estas agregaciones con nombre, pero no pude ver cómo se supone que debemos usarlas con varias columnas.

    – Simón Woodhead

    23 oct 2019 a las 15:39

  • Buena pregunta, no pude resolver esto, dudo que esto sea posible (todavía). abrí un billete para esto. Mantendré mi pregunta y tú actualizados. Gracias por señalar @SimonWoodhead

    – Erfán

    29 oct 2019 a las 15:05

  • ¿algún progreso al hacer esto con varias columnas? es decir ([‘a’, ‘b’]’suma’)

    – Derek Edén

    11 de septiembre de 2020 a las 3:26

  • @DerekEden, ¿te has enterado?

    – mihagazvoda

    9 oct 2021 a las 11:55

  • @mihagazvoda no lo siento

    – Derek Edén

    18 de octubre de 2021 a las 3:41

Como alternativa (principalmente en estética) a la respuesta de Ted Petrou, descubrí que prefería una lista un poco más compacta. No considere aceptarlo, es solo un comentario mucho más detallado sobre la respuesta de Ted, más código/datos. Python/pandas no es mi primero/mejor, pero encontré que esto se lee bien:

df.groupby('group') \
  .apply(lambda x: pd.Series({
      'a_sum'       : x['a'].sum(),
      'a_max'       : x['a'].max(),
      'b_mean'      : x['b'].mean(),
      'c_d_prodsum' : (x['c'] * x['d']).sum()
  })
)

          a_sum     a_max    b_mean  c_d_prodsum
group                                           
0      0.530559  0.374540  0.553354     0.488525
1      1.433558  0.832443  0.460206     0.053313

me parece que recuerda mas a dplyr tuberías y data.table comandos encadenados. No quiero decir que sean mejores, simplemente más familiares para mí. (Ciertamente reconozco el poder y, para muchos, la preferencia de usar más formalizado def funciones para este tipo de operaciones. Esta es solo una alternativa, no necesariamente mejor).


Generé datos de la misma manera que Ted, agregaré una semilla para la reproducibilidad.

import numpy as np
np.random.seed(42)
df = pd.DataFrame(np.random.rand(4,4), columns=list('abcd'))
df['group'] = [0, 0, 1, 1]
df

          a         b         c         d  group
0  0.374540  0.950714  0.731994  0.598658      0
1  0.156019  0.155995  0.058084  0.866176      0
2  0.601115  0.708073  0.020584  0.969910      1
3  0.832443  0.212339  0.181825  0.183405      1

1646745859 773 Aplicar multiples funciones a multiples columnas groupby
Exan

Nuevo en la versión 0.25.0.

Para admitir la agregación específica de columnas con control sobre los nombres de las columnas de salida, pandas acepta la sintaxis especial en Agrupar Por.agg()conocido como “agregación nombrada”donde

  • Las palabras clave son los nombres de las columnas de salida
  • Los valores son tuplas cuyo primer elemento es la columna a seleccionar y el segundo elemento es la agregación a aplicar a esa columna. Pandas proporciona pandas.NamedAgg namedtuple con los campos [‘column’, ‘aggfunc’] para que quede más claro cuáles son los argumentos. Como de costumbre, la agregación puede ser un alias invocable o de cadena.
>>> animals = pd.DataFrame({
...     'kind': ['cat', 'dog', 'cat', 'dog'],
...     'height': [9.1, 6.0, 9.5, 34.0],
...     'weight': [7.9, 7.5, 9.9, 198.0]
... })

>>> print(animals)
  kind  height  weight
0  cat     9.1     7.9
1  dog     6.0     7.5
2  cat     9.5     9.9
3  dog    34.0   198.0

>>> print(
...     animals
...     .groupby('kind')
...     .agg(
...         min_height=pd.NamedAgg(column='height', aggfunc="min"),
...         max_height=pd.NamedAgg(column='height', aggfunc="max"),
...         average_weight=pd.NamedAgg(column='weight', aggfunc=np.mean),
...     )
... )
      min_height  max_height  average_weight
kind                                        
cat          9.1         9.5            8.90
dog          6.0        34.0          102.75

pandas.NamedAgg es solo una tupla con nombre. También se permiten tuplas simples.

>>> print(
...     animals
...     .groupby('kind')
...     .agg(
...         min_height=('height', 'min'),
...         max_height=('height', 'max'),
...         average_weight=('weight', np.mean),
...     )
... )
      min_height  max_height  average_weight
kind                                        
cat          9.1         9.5            8.90
dog          6.0        34.0          102.75

Los argumentos de palabras clave adicionales no se pasan a las funciones de agregación. Solo los pares de (columna, aggfunc) deben pasarse como **kwargs. Si sus funciones de agregación requieren argumentos adicionales, aplíquelos parcialmente con functools.partial().

La agregación con nombre también es válida para las agregaciones de series agrupadas. En este caso, no hay selección de columnas, por lo que los valores son solo las funciones.

>>> print(
...     animals
...     .groupby('kind')
...     .height
...     .agg(
...         min_height="min",
...         max_height="max",
...     )
... )
      min_height  max_height
kind                        
cat          9.1         9.5
dog          6.0        34.0

  • Mi próximo comentario es un consejo que muestra cómo usar un diccionario de aggs con nombre. Sin embargo, parece que no puedo formatear bien el código en el comentario, así que también he creado una respuesta a continuación.

    – Menta

    22 de julio de 2020 a las 18:09


  • agg_dict = { "min_height": pd.NamedAgg(column='height', aggfunc='min'), "max_height": pd.NamedAgg(column='height', aggfunc='max'), "average_weight": pd.NamedAgg(column='weight', aggfunc=np.mean) } animals.groupby("kind").agg(**agg_dict)

    – Menta

    22 de julio de 2020 a las 18:11

1646745859 570 Aplicar multiples funciones a multiples columnas groupby
menta

Este es un giro en la respuesta ‘exans’ que usa Agregaciones con nombre. Es lo mismo pero con el desempaquetado de argumentos que le permite pasar un diccionario a la función agg.

Los agregados con nombre son una buena característica, pero a primera vista puede parecer difícil escribir mediante programación ya que usan palabras clave, pero en realidad es simple con el desempaquetado de argumentos/palabras clave.

animals = pd.DataFrame({'kind': ['cat', 'dog', 'cat', 'dog'],
                         'height': [9.1, 6.0, 9.5, 34.0],
                         'weight': [7.9, 7.5, 9.9, 198.0]})
 
agg_dict = {
    "min_height": pd.NamedAgg(column='height', aggfunc="min"),
    "max_height": pd.NamedAgg(column='height', aggfunc="max"),
    "average_weight": pd.NamedAgg(column='weight', aggfunc=np.mean)
}

animals.groupby("kind").agg(**agg_dict)

El resultado

      min_height  max_height  average_weight
kind                                        
cat          9.1         9.5            8.90
dog          6.0        34.0          102.75

  • Mi próximo comentario es un consejo que muestra cómo usar un diccionario de aggs con nombre. Sin embargo, parece que no puedo formatear bien el código en el comentario, así que también he creado una respuesta a continuación.

    – Menta

    22 de julio de 2020 a las 18:09


  • agg_dict = { "min_height": pd.NamedAgg(column='height', aggfunc='min'), "max_height": pd.NamedAgg(column='height', aggfunc='max'), "average_weight": pd.NamedAgg(column='weight', aggfunc=np.mean) } animals.groupby("kind").agg(**agg_dict)

    – Menta

    22 de julio de 2020 a las 18:11

Aplicar multiples funciones a multiples columnas groupby
Jaroslav Bezděk

La respuesta de Ted es increíble. Terminé usando una versión más pequeña de eso en caso de que alguien esté interesado. Útil cuando busca una agregación que depende de valores de varias columnas:

crear un marco de datos

df = pd.DataFrame({
    'a': [1, 2, 3, 4, 5, 6], 
    'b': [1, 1, 0, 1, 1, 0], 
    'c': ['x', 'x', 'y', 'y', 'z', 'z']
})

print(df)
   a  b  c
0  1  1  x
1  2  1  x
2  3  0  y
3  4  1  y
4  5  1  z
5  6  0  z

agrupar y agregar con aplicar (usando múltiples columnas)

print(
    df
    .groupby('c')
    .apply(lambda x: x['a'][(x['a'] > 1) & (x['b'] == 1)]
    .mean()
)
c
x    2.0
y    4.0
z    5.0

agrupar y agregar con agregado (usando múltiples columnas)

Me gusta este enfoque ya que todavía puedo usar agregado. Tal vez la gente me diga por qué se necesita aplicar para obtener varias columnas al hacer agregaciones en grupos.

Parece obvio ahora, pero mientras no seleccione la columna de interés directamente después del groupbytendrá acceso a todas las columnas del marco de datos desde su función de agregación.

solo acceso a la columna seleccionada

df.groupby('c')['a'].aggregate(lambda x: x[x > 1].mean())

acceso a todas las columnas ya que la selección es después de toda la magia

df.groupby('c').aggregate(lambda x: x[(x['a'] > 1) & (x['b'] == 1)].mean())['a']

o similar

df.groupby('c').aggregate(lambda x: x['a'][(x['a'] > 1) & (x['b'] == 1)].mean())

Espero que esto ayude.

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad