Pandas: La obsolescencia de los dictados de reetiquetado en groupby.agg trae muchos problemas

Creado en 19 nov. 2017  ·  37Comentarios  ·  Fuente: pandas-dev/pandas

Este problema se crea en base a la discusión de # 15931 luego de la desaprobación de los dictados de reetiquetado en groupby.agg . Mucho de lo que se resume a continuación ya se discutió en la discusión anterior. Recomendaría en particular https://github.com/pandas-dev/pandas/pull/15931#issuecomment -336139085 donde los problemas también se indican claramente.

La motivación detrás de la desaprobación de # 15931 estuvo relacionada principalmente con traer una interfaz consistente por agg() entre Series y Dataframe (ver también # 14668 para el contexto).

Algunos han descrito la funcionalidad de reetiquetado con un diccionario anidado como demasiado compleja y / o inconsistente y, por lo tanto, en desuso.

Sin embargo, esto tiene un precio: la imposibilidad de agregar y cambiar el nombre al mismo tiempo conduce a problemas muy molestos y cierta incompatibilidad hacia atrás donde no hay una solución sensata disponible:

  • _ [molesto] _ no más control sobre los nombres de las columnas resultantes
  • _ [molesto] _ necesita encontrar una manera de cambiar el nombre del MultiIndex _después_ de realizar la agregación, lo que requiere realizar un seguimiento del orden de las columnas en dos lugares del código ... no es práctico en absoluto y, a veces, francamente imposible (casos a continuación ).
  • ⚠️ _ [rompiendo] _ no puede aplicar más de un invocable con el mismo nombre interno en la misma columna de entrada. Esto da como resultado dos sub-casos:

    • _ [rompiendo] _ ya no se pueden aplicar dos o más agregadores lambda en la misma columna

    • _ [rompiendo] _ ya no puede aplicar dos o más agregadores de funciones parciales a menos que modifique su atributo __name__ oculto

Ejemplo

_ (tenga en cuenta que este es un ejemplo elaborado con el propósito de demostrar el problema en un código lo más corto posible, pero todos los problemas demostrados aquí me afectaron en la vida real desde el cambio, y en situaciones no tan simples como aquí ) _

Marco de datos de entrada

mydf = pd.DataFrame(
    {
        'cat': ['A', 'A', 'A', 'B', 'B', 'C'],
        'energy': [1.8, 1.95, 2.04, 1.25, 1.6, 1.01],
        'distance': [1.2, 1.5, 1.74, 0.82, 1.01, 0.6]
    },
    index=range(6)
)
  cat  distance  energy
0   A      1.20    1.80
1   A      1.50    1.95
2   A      1.74    2.04
3   B      0.82    1.25
4   B      1.01    1.60
5   C      0.60    1.01

Antes:

fácil de escribir y leer, y funciona como se esperaba

import numpy as np
import statsmodels.robust as smrb
from functools import partial

# median absolute deviation as a partial function
# in order to demonstrate the issue with partial functions as aggregators
mad_c1 = partial(smrb.mad, c=1)

# renaming and specifying the aggregators at the same time
# note that I want to choose the resulting column names myself
# for example "total_xxxx" instead of just "sum"
mydf_agg = mydf.groupby('cat').agg({
    'energy': {
        'total_energy': 'sum',
        'energy_p98': lambda x: np.percentile(x, 98),  # lambda
        'energy_p17': lambda x: np.percentile(x, 17),  # lambda
    },
    'distance': {
        'total_distance': 'sum',
        'average_distance': 'mean',
        'distance_mad': smrb.mad,   # original function
        'distance_mad_c1': mad_c1,  # partial function wrapping the original function
    },
})

resultados en

          energy                             distance
    total_energy energy_p98 energy_p17 total_distance average_distance distance_mad distance_mad_c1
cat
A           5.79     2.0364     1.8510           4.44            1.480     0.355825           0.240
B           2.85     1.5930     1.3095           1.83            0.915     0.140847           0.095
C           1.01     1.0100     1.0100           0.60            0.600     0.000000           0.000

y todo lo que queda es:

# get rid of the first MultiIndex level in a pretty straightforward way
mydf_agg.columns = mydf_agg.columns.droplevel(level=0)

¡Feliz baile alabando a los pandas 💃 🕺!

Después

import numpy as np
import statsmodels.robust as smrb
from functools import partial

# median absolute deviation as a partial function
# in order to demonstrate the issue with partial functions as aggregators
mad_c1 = partial(smrb.mad, c=1)

# no way of choosing the destination's column names...
mydf_agg = mydf.groupby('cat').agg({
    'energy': [
        'sum',
        lambda x: np.percentile(x, 98), # lambda
        lambda x: np.percentile(x, 17), # lambda
    ],
    'distance': [
        'sum',
        'mean',
        smrb.mad, # original function
        mad_c1,   # partial function wrapping the original function
    ],
})

Lo anterior se rompe porque todas las funciones lambda darán como resultado columnas denominadas <lambda> que dan como resultado

SpecificationError: Function names must be unique, found multiple named <lambda>

Regresión incompatible hacia atrás: ya no se pueden aplicar dos lambdas diferentes a la misma columna original.

Si uno elimina el lambda x: np.percentile(x, 98) de arriba, obtenemos el mismo problema con la función parcial que hereda el nombre de la función de la función original:

SpecificationError: Function names must be unique, found multiple named mad

Finalmente, después de sobrescribir el atributo __name__ del parcial (por ejemplo, con mad_c1.__name__ = 'mad_c1' ) obtenemos:

    energy          distance
       sum <lambda>      sum   mean       mad mad_c1
cat
A     5.79   1.8510     4.44  1.480  0.355825  0.240
B     2.85   1.3095     1.83  0.915  0.140847  0.095
C     1.01   1.0100     0.60  0.600  0.000000  0.000

con todavía

  • falta una columna (percentil 98)
  • el manejo de las columnas MultiIndex
  • y el cambio de nombre de las columnas

tratar en un paso separado.

No hay control posible para los nombres de las columnas después de la agregación, lo mejor que podemos obtener de forma automatizada es una combinación del nombre de la columna original y el nombre de la función _aggregate_ como este:

mydf_agg.columns = ['_'.join(col) for col in mydf_agg.columns]

lo que resulta en:

     energy_sum  energy_<lambda>  distance_sum  distance_mean  distance_mad distance_mad_c1
cat
A          5.79           1.8510          4.44          1.480      0.355825           0.240
B          2.85           1.3095          1.83          0.915      0.140847           0.095
C          1.01           1.0100          0.60          0.600      0.000000           0.000

y si realmente necesita tener nombres diferentes, puede hacerlo así:

mydf_agg.rename({
    "energy_sum": "total_energy",
    "energy_<lambda>": "energy_p17",
    "distance_sum": "total_distance",
    "distance_mean": "average_distance"
    }, inplace=True)

pero eso significa que debe tener cuidado de mantener el código de cambio de nombre (que ahora debe estar ubicado en otro lugar del código) sincronizado con el código donde se define la agregación ...

Usuario de pandas triste 😢 (que todavía ama a los pandas, por supuesto)


Estoy a favor de la coherencia y, al mismo tiempo, lamento profundamente la desaprobación de la funcionalidad _agregar y cambiar el nombre_. Espero que los ejemplos anteriores aclaren los puntos débiles.


Soluciones posibles

  • Elimine la desaprobación de la funcionalidad de reetiquetado dict-of-dict
  • Proporcione otra API para poder hacerlo (pero ¿por qué debería haber dos métodos para el mismo propósito principal, a saber, la agregación?)
  • ??? (abierto a sugerencias)

_Lectura opcional: _

Con respecto a la discusión antes mencionada en la solicitud de extracción, que ha estado sucediendo durante algunos meses, solo recientemente me di cuenta de una de las razones por las que estoy tan molesto por esta desaprobación: "agregar y cambiar el nombre" es algo natural que tiene que ver con GROUP BY agregaciones en SQL, ya que en SQL generalmente proporciona el nombre de la columna de destino directamente al lado de la expresión de agregación, por ejemplo, SELECT col1, avg(col2) AS col2_mean, stddev(col2) AS col2_var FROM mytable GROUP BY col1 .

Estoy diciendo que _No_ pandas necesariamente debe ofrecer las mismas funcionalidades como SQL, por supuesto. Pero los ejemplos proporcionados anteriormente demuestran por qué la API dict-of-dict fue, en mi opinión, una solución limpia y simple para muchos casos de uso.

(* Personalmente no estoy de acuerdo con que el enfoque de dictado de dictado sea complejo).

API Design Groupby

Comentario más útil

Por lo que vale, también estoy firmemente a favor de no depreciar la funcionalidad.

Una gran razón para mí es que hay algo profundamente extraño en mezclar el espacio de nombres de la función de Python (algo que ver con la implementación particular) con los datos de los nombres de las columnas (algo que seguramente no debería saber sobre la implementación). El hecho de que estemos viendo columnas (potencialmente columnas múltiples) llamadas '<lambda>' me causa una disonancia cognitiva severa.

El enfoque de cambio de nombre se repite, porque existe este paso intermedio en el que se llevan los nombres de columna innecesarios (y expuestos). Además, es difícil cambiarles el nombre de manera confiable y sistemática porque hay dependencias potenciales en la implementación.

Aparte de eso, la funcionalidad de dict anidada es ciertamente compleja, pero es una operación compleja que se está realizando.

TL; DR Por favor, no deprecie. :)

Todos 37 comentarios

@zertrin : Gracias por armar esto. Vi que hubo mucha discusión en el n. ° 15931 sobre esto. Como no he podido leer esto en su totalidad, no puedo comentar en este momento. Sin embargo, déjame hacer ping:

@jreback @jorisvandenbossche @TomAugspurger @ chris-b1

Estoy de acuerdo en que cambiar el nombre con la implementación actual agg es muy torpe y está roto en este ejemplo. Los dictados anidados son algo complejos, pero escribirlos como lo hizo deja muy claro lo que está sucediendo.

Supongo que podría haber un parámetro names agregado a agg que haría que el diccionario asignara las columnas agregadas a sus nuevos nombres. Incluso podría agregar otro parámetro drop_index como booleano para determinar si se debe mantener el nivel de índice superior.

Entonces la sintaxis se convertiría en:

agg_dict = {'energy': ['sum',
                       lambda x: np.percentile(x, 98), # lambda
                       lambda x: np.percentile(x, 17), # lambda
                      ],
            'distance': ['sum',
                         'mean',
                         smrb.mad, # original function
                         mad_c1,   # partial function wrapping the original function
                        ]
           }

name_dict = {'energy':['energy_sum', 'energy_p98', 'energy_p17'],
             'distance':['distance_sum', 'distance_mean', 'distance_mad', 'distance_mad_c1']}


mydf.groupby('cat').agg(agg_dict, names=name_dict, drop_index=True)

O tal vez, se podría crear un método completamente nuevo agg_assign , que funcionaría de manera similar a DataFrame.assign :

mydf.groupby('cat').agg_assign(energy_sum=lambda x: x.energy.sum(),
                               energy_p98=lambda x: np.percentile(x.energy, 98),
                               energy_p17=lambda x: np.percentile(x.energy, 17),
                               distance_sum=lambda x: x.distance.sum(),
                               distance_mean=lambda x: x.distance.mean(),
                               distance_mad=lambda x: smrb.mad(x.distance),
                               distance_mad_c1=lambda x: mad_c1(x.distance))

De hecho, me gusta mucho más esta opción.

Por lo que vale, también estoy firmemente a favor de no depreciar la funcionalidad.

Una gran razón para mí es que hay algo profundamente extraño en mezclar el espacio de nombres de la función de Python (algo que ver con la implementación particular) con los datos de los nombres de las columnas (algo que seguramente no debería saber sobre la implementación). El hecho de que estemos viendo columnas (potencialmente columnas múltiples) llamadas '<lambda>' me causa una disonancia cognitiva severa.

El enfoque de cambio de nombre se repite, porque existe este paso intermedio en el que se llevan los nombres de columna innecesarios (y expuestos). Además, es difícil cambiarles el nombre de manera confiable y sistemática porque hay dependencias potenciales en la implementación.

Aparte de eso, la funcionalidad de dict anidada es ciertamente compleja, pero es una operación compleja que se está realizando.

TL; DR Por favor, no deprecie. :)

Mi contribución está motivada por dos cosas.

  1. Soy consciente y estoy de acuerdo con la motivación para reducir la API hinchada de Pandas. Incluso si estoy equivocado con respecto a la motivación percibida para reducir los elementos de la API "inflados", sigo siendo mi opinión que la API de Pandas podría simplificarse.
  2. Creo que es mejor tener un buen libro de cocina con buenas recetas que proporcionar API para satisfacer los deseos y necesidades de todos. No estoy afirmando que el cambio de nombre a través de diccionarios anidados califica como satisfacer caprichos, ya que ya existía y la estamos discutiendo de desaprobación. Pero se encuentra en el espectro entre API optimizada y algo ... más.

Además, los objetos Pandas Series y DataFrame han tenido métodos pipe para facilitar la canalización. En este segmento de documentos se discute que podríamos usar pipe como proxy de métodos en lugar de subclases. Con el mismo espíritu, podríamos usar el nuevo GroupBy.pipe para realizar un rol similar y permitirnos construir métodos proxy para objetos groupby.

Usaré el ejemplo de

import numpy as np
import statsmodels.robust as smrb
from functools import partial

# The DataFrame offered up above
mydf = pd.DataFrame(
    {
        'cat': ['A', 'A', 'A', 'B', 'B', 'C'],
        'energy': [1.8, 1.95, 2.04, 1.25, 1.6, 1.01],
        'distance': [1.2, 1.5, 1.74, 0.82, 1.01, 0.6]
    },
    index=range(6)
)

# Identical dictionary passed to `agg`
funcs = {
    'energy': {
        'total_energy': 'sum',
        'energy_p98': lambda x: np.percentile(x, 98),  # lambda
        'energy_p17': lambda x: np.percentile(x, 17),  # lambda
    },
    'distance': {
        'total_distance': 'sum',
        'average_distance': 'mean',
        'distance_mad': smrb.mad,   # original function
        'distance_mad_c1': mad_c1,  # partial function wrapping the original function
    },
}

# Write a proxy method to be passed to `pipe`
def agg_assign(gb, fdict):
    data = {
        (cl, nm): gb[cl].agg(fn)
        for cl, d in fdict.items()
        for nm, fn in d.items()
    }
    return pd.DataFrame(data)

# All the API we need already exists with `pipe`
mydf.groupby('cat').pipe(agg_assign, fdict=funcs)

Lo que resulta en

            distance                                                 energy                        
    average_distance distance_mad distance_mad_c1 total_distance energy_p17 energy_p98 total_energy
cat                                                                                                
A              1.480     0.355825           0.240           4.44     1.8510     2.0364         5.79
B              0.915     0.140847           0.095           1.83     1.3095     1.5930         2.85
C              0.600     0.000000           0.000           0.60     1.0100     1.0100         1.01

El método pipe hace que agregar una nueva API sea innecesario en muchos casos. También proporciona los medios para reemplazar la funcionalidad obsoleta que estamos discutiendo. Por lo tanto, me inclinaría a seguir adelante con la desaprobación.

Realmente me gusta la idea de tdpetrou: usar: names=name_dict .

Esto puede hacer felices a todos. Nos da la posibilidad de cambiar el nombre de las columnas fácilmente como lo deseemos.

Realmente no, como mencioné en mi publicación inicial, esto no resolvería el problema de desacoplar el lugar donde se define la operación agregada del nombre de la columna resultante, requiriendo un esfuerzo adicional para asegurarse de que ambos estén "sincronizados".

No digo que sea una mala solución (después de todo, resuelve los otros problemas), pero no sería tan fácil y claro como el enfoque de dict of dict. Quiero decir aquí que al momento de escribir es necesario mantener sincronizados ambos dictados de listas, y al leer la fuente, el lector debe hacer un esfuerzo para hacer coincidir los nombres en el segundo dictado de listas con la definición agregada en el primer dictado de listas. Eso es el doble del esfuerzo en cada caso.

Los dictados anidados son algo complejos, pero escribirlos como lo hizo deja muy claro lo que está sucediendo.

Todavía no entiendo por qué todo el mundo parece decir que dict of dict es complejo. Para mí, esa es la forma más clara de hacerlo.

Dicho esto, si la palabra clave names es la única solución con la que el equipo de pandas se siente cómodo, aún sería una mejora con respecto a la situación actual.

@pirsquared interesante solución con API actual. Aunque, en mi opinión, no es fácil de entender (realmente no entiendo cómo funciona: confuso :)

Comencé un hilo en el subreddit de ciencia de datos: ¿Qué odias de los pandas? . Alguien mencionó su desprecio por el MultiIndex devuelto después de un groupby y señaló el verbo dplyr do que se implementa en plydata . Da la casualidad de que funciona exactamente como agg_assign por lo que fue bastante interesante.

@zertrin agg_assign sería superior a su enfoque dict of dict y sería idéntico a las agregaciones sql, además de permitir que múltiples columnas interactúen entre sí dentro de la agregación. También funcionaría de manera idéntica a DataFrame.assign .

¿Alguna idea @jreback @TomAugspurger ?

...
mydf.groupby ('gato'). agg (agg_dict, names = name_dict, drop_index = True)

Aunque esto resuelve el problema, es necesario alinear claves y valores en dos lugares. Creo que una API (como se sugiere para .agg_assign ) que no requiere dicho código de contabilidad es menos propensa a errores.

También existe el problema del código de limpieza después de usar la API. Cuando las operaciones groupby devuelven un marco de datos MultiIndex , en la mayoría de los casos el usuario deshace el MultiIndex . La forma declarativa directa de usar .agg_assign , sugiere que no hay jerarquía, no hay salida de MultiIndex , no hay limpieza después.

Según los patrones de uso, creo que las salidas de índices múltiples deben ser estrictamente optativas y no excluidas.

Inicialmente era escéptico sobre la propuesta agg_assign , pero los dos últimos comentarios me han convencido de que esta podría ser una buena solución.

Especialmente pensando en la posibilidad de usarlo en la forma agg_assign(**relabeling_dict) y así poder definir mi relabeling_dict así:

relabeling_dict = {
    'energy_sum': lambda x: x.energy.sum(),
    'energy_p98': lambda x: np.percentile(x.energy, 98),
    'energy_p17': lambda x: np.percentile(x.energy, 17),
    'distance_sum': lambda x: x.distance.sum(),
    'distance_mean': lambda x: x.distance.mean(),
    'distance_mad': lambda x: smrb.mad(x.distance),
    'distance_mad_c1': lambda x: mad_c1(x.distance)
}

Eso sería bastante flexible y resolvería todos los problemas mencionados en mi OP.

@zertrin @ has2k1

Estaba pensando en esto un poco más y esta funcionalidad ya existe con apply . Simplemente devuelve una Serie con índice como los nuevos nombres de columna y valores como agregación. Esto permite espacios en el nombre y le da la posibilidad de ordenar las columnas exactamente como lo desea:

def my_agg(x):
    data = {'energy_sum': x.energy.sum(),
            'energy_p98': np.percentile(x.energy, 98),
            'energy_p17': np.percentile(x.energy, 17),
            'distance sum' : x.distance.sum(),
            'distance mean': x.distance.mean(),
            'distance MAD': smrb.mad(x.distance),
            'distance MAD C1': mad_c1(x.distance)}
    return pd.Series(data, index=list_of_column_order)

mydf.groupby('cat').apply(my_agg)

Por lo tanto, es posible que no sea necesario un nuevo método y, en su lugar, solo un mejor ejemplo en los documentos.

@tdpetrou , tienes razón. Había olvidado cómo funciona apply ya que uso mi propia versión debido a la doble ejecución en el proceso de selección de ruta rápido-lento.

Hum, de hecho, no hay posibilidad de que hubiera pensado en usarlo en un contexto de agregación con solo leer el documento, sin embargo ...
Además, todavía encuentro la solución con apply un poco demasiado complicada. El enfoque agg_assign parecía más sencillo y comprensible.

Dado que nunca hubo una declaración al respecto, ¿el enfoque dict-of-dict (que, aunque actualmente está en desuso, ya está implementado y también resuelve todos estos problemas) definitivamente está fuera de discusión?

Excepto por el enfoque agg_assign , dict-of-dict todavía parece el más simple, y no necesita codificación, simplemente no está en desaprobación.

El beneficio y la desventaja del enfoque agg_assign es que empuja la selección de columnas al método de agregación . En todos los ejemplos, el x pasado al lambda es algo así como self.get_group(group) para cada grupo en self , un objeto DataFrameGroupBy . Esto es bueno porque separa limpiamente el nombre , que está en **kwargs , de la selección , que está en la función.

El inconveniente es que sus funciones de agregación genéricas y agradables ahora tienen que preocuparse por la selección de columnas. ¡No hay almuerzo gratis! Eso significa que terminará con muchos ayudantes como lambda x: x[col].min . También deberá tener cuidado con cosas como np.min que se reduce en todas las dimensiones, frente a pd.DataFrame.min , que reduce más de axis=0 . Es por eso que algo como agg_assign no sería equivalente a apply . apply todavía funciona en columnas para ciertos métodos.

No estoy seguro acerca de estas compensaciones frente al método de dictado, pero tengo curiosidad por escuchar los pensamientos de otras personas. Aquí hay un bosquejo aproximado de agg_assign , que llamé y que llamé agg_table para enfatizar que las funciones se pasan a las tablas, no a las columnas:

from collections import defaultdict

import pandas as pd
import numpy as np
from pandas.core.groupby import DataFrameGroupBy

mydf = pd.DataFrame(
    {
        'cat': ['A', 'A', 'A', 'B', 'B', 'C'],
        'energy': [1.8, 1.95, 2.04, 1.25, 1.6, 1.01],
        'distance': [1.2, 1.5, 1.74, 0.82, 1.01, 0.6]
    },
    index=range(6)
)


def agg_table(self, **kwargs):
    output = defaultdict(dict)
    for group in self.groups:
        for k, v in kwargs.items():
            output[k][group] = v(self.get_group(group))

    return pd.concat([pd.Series(output[k]) for k in output],
                     keys=list(output),
                     axis=1)

DataFrameGroupBy.agg_table = agg_table

Uso

>>> gr = mydf.groupby("cat")
>>> gr.agg_table(n=len,
                 foo=lambda x: x.energy.min(),
                 bar=lambda y: y.distance.min())

   n   foo   bar
A  3  1.80  1.20
B  2  1.25  0.82
C  1  1.01  0.60

Sospecho que podríamos hacer un poco para que el rendimiento de esto sea menos terrible, pero no tanto como lo hace .agg ...

¿Podría alguien del equipo central de Pandas explicar cuál es la razón principal para desaprobar los dictados de reetiquetado en groupby.agg ?

Podría entender fácilmente si causa demasiados problemas para mantener el código, pero si se trata de complejidad para el usuario final, también optaría por traerlo de vuelta, ya que es bastante claro en comparación con las soluciones necesarias ...

¡Gracias!

¿Podría alguien del equipo central de Pandas explicar cuál es la razón principal para desaprobar los dictados de reetiquetado en groupby.agg?

¿Viste https://github.com/pandas-dev/pandas/pull/15931/files#diff -52364fb643114f3349390ad6bcf24d8fR461?

La razón principal fue que las claves de dictado estaban sobrecargadas para hacer dos cosas. Para Series / SeriesGroupBy, son para nombrar. Para DataFrame / DataFrameGroupBy, son para seleccionar una columna.

In [32]: mydf.aggregate({"distance": "min"})
Out[32]:
distance    0.6
dtype: float64

In [33]: mydf.aggregate({"distance": {"foo": "min"}})
/Users/taugspurger/Envs/pandas-dev/bin/ipython:1: FutureWarning: using a dict with renaming is deprecated and will be removed in a future version
  #!/Users/taugspurger/Envs/pandas-dev/bin/python3.6
Out[33]:
     distance
foo       0.6

In [34]: mydf.distance.agg({"foo": "min"})
Out[34]:
foo    0.6
Name: distance, dtype: float64

In [35]: mydf.groupby("cat").agg({"distance": {"foo": "min"}})
/Users/taugspurger/Envs/pandas-dev/lib/python3.6/site-packages/pandas/pandas/core/groupby.py:4201: FutureWarning: using a dict with renaming is deprecated and will be removed in a future version
  return super(DataFrameGroupBy, self).aggregate(arg, *args, **kwargs)
Out[35]:
    distance
         foo
cat
A       1.20
B       0.82
C       0.60

In [36]: mydf.groupby("cat").distance.agg({"foo": "min"})
/Users/taugspurger/Envs/pandas-dev/bin/ipython:1: FutureWarning: using a dict on a Series for aggregation
is deprecated and will be removed in a future version
  #!/Users/taugspurger/Envs/pandas-dev/bin/python3.6
Out[36]:
      foo
cat
A    1.20
B    0.82
C    0.60

Probablemente, esto no sea lo más confuso en pandas, así que quizás podríamos volver a visitarlo :) Es de suponer que me faltan algunos casos extremos. Pero incluso si eliminamos las agregaciones de dictados, todavía tenemos la inconsistencia entre la selección de nombres y columnas:

Para Series / SeriesGroupBy, las claves del diccionario son siempre para nombrar la salida.

Para DataFrame / DataFrameGroupby, las claves de dictado son siempre para la selección. Con dict-of-dicts seleccionamos una columna, y luego el dict interno es para nombrar la salida, al igual que Series / SeriesGroupBy.

Discutimos esto brevemente antes (en algún lugar de la larga discusión sobre la desaprobación), y propuse algo similar aquí: https://github.com/pandas-dev/pandas/pull/14668#issuecomment -274508089. Pero al final solo se implementó la desaprobación, y no las ideas para facilitar la otra funcionalidad de usar dictados (la función de 'cambio de nombre').

El problema era que los dictados se usaban tanto para 'selección' (en qué columna desea que se aplicara esta función) como para 'cambiar el nombre' (cuál debería ser el nombre de la columna resultante al aplicar esta función). Una sintaxis alternativa, además de los dicts, podrían ser los argumentos de palabras clave, como se analiza aquí en la propuesta agg_assign .
Todavía estoy a favor de explorar esta posibilidad, ya sea en agg o en un nuevo método como agg_assign .

Lo que propuse en ese entonces era algo similar a agg_assign pero usando un dict por palabra clave en lugar de una función lambda. Traducido al ejemplo aquí, esto sería algo como:

mydf.groupby('cat').agg(
    energy_sum={'energy': 'sum'},
    energy_p98={'energy': lambda x: np.percentile(x, 98)},
    energy_p17={'energy': lambda x: np.percentile(x, 17)},
    distance_sum={'distance': 'sum'},
    distance_mean={'distance': 'mean'},
    distance_mad={'distance': smrb.mad},
    distance_mad_c1={'distance': mad_c1})

No estoy seguro de que esto sea necesariamente más legible o más fácil de escribir como la versión con todas las lambdas, pero esta podría ser potencialmente más eficiente, ya que los pandas aún pueden usar las implementaciones optimizadas para suma, media, etc.en esas columnas donde lo hace no tiene una función lambda o especificada por el usuario.

Una gran pregunta con este enfoque sería ¿qué significaría df.groupby('cat').agg(foo='mean') ? Eso aplicaría lógicamente 'media' a todas las columnas ya que no hizo ninguna selección (similar a {'col1' : {'foo': 'mean'}, 'col2': {'foo':'mean'}, 'col3': ...} antes). Pero, eso daría como resultado columnas con múltiples índices, mientras que en el ejemplo anterior creo que sería bueno no terminar con columnas MI.

Creo que lo anterior se puede hacer compatible con versiones anteriores dentro del agg , pero la pregunta es si esto es necesario.
También creo que esto se extendería muy bien al caso series como:

mydf.groupby('cat').distance.agg(
    distance_sum='sum',
    distance_mean='mean',
    distance_mad=smrb.mad,
    distance_mad_c1=mad_c1)

(e incluso podría considerar hacer lo anterior una vez para 'distancia' y una vez para 'energía' y obtener el resultado si no le gustan todos los dicts / lambda's)

@TomAugspurger En su ejemplo de implementación simple de agg_table , ¿no sería mejor iterar sobre las diferentes funciones que se aplicarán, en lugar de iterar de los grupos, y al final concatenando las nuevas columnas por eje = 1 en lugar de concatenar las filas recién formadas por eje = 0?

Por cierto, @zertrin @tdpetrou @smcateer @pirsquared y otros, muchas gracias por plantear este problema y brindar comentarios tan detallados. ¡Estos comentarios y la participación de la comunidad son muy importantes!

De hecho, me gusta mucho el patrón sugerido por @tdpetrou (usando aplicar con una función que devuelve una Serie), probablemente incluso mejor que el dictado de dictados.

Si la función devuelve pd.Series(data, index=data.keys()) ¿tenemos la garantía de obtener los índices en el orden correcto? (Solo estoy pensando en la mejor manera de implementar el patrón en mi código, a riesgo de desviarme del tema).

Editar: lo siento, entendí mal el punto del argumento del índice (aquí es opcional, solo es necesario si desea especificar el orden de las columnas; devolver pd.Series(data) hace el trabajo por mí).

¿ Funcionaría el ejemplo de first & last ?

Tuve que recurrir a cabeza / cola así

def agg_funcs(x):
    data = {'start':x['DATE_TIME'].head(1).values[0],
           'finish':x['DATE_TIME'].tail(1).values[0],
           'events':len(x['DATE_TIME'])}
    return pd.Series(data, index = list(data.keys()))

results = df.groupby('col').apply(agg_funcs)

Todavía me gustaría abordar esto, pero no creo que se haga para 0.23.

¿Podría funcionar el enfoque de

OP aquí.

Honestamente, después de todo este tiempo y la gran cantidad de discusión en el n. ° 15931 y aquí, todavía no estoy convencido de que sea una buena idea desaprobar los dictados de reetiquetado.

Al final, ninguna de las alternativas propuestas aquí son más intuitivas para los usuarios que el enfoque de dictado de reetiquetado actual en mi humilde opinión. Cuando estaba en la documentación, solo con un ejemplo, quedó claro cómo funciona, y es muy flexible.

Por supuesto, los desarrolladores de pandas todavía pueden pensar lo contrario, simplemente respondiendo con el punto de vista de un usuario.

Incluso el enfoque del dict de reetiquetado no es muy intuitivo. En mi opinión, la sintaxis debería ser similar a SQL - func(column_name) as new_column_name . En Python podemos hacer esto con una tupla de tres elementos. (func, column_name, new_column_name) . Así es como dexplo agrupa por agregación.

dexplo

@zertrin , ¿tiene comentarios sobre mi propuesta anterior? https://github.com/pandas-dev/pandas/issues/18366/#issuecomment -349089667
Al final, invierte el orden del dict: en lugar de "{col: {name: func}}" sería una especie de "** {name: {col: func}}"

@jorisvandenbossche He considerado su enfoque. La cuestión es que realmente no veo qué ventajas adicionales aporta sobre el enfoque actual.

Para decirlo más sin rodeos, dadas las siguientes opciones:

  1. Recupere el comportamiento actual que funciona bien (algunas líneas de código de desaprobación para eliminar, vuelva a agregar la documentación que se eliminó)
  2. Implementar su propuesta (cambios significativos que se realizarán en el código, continuar con la desaprobación del enfoque actual, necesidad de que todos los usuarios adapten su código)

No veo por qué deberíamos elegir 2 a menos que brinde ventajas significativas y tangibles desde la perspectiva del desarrollador y del usuario.

Para abordar algunos de los puntos de su propuesta anterior:

El problema era que los dictados se usaban tanto para 'selección' (en qué columna desea que se aplicara esta función) como para 'cambiar el nombre' (cuál debería ser el nombre de la columna resultante al aplicar esta función).

Dado que antes estaba bien documentado, no creo que haya sido un problema para los usuarios . Personalmente, entendí el punto inmediatamente mirando los ejemplos en la documentación. (EDITAR: y pensé: _ "¡yay! Construcción muy útil, coincide exactamente con lo que estaba buscando. Bien". _)

Una sintaxis alternativa, además de los dictados, podrían ser los argumentos de palabras clave.

Uno de los aspectos atractivos de utilizar el enfoque dict-of-dict es que los usuarios pueden generarlo fácilmente de forma dinámica con algún otro código. Como señaló en el comentario justo encima de este, pasar a argumentos de palabras clave como en su propuesta aún permitiría esto a través de la construcción **{name: {col: func}} . Entonces no estoy en contra de tu propuesta. Simplemente no veo el valor agregado y la necesidad de tales cambios cuando ya logramos el mismo nivel de funcionalidad con el sistema implementado actual.

Al final, su propuesta sería _de acuerdo_ si el desarrollador del núcleo de pandas tiene un fuerte sentimiento en contra del enfoque actual. Simplemente no veo ningún beneficio como _usuario_. (de hecho, veo el inconveniente de cambiar todo el código de usuario existente para que funcione nuevamente con la nueva propuesta).

@zertrin discutimos esto ayer con algunos desarrolladores centrales, pero no pudimos escribir el resumen aquí. Así que ahora voy a hacer eso, antes de responder a su comentario, para reflejar solo nuestros pensamientos de ayer.


Entonces, en primer lugar, la noción de que una funcionalidad básica como SQL "SELECT avg (col2) as col2_avg" debería funcionar y ser fácil, es algo en lo que estamos completamente de acuerdo, y realmente queremos tener una solución para esto.

Aparte de las razones originales por las que decidimos desaprobar esto (que puede o no ser tan fuerte), los dictados actuales (desaprobados) de dictados tampoco son tan ideales, ya que esto crea un MultiIndex que en realidad nunca desea:

In [1]: df = pd.DataFrame({'A': ['a', 'b', 'a'], 'B': range(3), 'C': [.1, .2, .3]})

In [3]: gr = df.groupby('A')

In [4]: gr.agg({'B': {'b_sum': 'sum'}, 'C': {'c_mean': 'mean', 'c_count': 'count'}})
Out[4]: 
        C            B
  c_count c_mean b_sum
A                     
a       2    0.2     2
b       1    0.2     1

En lo anterior, el primer nivel del MultiIndex es superfluo, ya que ya renombró específicamente las columnas (en el ejemplo del OP, esto también se sigue directamente al eliminar el primer nivel de las columnas).
Sin embargo, es difícil cambiar esto porque también puede hacer cosas como gr.agg(['sum', 'mean']) o (mixto) gr.agg({'B': ['sum', 'mean'], 'C': {'c_mean': 'mean', 'c_count': 'count'}}) el MultiIndex es necesario y tiene sentido.

Entonces, una de las propuestas que se mencionó en la discusión anterior fue tener una forma de especificar los nombres de las columnas finales por separado (por ejemplo, https://github.com/pandas-dev/pandas/issues/18366#issuecomment-346683449).
Agregar, por ejemplo, una palabra clave adicional a aggregate para especificar los nombres de las columnas, como

gr.agg({'B': 'sum', 'C': ['mean', 'count']}, columns=['b_sum', 'c_mean', 'c_count'])

sería posible.
Sin embargo, si dividimos la especificación de la columna / función y los nuevos nombres de columna, también podemos hacer esto más genérico que una nueva palabra clave y hacer algo como:

gr.agg({'B': 'sum', 'C': ['mean', 'count']}).rename(columns=['b_sum', 'c_mean', 'c_count'])

Esto necesita https://github.com/pandas-dev/pandas/issues/14829 para ser resuelto (algo que queremos hacer para 0.24.0).
(Nota importante: para esto que necesitamos para solucionar el problema de nombres de duplicado de las funciones lambda, por lo que deberíamos hacer algún tipo de deduplicación automática de los nombres si queremos apoyar esta solución.)


Entonces, todavía nos gusta la forma de los argumentos de palabras clave para cambiar el nombre. Las razones de esto son:

  • es similar a cómo funciona assign en pandas, y también coherente con cómo funciona groupby().aggregate() en ibis (y también similar a cómo se ve en, por ejemplo, dplyr en R)
  • le da directamente los nombres de columna no jerárquicos que le gustaría (sin MultiIndex)
  • para los casos simples (también, por ejemplo, para el caso de la serie), creo que es más simple como el dict de dict

Todavía tuvimos un poco de discusión sobre cómo podría verse. Lo que propuse anteriormente fue (para usar la selección de columna / función equivalente como en mis primeros ejemplos):

gr.agg(b_sum={'B': 'sum'}, c_mean={'C': 'mean'}, c_count={'C': 'count'})

Aún puede construir esta especificación como un dictado de dictados, pero con el nivel interno y externo intercambiados en comparación con la versión actual (obsoleta):

gr.agg(**{'b_sum': {'B': 'sum'}, 'c_mean': {'C': 'mean'}, 'c_count': {'C': 'count'})

(podríamos tener una función auxiliar de ejemplo que convierta los dictados de dictados existentes a esta versión)

Sin embargo, el dictado es siempre solo un {col: func} , y esos dictados múltiples de un solo elemento se ven un poco extraños. Entonces, una alternativa que pensamos es usar tuplas:

gr.agg(b_sum=('B', 'sum'), c_mean=('C', 'mean'), c_count=('C', 'count'))

Esto se ve un poco mejor, pero por otro lado el {'B': 'sum'} dict es consistente con las otras API para especificar la columna en la que aplicar la función.


Ambas sugerencias anteriores (el cambio de nombre más fácil después y el nombre basado en palabras clave) son en principio ortogonales, pero podría ser bueno tener ambos (o algo más basado en una discusión adicional)

Gracias por reenviar aquí los pensamientos actuales de los desarrolladores 😃

Reconozco el (, en mi opinión, solo) inconveniente del enfoque obsoleto de dictado de dictado con el MultiIndex resultante. Podría aplastarse si el usuario pasa una opción adicional (sí, YAO: - /).

Como se mencionó, no estoy en contra de la segunda versión, siempre que sea posible:

  • genere cosas dinámicamente de alguna manera y descomprímalas (gracias a la construcción **{} , yay Python!)
  • mantener el cambio de nombre y la especificación de agregación juntos (tener que realizar un seguimiento de dos listas de modo que su orden permanezca igual es simplemente molesto como usuario en mi humilde opinión)
  • use lambda o funciones parciales sin necesidad de soluciones alternativas debido a los nombres de función (potencialmente falta o conflicto con).

Como tal, la última sugerencia (con dictados o tuplas para el mapeo col> func) está bien, creo.

La primera propuesta en el comentario anterior se puede implementar si realmente lo desea, pero mi comentario al respecto es que, como usuario, no elegiría usarlo en lugar de la segunda alternativa debido al dolor de mantener las cosas sincronizadas entre dos listas.

Discutido en la reunión de desarrolladores de hoy.

Breve resumen

  1. @jorisvandenbossche intentará implementar gr.agg(b_sum=("B", "sum), ...) , es decir, cuando no se haya pasado arg a *GroupBy.agg , interprete kwargs como <output_name>=(<selection>, <aggfunc>)
  2. Ortogonal a estos problemas, nos gustaría implementar MutliIndex.flatten y proporcionar una palabra clave flatten=True a .agg

Tal vez esto ayude: mi solución para la desaprobación son estas funciones auxiliares que reemplazan los mapas alias-> aggr con una lista de funciones con el nombre correcto:

def aliased_aggr(aggr, name):
    if isinstance(aggr,str):
        def f(data):
            return data.agg(aggr)
    else:
        def f(data):
            return aggr(data)
    f.__name__ = name
    return f

def convert_aggr_spec(aggr_spec):
    return {
        col : [ 
            aliased_aggr(aggr,alias) for alias, aggr in aggr_map.items() 
        ]  
        for col, aggr_map in aggr_spec.items() 
    }

que da el comportamiento anterior con:

mydf_agg = mydf.groupby('cat').agg(convert_aggr_spec{
    'energy': {
        'total_energy': 'sum',
        'energy_p98': lambda x: np.percentile(x, 98),  # lambda
        'energy_p17': lambda x: np.percentile(x, 17),  # lambda
    },
    'distance': {
        'total_distance': 'sum',
        'average_distance': 'mean',
        'distance_mad': smrb.mad,   # original function
        'distance_mad_c1': mad_c1,  # partial function wrapping the original function
    },
}))

que es lo mismo que

mydf_agg = mydf.groupby('cat').agg({
    'energy': [ 
        aliased_aggr('sum', 'total_energy'),
        aliased_aggr(lambda x: np.percentile(x, 98), 'energy_p98'),
        aliased_aggr(lambda x: np.percentile(x, 17), 'energy_p17')
    ],
    'distance': [
         aliased_aggr('sum', 'total_distance'),
         aliased_aggr('mean', 'average_distance'),
         aliased_aggr(smrb.mad, 'distance_mad'),
         aliased_aggr(mad_c1, 'distance_mad_c1'),
    ]
})

Esto funciona para mí, pero probablemente no funcionará en algunos casos de esquina ...

Actualización : descubrió que el cambio de nombre no es necesario, ya que las tuplas en una especificación de agregación se interpretan como (alias, aggr). Entonces, la función alias_aggr no es necesaria y la conversión se convierte en:

def convert_aggr_spec(aggr_spec):
    return {
        col : [ 
           (alias,aggr) for alias, aggr in aggr_map.items() 
        ]  
        for col, aggr_map in aggr_spec.items() 
    }

Solo quiero intervenir aquí como otro usuario más al que realmente, realmente le falta la funcionalidad de agregar una columna en cualquier función e inmediatamente cambiarle el nombre en la misma fila. _Nunca_ me he encontrado usando el MultiIndex devuelto por pandas; lo aplané inmediatamente o realmente quiero especificar manualmente los nombres de mis columnas porque en realidad significan algo específico.

Estaría contento con cualquiera de los enfoques propuestos aquí: sintaxis similar a SQL (en realidad ya me encuentro usando mucho .query() en pandas), volviendo al comportamiento depreciado, cualquiera de las otras sugerencias. El enfoque actual ya me trajo el ridículo de los colegas que usan R.

Recientemente, incluso me encontré usando PySpark en lugar de pandas aunque no era necesario, solo porque me gusta mucho más la sintaxis:

df.groupby("whatever").agg(
    F.max("col1").alias("my_max_col"),
    F.avg("age_col").alias("average_age"),
    F.sum("col2").alias("total_yearly_payments")
)

Además, PySpark es mucho más complicado de escribir que los pandas en la mayoría de los casos, ¡esto se ve mucho más limpio! Así que definitivamente aprecio que el trabajo en esto todavía esté por hacer :-)

Creo que tenemos una sintaxis acordada para esta funcionalidad; necesitamos a alguien para
Impleméntalo.

El miércoles, 27 de marzo de 2019 a las 9:01 a. M. Thomas Kastl [email protected]
escribió:

Solo quiero intervenir aquí como otro usuario que es realmente, realmente
falta la funcionalidad de agregar una columna en cualquier función y
inmediatamente renombrarlo en la misma fila. Nunca he encontrado a mí mismo
usando el MultiIndex devuelto por pandas - o lo aplasto inmediatamente,
o en realidad quiero especificar manualmente los nombres de mis columnas porque
en realidad significa algo específico.

Estaría contento con cualquiera de los enfoques propuestos aquí: sintaxis similar a SQL
(De hecho, ya me encuentro usando mucho .query () en pandas),
volviendo al comportamiento depreciado, cualquiera de las otras sugerencias. El
El enfoque actual ya me trajo el ridículo de los colegas que usan R.

Recientemente incluso me encontré usando PySpark en lugar de pandas a pesar de que
no era necesario, solo porque me gusta mucho más la sintaxis:

df.groupby ("lo que sea"). agg (F.max ("col1"). alias ("my_max_col"),
F.avg ("age_col"). Alias ​​("average_age"),
F.sum ("col2"). Alias ​​("total_yearly_payments"))

Además, PySpark es mucho más complicado de escribir que los pandas en la mayoría de los casos,
¡Esto se ve mucho más limpio! Así que definitivamente aprecio ese trabajo en
esto todavía está por hacer :-)

-
Recibes esto porque te mencionaron.
Responda a este correo electrónico directamente, véalo en GitHub
https://github.com/pandas-dev/pandas/issues/18366#issuecomment-477168767 ,
o silenciar el hilo
https://github.com/notifications/unsubscribe-auth/ABQHIkCYYsah5siYA4_z0oop_ufIB3h8ks5va3nJgaJpZM4QjSLL
.

Voy a intentar llegar a esto por 0.25.0

Puse un PR en https://github.com/pandas-dev/pandas/pull/26399. La idea básica es permitir esta mezcla de cambio de nombre y agregación específica de columna usando **kwargs con el entendimiento de que los valores deben ser tuplas de (selection, aggfunc) .

In [2]: df = 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]})

In [3]: df
Out[3]:
  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

In [4]: df.groupby('kind').agg(min_height=('height', 'min'), max_weight=('weight', 'max'))
Out[4]:
      min_height  max_weight
kind
cat          9.1         9.9
dog          6.0       198.0

Esto tiene algunas limitaciones

  • Es algo peculiar al resto de pandas. El sytanx (output_name=(selection, aggfunc)) realmente no aparece en ningún otro lugar (aunque .assign usa el patrón output_name=... )
  • La ortografía de los nombres de salida que no son identificadores de Python es fea: .agg(**{'output name': (col, func)})
  • Es solo Python 3.6+, o necesitamos algunos trucos feos para 3.5 y anteriores, ya que el orden de **kwargs no se conservó previamente
  • El aggfunc debe ser una función unaria. Si su aggfunc personalizado necesita argumentos adicionales, primero deberá aplicarlo parcialmente

Y hay un detalle de implementación, aún no se admiten varios lambda aggfuncs para la misma columna, aunque eso se puede solucionar más adelante.


Sospecho que la mayoría de las personas suscritas aquí apoyarían alguna alternativa al comportamiento obsoleto. ¿Qué piensa la gente de este en concreto?

cc @WillAyd si me perdí alguna de sus preocupaciones.

Hola @TomAugspurger ,

Gracias por hacer que este avance.

Esto tiene algunas limitaciones

  • Es algo peculiar al resto de pandas. El sytanx (output_name=(selection, aggfunc)) realmente no aparece en ningún otro lugar (aunque .assign usa el patrón output_name=... )

No puedo evitar sentir que este tipo de argumento parece bastante similar al que motivó la desaprobación de la implementación existente en primer lugar.

¿Podría compartir por qué nos beneficiamos más de esta nueva forma que de la anterior _con respecto a ese argumento en particular_?

Un beneficio en el que ya podía pensar es que (para py3.6 +) podemos seleccionar el orden de salida de las columnas individualmente.

  • La ortografía de los nombres de salida que no son identificadores de Python es fea: .agg(**{'output name': (col, func)})

De alguna manera, la forma antigua era mejor en ese sentido. Pero como dije antes, siempre que sea posible usar la construcción **{...} para construir la agregación dinámicamente, estaré bastante feliz.

  • Es solo Python 3.6+, o necesitamos algunos trucos feos para 3.5 y anteriores, ya que el orden de **kwargs no se conservó previamente

¿Cómo funcionaba antes (función de dictado de dicto existente)? ¿El pedido estaba garantizado de alguna manera?

  • El aggfunc debe ser una función unaria. Si su aggfunc personalizado necesita argumentos adicionales, primero deberá aplicarlo parcialmente

Solo para confirmar mi comprensión: el aggfunc puede ser cualquier invocable que devuelva un valor válido, ¿verdad? (además de los aggfungs de cadena "usados ​​frecuentemente" como 'min' , 'max' , etc.). ¿Hay alguna diferencia con respecto a antes? (es decir, ¿no estaba ya presente la limitación unaria?)

Y hay un detalle de implementación, aún no se admiten varios lambda aggfuncs para la misma columna, aunque eso se puede solucionar más adelante.

Sí, ese es un poco molesto, pero siempre que sea solo una limitación temporal y esté abierto a solucionarlo, podría funcionar.

Sospecho que la mayoría de las personas suscritas aquí apoyarían alguna alternativa al comportamiento obsoleto. ¿Qué piensa la gente de este en concreto?

Bueno, en cualquier caso, creo que es muy importante mantener la agregación y el cambio de nombre en un solo paso. Si el comportamiento anterior no es realmente una opción, entonces esta alternativa podría funcionar.

¿Podría compartir por qué nos beneficiamos más de esta nueva forma que de la anterior con respecto a ese argumento en particular?

Puedo estar recordando mal, pero creo que SeriesGroupby.agg y DataFrameGroupby.agg tienen diferentes significados entre la clave externa en un diccionario (¿es una selección de columna o un nombre de salida?). Con esta sintaxis, consistentemente podemos hacer que la palabra clave signifique el nombre de salida.

De alguna manera, la forma antigua era mejor en ese sentido.

¿Es la diferencia solo el ** ? De lo contrario, creo que se comparten las mismas limitaciones.

¿Cómo funcionaba antes (función de dictado de dicto existente)? ¿El pedido estaba garantizado de alguna manera?

Ordenando las claves, que es lo que estoy haciendo ahora en PR.

Solo para confirmar mi comprensión: el aggfunc puede ser cualquier invocable que devuelva un valor válido, ¿verdad?

Esta es la diferencia

In [21]: df = pd.DataFrame({"A": ['a', 'a'], 'B': [1, 2], 'C': [3, 4]})

In [22]: def aggfunc(x, myarg=None):
    ...:     print(myarg)
    ...:     return sum(x)
    ...:

In [23]: df.groupby("A").agg({'B': {'foo': aggfunc}}, myarg='bar')
/Users/taugspurger/sandbox/pandas/pandas/core/groupby/generic.py:1308: FutureWarning: using a dict with renaming is deprecated and will be removed in a future version
  return super().aggregate(arg, *args, **kwargs)
None
Out[23]:
    B
  foo
A
a   3

con la propuesta alternativa, estamos reservando **kwargs para los nombres de las columnas de salida. Entonces necesitaría functools.partitial(aggfunc, myarg='bar') .

Ok, gracias, creo que el enfoque propuesto es 👍 para una primera iteración (y realmente estará bien como reemplazo tan pronto como se elimine la limitación de implementación de lambda múltiple)

¿Fue útil esta página
0 / 5 - 0 calificaciones