Pandas: A suspensão de reetiquetagem de dictos em groupby.agg traz muitos problemas

Criado em 19 nov. 2017  ·  37Comentários  ·  Fonte: pandas-dev/pandas

Este problema foi criado com base na discussão de # 15931 após a suspensão de reetiquetagem de dictos em groupby.agg . Muito do que está resumido a seguir já foi discutido na discussão anterior. Eu recomendaria em particular https://github.com/pandas-dev/pandas/pull/15931#issuecomment -336139085, onde os problemas também são claramente indicados.

A motivação por trás da descontinuação do # 15931 estava principalmente relacionada a trazer uma interface consistente para agg() entre a série e o Dataframe (veja também # 14668 para o contexto).

A funcionalidade de reclassificação com um dicionário aninhado foi descrita por alguns como sendo muito complexa e / ou inconsistente e, portanto, obsoleta.

No entanto, isso tem um preço: a impossibilidade de agregar e renomear ao mesmo tempo leva a problemas muito incômodos e alguma incompatibilidade com versões anteriores, onde nenhuma solução sensata está disponível:

  • _ [irritante] _ sem mais controle sobre os nomes das colunas resultantes
  • _ [irritante] _ você precisa encontrar uma maneira de renomear o MultiIndex _após_ realizar a agregação, exigindo manter o controle da ordem das colunas em dois lugares no código .... nada prático e às vezes totalmente impossível (casos abaixo )
  • ⚠️ _ [quebrar] _ não pode aplicar mais de um chamável com o mesmo nome interno na mesma coluna de entrada. Isso resulta em dois subcasos:

    • _ [quebrando] _ você não pode aplicar mais dois ou mais agregadores lambda na mesma coluna

    • _ [quebrar] _ você não pode aplicar mais dois ou mais agregadores de funções parciais, a menos que altere seu atributo __name__ oculto

Exemplo

_ (observe que este é um exemplo elaborado com o propósito de demonstrar o problema em um código o mais curto possível, mas todos os problemas demonstrados aqui me incomodaram na vida real desde a mudança e em situações não tão simples como aqui ) _

Dataframe 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 de:

fácil de escrever e ler e funciona conforme o esperado

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
    },
})

resulta em

          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

e tudo o que resta é:

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

Dança feliz elogiando os pandas 💃 🕺!

Depois de

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
    ],
})

O acima é quebrado porque todas as funções lambda resultarão em colunas chamadas <lambda> que resulta em

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

Regressão incompatível com versões anteriores: não se pode mais aplicar dois lambdas diferentes à mesma coluna original.

Se alguém remover lambda x: np.percentile(x, 98) de cima, teremos o mesmo problema com a função parcial que herda o nome da função da função original:

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

Finalmente, após sobrescrever o atributo __name__ da parcial (por exemplo, mad_c1.__name__ = 'mad_c1' ), obtemos:

    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

com ainda

  • falta uma coluna (98º percentil)
  • o manuseio das colunas MultiIndex
  • e a renomeação das colunas

para tratar em uma etapa separada.

Não há controle possível para os nomes das colunas após a agregação, o melhor que podemos obter de forma automatizada é alguma combinação do nome da coluna original e o _nome da função de agregação_ como este:

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

o que resulta em:

     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

e se você realmente precisar de nomes diferentes, pode fazer assim:

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

mas isso significa que você precisa ter cuidado para manter o código de renomeação (que agora deve estar localizado em outro lugar no código) em sincronia com o código onde a agregação é definida ...

Usuário de pandas tristes 😢 (que ainda adora pandas, é claro)


Estou totalmente empenhado em manter a consistência e, ao mesmo tempo, lamento profundamente a descontinuação da funcionalidade _agregar e renomear_. Espero que os exemplos acima tornem os pontos problemáticos claros.


Soluções possíveis

  • Retire o uso da funcionalidade de reclassificação de rótulos ditado.
  • Forneça outra API para poder fazer isso (mas por que deveriam haver dois métodos para o mesmo propósito principal, ou seja, agregação?)
  • ??? (aberto a sugestões)

_Lida opcional: _

Com relação à discussão mencionada na solicitação de pull que já está acontecendo há alguns meses, só recentemente percebi uma das razões pelas quais estou tão incomodado com essa reprovação: "agregar e renomear" é uma coisa natural a se fazer com Agrupamentos GROUP BY no SQL, já que no SQL você geralmente fornece o nome da coluna de destino diretamente ao lado da expressão de agregação, por exemplo, SELECT col1, avg(col2) AS col2_mean, stddev(col2) AS col2_var FROM mytable GROUP BY col1 .

_Não_ estou dizendo que o Pandas deve necessariamente fornecer as mesmas funcionalidades do SQL, é claro. Mas os exemplos fornecidos acima demonstram por que a API dict-of-dict foi, em minha opinião, uma solução limpa e simples para muitos casos de uso.

(* Eu pessoalmente não concordo que a abordagem ditado seja complexa.)

API Design Groupby

Comentários muito úteis

Para valer a pena, também sou fortemente a favor de não depreciar a funcionalidade.

Um grande motivo para mim é que há algo profundamente estranho em misturar o espaço de nomes da função do Python (algo a ver com a implementação particular) com os dados dos nomes das colunas (algo que certamente não deveria saber sobre a implementação). O fato de estarmos vendo colunas (potencialmente múltiplas colunas) com o nome '<lambda>' me causa grave dissonância cognitiva.

A abordagem de renomeação é irritante, porque há essa etapa intermediária em que nomes de colunas desnecessários (e expostos) são carregados. Além disso, eles são difíceis de renomear de forma confiável e sistemática porque há dependências potenciais na implementação.

Além disso, a funcionalidade dict aninhada é reconhecidamente complexa, mas é uma operação complexa que está sendo executada.

TL; DR Por favor, não deprecie. :)

Todos 37 comentários

@zertrin : Obrigado por fazer isso. Percebi que houve muita discussão em # 15931 sobre isso. Como não consegui ler na íntegra, não posso comentar no momento. No entanto, deixe-me pingar:

@jreback @jorisvandenbossche @TomAugspurger @ chris-b1

Eu concordo que renomear com a implementação agg atual é muito desajeitado e falha neste exemplo. Os dicts aninhados são um tanto complexos, mas escrevê-los como você deixou muito claro o que está acontecendo.

Suponho que poderia haver um parâmetro names adicionado a agg que levaria o mapeamento de dicionário das colunas agregadas a seus novos nomes. Você pode até adicionar outro parâmetro drop_index como um booleano para determinar se deve manter o nível de índice superior.

Portanto, a sintaxe se transformaria em:

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)

Ou talvez, um método totalmente novo agg_assign pudesse ser criado, que funcionaria de forma semelhante 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))

Na verdade, gosto muito mais dessa opção.

Para valer a pena, também sou fortemente a favor de não depreciar a funcionalidade.

Um grande motivo para mim é que há algo profundamente estranho em misturar o espaço de nomes da função do Python (algo a ver com a implementação particular) com os dados dos nomes das colunas (algo que certamente não deveria saber sobre a implementação). O fato de estarmos vendo colunas (potencialmente múltiplas colunas) com o nome '<lambda>' me causa grave dissonância cognitiva.

A abordagem de renomeação é irritante, porque há essa etapa intermediária em que nomes de colunas desnecessários (e expostos) são carregados. Além disso, eles são difíceis de renomear de forma confiável e sistemática porque há dependências potenciais na implementação.

Além disso, a funcionalidade dict aninhada é reconhecidamente complexa, mas é uma operação complexa que está sendo executada.

TL; DR Por favor, não deprecie. :)

Minha contribuição é motivada por duas coisas.

  1. Estou ciente e concordo com a motivação para reduzir a API inchada dos Pandas. Mesmo que eu esteja equivocado em relação à motivação percebida para reduzir os elementos "inchados" da API, ainda é minha opinião que a API do Pandas poderia ser simplificada.
  2. Acho que é melhor ter um bom livro de receitas com boas receitas do que fornecer API para satisfazer os desejos e vontades de todos. Não estou afirmando que a renomeação via dicionários qualifica aninhados como satisfazer caprichos, pois já existia e estamos discutindo isso de depreciação. Mas está no espectro entre a API simplificada e algo ... mais.

Além disso, os objetos Pandas Series e DataFrame tinham métodos pipe para facilitar o pipelining. Neste segmento de documento , é discutido que poderíamos usar pipe como proxy de métodos em vez de subclasses. No mesmo espírito, poderíamos usar o novo GroupBy.pipe para desempenhar uma função semelhante e permitir-nos construir métodos de proxy para objetos groupby.

Vou usar o exemplo de @zertrin

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)

O que resulta em

            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

O método pipe torna a adição de uma nova API desnecessária em muitos casos. Ele também fornece os meios para uma substituição da funcionalidade obsoleta que estamos discutindo. Portanto, estou inclinado a prosseguir com a suspensão de uso.

Eu realmente gosto da ideia de tdpetrou - usar: names=name_dict .

Isso pode deixar todos felizes. Isso nos dá a possibilidade de renomear colunas facilmente como desejarmos.

Na verdade não, como mencionei no meu post inicial, isso não resolveria o problema de desacoplar o local onde a operação de agregação é definida do nome da coluna resultante, exigindo um esforço extra para ter certeza de que ambos estão "sincronizados".

Não digo que seja uma má solução (afinal resolve os outros problemas), mas não seria tão fácil e claro como a abordagem dict of dict. Quero dizer aqui que, no momento da escrita, você precisa manter ambos os dictos de listas sincronizados e, ao ler a fonte, o leitor deve fazer um esforço para combinar os nomes no segundo ditado de listas com a definição agregada no primeiro ditado de listas. Isso é o dobro do esforço em cada caso.

Os dicts aninhados são um tanto complexos, mas escrevê-los como você deixou muito claro o que está acontecendo.

Ainda não entendo por que todo mundo parece dizer que dict ou dict é complexo. Para mim, essa é a maneira mais clara de fazer isso.

Dito isso, se a palavra-chave names for a única solução com a qual a equipe do pandas se sente confortável, isso ainda seria uma melhoria em relação à situação atual.

@pirsquared solução interessante com API atual. Embora na minha opinião não seja muito fácil de entender (eu realmente não entendo como funciona: confused :)

Comecei um tópico sobre o subreddit da datascience - O que você odeia nos pandas? . Alguém expôs seu desprezo pelo MultiIndex retornado após um groupby e apontou para o verbo dplyr do que é implementado em plydata . Acontece que funciona exatamente como agg_assign , o que foi bastante interessante.

@zertrin agg_assign seria superior à sua abordagem dict of dict e seria idêntico às agregações sql, além de permitir que várias colunas interajam entre si dentro da agregação. Também funcionaria de forma idêntica a DataFrame.assign .

Alguma opinião sobre @jreback @TomAugspurger ?

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

Embora isso resolva o problema, é necessário alinhar chaves e valores em dois lugares. Acho que uma API (como sugerido para .agg_assign ) que não exige esse código de contabilidade é menos sujeita a erros.

Há também o problema do código de limpeza após o uso da API. Quando as operações de groupby retornam um MultiIndex dataframe, na maioria dos casos o usuário desfaz o MultiIndex . A maneira declarativa direta de usar .agg_assign não sugere nenhuma hierarquia, nenhuma saída de MultiIndex , nenhuma limpeza posterior.

Com base nos padrões de uso, acho que as saídas de vários índices devem ser estritamente opt-in e não opt-out.

Eu estava inicialmente cético sobre a proposição agg_assign , mas os dois últimos comentários me convenceram de que essa poderia ser uma boa solução.

Pensando especialmente na possibilidade de usá-lo na forma agg_assign(**relabeling_dict) e assim poder definir meu relabeling_dict assim:

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)
}

Isso seria bastante flexível e resolveria todos os problemas mencionados no meu OP.

@zertrin @ has2k1

Estava pensando um pouco mais sobre isso e essa funcionalidade já existe com apply . Você simplesmente retorna uma série com índice como os novos nomes de coluna e valores como a agregação. Isso permite espaços no nome e permite ordenar as colunas exatamente como você deseja:

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)

Portanto, pode não haver necessidade de um novo método, mas apenas de um exemplo melhor nos documentos.

@tdpetrou , você está correto. Eu tinha esquecido como apply funciona porque uso minha própria versão por causa da dupla execução no processo de seleção de caminho rápido-lento.

Hum, de fato, não há chance de que eu pensasse em usá-lo em um contexto de agregação apenas lendo o documento, entretanto ...
Além disso, ainda acho a solução com apply um pouco complicada demais. A abordagem agg_assign parecia mais direta e compreensível.

Uma vez que nunca houve realmente uma declaração sobre isso, a abordagem dict-of-dict (que, embora atualmente obsoleta, já está implementada e também resolve todos esses problemas) definitivamente fora de questão?

Exceto pela abordagem agg_assign , dict-of-dict ainda parece a mais simples e não precisa de nenhuma codificação, apenas desaprovando.

A vantagem e a desvantagem da abordagem agg_assign é que ela empurra a seleção da coluna para o método de agregação . Em todos os exemplos, o x passado para lambda é algo como self.get_group(group) para cada grupo em self , um objeto DataFrameGroupBy . Isso é bom porque separa claramente a nomenclatura , que está em **kwargs , da seleção , que está na função.

A desvantagem é que suas funções de agregação genéricas e legais agora precisam se preocupar com a seleção de colunas. Não há almoço grátis! Isso significa que você terá muitos ajudantes como lambda x: x[col].min . Você também precisará ter cuidado com coisas como np.min que reduz em todas as dimensões, contra pd.DataFrame.min , que reduz mais de axis=0 . É por isso que algo como agg_assign não seria equivalente a apply . apply ainda opera em colunas para certos métodos.

Não tenho certeza sobre essas compensações em relação ao método dos ditos, mas estou curioso para ouvir os pensamentos de outras pessoas. Aqui está um esboço de agg_assign , que chamei de agg_table para enfatizar que as funções estão sendo transmitidas às tabelas, não às colunas:

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

Suspeito que poderíamos fazer um pouco para tornar o desempenho disso menos terrível, mas não tanto quanto .agg ...

Alguém da Equipe Principal do Pandas poderia explicar qual é o principal motivo para a descontinuação da renomeação de dictos em groupby.agg ?

Eu poderia facilmente entender se isso causa muitos problemas para manter o código, mas se for sobre complexidade para o usuário final - eu também optaria por trazê-lo de volta, pois é muito claro em comparação com as soluções necessárias ...

Obrigada!

Alguém da Equipe Principal do Pandas poderia explicar qual é o principal motivo para a rejeição da renomeação de dictos em groupby.agg?

Você viu https://github.com/pandas-dev/pandas/pull/15931/files#diff -52364fb643114f3349390ad6bcf24d8fR461?

O principal motivo era que as teclas de comando estavam sobrecarregadas para fazer duas coisas. Para Series / SeriesGroupBy, são para nomenclatura. Para DataFrame / DataFrameGroupBy, eles servem para selecionar uma coluna.

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

Provavelmente, essa não é a coisa mais confusa em pandas, então talvez pudéssemos revisitá-la :) Presumivelmente estou perdendo alguns casos extremos. Mas mesmo se removermos as agregações de ditos, ainda teremos a inconsistência entre nomenclatura e seleção de coluna:

Para Series / SeriesGroupBy, as chaves de dicionário são sempre para nomear a saída.

Para DataFrame / DataFrameGroupby, as teclas dict são sempre para seleção. Com dict-of-dicts, selecionamos uma coluna e, em seguida, o dict interno é para nomear a saída, assim como Series / SeriesGroupBy.

Discutimos isso brevemente antes (em algum lugar na longa discussão sobre a suspensão do uso) e propus algo semelhante aqui: https://github.com/pandas-dev/pandas/pull/14668#issuecomment -274508089. Mas no final apenas a depreciação foi implementada, e não as idéias para tornar mais fácil a outra funcionalidade de usar dicts (a função de 'renomeação').

O problema era que dicts eram usados ​​para 'seleção' (em qual coluna você deseja que esta função fosse aplicada) e 'renomear' (qual deveria ser o nome da coluna resultante ao aplicar esta função). Uma sintaxe alternativa, além de dicts, podem ser argumentos de palavra-chave, conforme discutido aqui na proposta agg_assign .
Ainda sou a favor de explorar essa possibilidade, seja no próprio agg ou em um novo método como agg_assign .

O que propus naquela época era algo semelhante a agg_assign mas usando um dicionário por palavra-chave em vez de uma função lambda. Traduzido para o exemplo aqui, seria 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})

Não tenho certeza se isso é necessariamente mais legível ou fácil de escrever como a versão com todos os lambdas, mas, este poderia ser potencialmente mais performante, já que os pandas ainda podem usar as implementações otimizadas para soma, média, etc. nas colunas onde você faz não tem uma função lambda ou especificada pelo usuário.

Uma grande questão com essa abordagem seria o que df.groupby('cat').agg(foo='mean') significaria? Isso logicamente aplicaria 'mean' a todas as colunas, uma vez que você não fez nenhuma seleção (semelhante a {'col1' : {'foo': 'mean'}, 'col2': {'foo':'mean'}, 'col3': ...} antes). Mas, isso resultaria em colunas multi-indexadas, enquanto no exemplo acima eu acho que seria bom não acabar com colunas MI.

Eu acho que o acima pode ser compatível com versões anteriores dentro do agg , mas a questão é se isso é necessário.
Eu também acho que isso se estenderia muito bem ao caso series , como:

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

(e você pode até considerar fazer o acima uma vez para 'distância' e uma vez para 'energia' e concat o resultado se você não gostar de todos os dicts / lambda's)

@TomAugspurger Em seu exemplo de implementação simples de agg_table , não seria melhor iterar sobre as diferentes funções a serem aplicadas, em vez de iterar os grupos e, no final, concatenar as novas colunas por eixo = 1 em vez de concatenar as linhas recém-formadas por eixo = 0?

A propósito , @tdpetrou @smcateer @pirsquared e outros, muito obrigado por levantar esta questão e fornecer feedback tão detalhado. Esse feedback e envolvimento da comunidade são muito importantes!

Na verdade, eu realmente gosto do padrão sugerido por @tdpetrou (usando aplicar com uma função que retorna uma Série) - provavelmente ainda melhor do que o ditado.

Se a função retornar pd.Series(data, index=data.keys()) temos a garantia de obter os índices na ordem certa? (Apenas pensando sobre a melhor forma de implementar o padrão em meu código - correndo o risco de desviar do assunto).

Edit: desculpe, eu entendi mal o ponto do argumento do índice (é opcional aqui, apenas necessário se você quiser especificar a ordem das colunas - retornar pd.Series(data) faz o trabalho para mim).

O exemplo de @tdpetrou funcionaria com first & last ?

Eu tive que recorrer a cabeça / cauda assim

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)

Eu ainda gostaria de abordar isso, mas não acho que será feito por 0,23.

A abordagem de @tdpetrou poderia funcionar sem definir uma função que nunca mais usaremos em nosso código? Vindo de um mundo Q / Kdb + (semelhante ao SQL), estou confuso por que precisamos criar qualquer variável / função temporal para uma instrução select simples.

OP aqui.

Honestamente, depois de todo esse tempo e de muita discussão em # 15931 e aqui, ainda não estou convencido de que esta seja uma boa ideia para descartar a renomeação de dictos.

No final, nenhuma das alternativas propostas aqui é mais intuitiva para os usuários do que a atual abordagem de ditado de reetiquetagem IMHO. Quando estava na documentação, apenas com um exemplo, ficava claro como isso funciona, e é muito flexível.

É claro que os desenvolvedores do pandas ainda podem pensar o contrário, apenas concordando com o ponto de vista de um usuário.

Mesmo a abordagem de reetiquetagem não é muito intuitiva. Em minha opinião, a sintaxe deve ser semelhante a SQL - func(column_name) as new_column_name . Em Python, podemos fazer isso com uma tupla de três itens. (func, column_name, new_column_name) . É assim que dexplo faz agregação por grupo.

dexplo

@zertrin , você tem comentários sobre minha proposta acima: https://github.com/pandas-dev/pandas/issues/18366/#issuecomment -349089667
No final, meio que inverte a ordem do dicionário: em vez de "{col: {nome: função}}" seria mais ou menos "** {nome: {col: função}}"

@jorisvandenbossche Considerei sua abordagem. O fato é que não vejo quais vantagens adicionais isso traz em relação à abordagem atual.

Para ser mais franco, dadas as seguintes opções:

  1. Comportamento atual incorreto que funciona bem (algumas linhas de código de reprovação para remover, adicionar novamente a parte da documentação que foi removida)
  2. Implementar sua proposta (mudanças significativas a serem feitas no código, prosseguir com a depreciação da abordagem atual, necessidade de todos os usuários adaptarem seu código)

Não vejo por que devemos escolher 2, a menos que traga vantagens significativas e tangíveis do ponto de vista do desenvolvedor e do usuário.

Para abordar alguns dos pontos em sua proposta acima:

O problema era que dicts eram usados ​​para 'seleção' (em qual coluna você deseja que esta função fosse aplicada) e 'renomear' (qual deveria ser o nome da coluna resultante ao aplicar esta função).

Como foi bem documentado antes, não acredito que seja um problema para os usuários . Pessoalmente, entendi imediatamente ao examinar os exemplos na documentação. (EDITAR: e pensei: _ "yay! Construção muito útil, corresponde exatamente ao que eu estava procurando. Legal." _)

Uma sintaxe alternativa, além de dicts, podem ser argumentos de palavra-chave

Uma das coisas atraentes de usar a abordagem dict-of-dict é que os usuários podem facilmente gerá-la dinamicamente com algum outro código. Como você apontou no comentário logo acima deste, passar para os argumentos de palavra-chave como em sua proposição ainda permitiria isso por meio da construção **{name: {col: func}} . Portanto, não sou contra sua proposta. Simplesmente não vejo o valor agregado e a necessidade de tais mudanças quando já alcançamos o mesmo nível de funcionalidade com o sistema atualmente implementado.

No final, sua proposta seria _tudo_ se o pandas core dev tivesse um forte sentimento contra a abordagem atual. Simplesmente não vejo nenhum benefício como _usuário_. (na verdade, vejo a desvantagem de alterar todo o código de usuário existente para fazê-lo funcionar novamente com a nova proposição).

@zertrin , discutimos isso ontem com alguns desenvolvedores principais, mas não escrevemos o resumo aqui. Portanto, agora vou fazer isso, antes de responder ao seu comentário, para refletir apenas nossos pensamentos de ontem.


Portanto, primeiro a declarar, a noção de que uma funcionalidade básica como o SQL "SELECT avg (col2) as col2_avg" deve funcionar e ser fácil, é algo com que concordamos totalmente e realmente queremos uma solução para isso.

Além dos motivos originais pelos quais decidimos descontinuar isso (que pode ou não ser tão forte), os dictos atuais (descontinuados) também não são tão ideais, pois isso cria um MultiIndex que você na verdade nunca deseja:

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

Acima, o primeiro nível do MultiIndex é supérfluo, pois você já renomeou especificamente as colunas (no exemplo do OP, isso também é seguido diretamente pela eliminação do primeiro nível das colunas).
No entanto, é difícil mudar isso porque você também pode fazer coisas como gr.agg(['sum', 'mean']) ou (misto) gr.agg({'B': ['sum', 'mean'], 'C': {'c_mean': 'mean', 'c_count': 'count'}}) onde o MultiIndex é necessário e faz sentido.

Portanto, uma das propostas mencionadas na discussão acima era ter uma maneira de especificar os nomes das colunas finais separadamente (por exemplo, https://github.com/pandas-dev/pandas/issues/18366#issuecomment-346683449).
Adicionar, por exemplo, uma palavra-chave extra a aggregate para especificar os nomes das colunas, como

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

seria possível.
No entanto, se dividirmos a especificação da coluna / função e os novos nomes das colunas, também podemos tornar isso mais genérico do que uma nova palavra-chave e fazer algo como:

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

Isso precisa de https://github.com/pandas-dev/pandas/issues/14829 para ser resolvido (algo que queremos fazer para o 0.24.0).
(Nota importante: para isso, é necessário corrigir o duplicado nomes problema das funções lambda, por isso, devemos fazer algum tipo de deduplicação automática dos nomes, se queremos apoiar esta solução.)


Então, ainda gostamos da maneira como os argumentos de palavra-chave são renomeados. As razões para isso são:

  • é semelhante a como assign funciona em pandas, e também é consistente com como groupby().aggregate() funciona em ibis (e também semelhante a como parece, por exemplo, dplyr em R)
  • fornece diretamente os nomes das colunas não hierárquicas que você deseja (sem MultiIndex)
  • para os casos simples (também por exemplo, para o caso da série), acho que é mais simples como o dict de dict

Ainda discutimos um pouco sobre como isso poderia ser. O que propus acima foi (usar a seleção de coluna / função equivalente como em meus primeiros exemplos):

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

Você ainda pode construir esta especificação como um ditos, mas com os níveis interno e externo trocados em comparação com a versão atual (obsoleta):

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

(poderíamos ter um exemplo de função auxiliar que converte os dictos de dictos existentes para esta versão)

No entanto, o dict é sempre apenas um único {col: func} , e aqueles múltiplos dicts de um único elemento parecem um pouco estranhos. Então, uma alternativa que pensamos é usar tuplas:

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

Isso parece um pouco melhor, mas por outro lado, {'B': 'sum'} dict é consistente com as outras APIs para especificar a coluna na qual aplicar a função.


Ambas as sugestões acima (a renomeação mais fácil depois e a nomenclatura baseada em palavras-chave) são, em princípio, ortogonais, mas pode ser bom ter as duas (ou ainda algo mais com base em uma discussão posterior)

Obrigado por encaminhar aqui os pensamentos atuais dos desenvolvedores 😃

Eu reconheço a desvantagem (na minha opinião, apenas) da abordagem obsoleta do ditado com o MultiIndex resultante. Pode ser nivelado se o usuário passar uma opção adicional (sim YAO: - /).

Como mencionei, não sou contra a segunda versão, desde que seja possível:

  • gerar coisas dinamicamente de alguma forma e desempacotar (graças à construção **{} , yay Python!)
  • manter a renomeação e a especificação de agregação juntas (ter que manter o controle de duas listas de forma que sua ordem permaneça a mesma é simplesmente irritante para um usuário IMHO)
  • use lambda ou funções parciais sem a necessidade de soluções alternativas por causa dos nomes de função (potencialmente falta ou conflito com).

Como tal, a última sugestão (com dicts ou tuplas para o mapeamento col> func) está ok, eu acho.

A primeira proposição no comentário anterior pode ser implementada se você realmente quiser, mas meu feedback sobre isso é que, como um usuário, eu não escolheria usá-la em vez da segunda alternativa devido à dor de manter as coisas em sincronia entre duas listas.

Discutido na reunião de desenvolvimento de hoje.

Pequeno resumo

  1. @jorisvandenbossche tentará implementar gr.agg(b_sum=("B", "sum), ...) , ou seja, quando não houver arg passado para *GroupBy.agg , interpretará os kwargs como <output_name>=(<selection>, <aggfunc>)
  2. Ortogonalmente a esses problemas, gostaríamos de implementar MutliIndex.flatten e fornecer uma palavra-chave flatten=True para .agg

Talvez isso ajude: minha solução alternativa para a descontinuação são essas funções auxiliares que substituem os mapas alias-> aggr pela lista de funções nomeadas corretamente:

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 dá o comportamento antigo com:

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 é o mesmo 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'),
    ]
})

Isso funciona para mim, mas provavelmente não funcionará em alguns casos extremos ...

Atualização : descobri que renomear não é necessário, pois as tuplas em uma especificação de agregação são interpretadas como (alias, aggr). Portanto, a função alias_aggr não é necessária e a conversão se torna:

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() 
    }

Eu só quero comentar aqui como mais um usuário que está realmente sentindo falta da funcionalidade de agregar uma coluna em qualquer função e renomeá-la imediatamente na mesma linha. Eu _nunca_ me vi usando o MultiIndex retornado pelo pandas - ou eu o nivelo imediatamente ou na verdade quero especificar manualmente os nomes das minhas colunas porque eles realmente significam algo específico.

Eu ficaria feliz com qualquer uma das abordagens propostas aqui: sintaxe do tipo SQL (na verdade, já estou usando .query() muito em pandas), revertendo para o comportamento depreciado, qualquer uma das outras sugestões. A abordagem atual já me trouxe o ridículo de colegas que usam R.

Recentemente, comecei a usar o PySpark em vez dos pandas, embora não fosse necessário, só porque gosto muito mais da sintaxe:

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")
)

Além disso, o PySpark é muito mais complicado de escrever do que os pandas na maioria dos casos, parece muito mais limpo! Então, eu definitivamente agradeço que o trabalho nisso ainda esteja sendo feito :-)

Acho que temos uma sintaxe combinada para esta funcionalidade; precisamos de alguém para
Implementá-lo.

Na quarta-feira, 27 de março de 2019 às 9:01 Thomas Kastl [email protected]
escreveu:

Eu só quero falar aqui como mais um usuário que é muito, muito
falta a funcionalidade de agregar uma coluna em qualquer função e
renomeando-o imediatamente na mesma linha. Eu nunca me encontrei
usando o MultiIndex retornado pelos pandas - eu aliso-o imediatamente,
ou na verdade quero especificar manualmente os nomes das minhas colunas porque eles
realmente significa algo específico.

Eu ficaria feliz com qualquer uma das abordagens propostas aqui: sintaxe semelhante a SQL
(Na verdade, já uso bastante .query () em pandas),
revertendo para o comportamento depreciado, qualquer uma das demais sugestões. O
abordagem atual já me trouxe o ridículo de colegas que usam R.

Recentemente, comecei a usar o PySpark em vez dos pandas, embora
não foi necessário, só porque gosto muito mais da sintaxe:

df.groupby ("qualquer"). agg (F.max ("col1"). alias ("my_max_col"),
F.avg ("idade_col"). Alias ​​("idade média"),
F.sum ("col2"). Alias ​​("total_ano_pagamentos"))

Além disso, o PySpark é muito mais complicado de escrever do que os pandas na maioria dos casos,
isso parece muito mais limpo! Então, eu definitivamente aprecio o trabalho em
isso ainda deve ser feito :-)

-
Você está recebendo isso porque foi mencionado.
Responda a este e-mail diretamente, visualize-o no GitHub
https://github.com/pandas-dev/pandas/issues/18366#issuecomment-477168767 ,
ou silenciar o tópico
https://github.com/notifications/unsubscribe-auth/ABQHIkCYYsah5siYA4_z0oop_ufIB3h8ks5va3nJgaJpZM4QjSLL
.

Vou tentar chegar a 0.25.0

Eu coloquei um PR em https://github.com/pandas-dev/pandas/pull/26399. A ideia básica é permitir essa mistura de renomeação e agregação específica de coluna usando **kwargs com o entendimento de que os valores devem 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

Isso tem algumas limitações

  • É um tanto peculiar para o resto dos pandas. O sytanx (output_name=(selection, aggfunc)) realmente não aparece em nenhum outro lugar (embora .assign use o padrão output_name=... )
  • A grafia dos nomes de saída que não são identificadores Python é feia: .agg(**{'output name': (col, func)})
  • É apenas Python 3.6+, ou precisamos de alguns hacks feios para 3.5 e anteriores, uma vez que a ordem de **kwargs não foi preservada anteriormente
  • O aggfunc precisa ser uma função unária. Se o seu aggfunc personalizado precisar de argumentos adicionais, você precisará aplicá-lo parcialmente primeiro

E há um detalhe de implementação, vários lambda aggfuncs para a mesma coluna ainda não são suportados, embora isso possa ser corrigido mais tarde.


Suspeito que a maioria das pessoas inscritas aqui apoiaria alguma alternativa ao comportamento obsoleto. O que as pessoas pensam especificamente sobre este?

cc @WillAyd se perdi alguma de suas preocupações.

Olá @TomAugspurger ,

Obrigado por avançar.

Isso tem algumas limitações

  • É um tanto peculiar para o resto dos pandas. O sytanx (output_name=(selection, aggfunc)) realmente não aparece em nenhum outro lugar (embora .assign use o padrão output_name=... )

Não posso deixar de sentir que esse tipo de argumento parece bastante semelhante ao que motivou a depreciação da implementação existente em primeiro lugar.

Você poderia compartilhar por que nos beneficiamos mais com esta nova maneira em relação à antiga _ com relação a esse argumento em particular_?

Um benefício que eu já poderia pensar é que (para py3.6 +) podemos selecionar a ordem de saída das colunas individualmente.

  • A grafia dos nomes de saída que não são identificadores Python é feia: .agg(**{'output name': (col, func)})

De alguma forma, o jeito antigo era melhor nesse aspecto. Mas, como eu disse antes, contanto que seja possível usar a construção **{...} para construir a agregação dinamicamente, eu ficaria feliz.

  • É apenas Python 3.6+, ou precisamos de alguns hacks feios para 3.5 e anteriores, uma vez que a ordem de **kwargs não foi preservada anteriormente

Como funcionava antes (recurso dict-of-dict existente)? o pedido foi garantido de alguma forma?

  • O aggfunc precisa ser uma função unária. Se o seu aggfunc personalizado precisar de argumentos adicionais, você precisará aplicá-lo parcialmente primeiro

Só para confirmar meu entendimento: o aggfunc pode ser qualquer chamável que retorne um valor válido certo? (além dos aggfungs de string "frequentemente usados" como 'min' , 'max' , etc.). Existe alguma diferença em relação a antes? (ou seja, a limitação unária já não estava presente?)

E há um detalhe de implementação, vários lambda aggfuncs para a mesma coluna ainda não são suportados, embora isso possa ser corrigido mais tarde.

Sim, esse é meio chato, mas contanto que seja apenas uma limitação temporária e esteja aberto para consertar isso, isso pode funcionar.

Suspeito que a maioria das pessoas inscritas aqui apoiaria alguma alternativa ao comportamento obsoleto. O que as pessoas pensam especificamente sobre este?

Bem, em qualquer caso, acho que agregar e renomear em uma única etapa é realmente importante manter. Se o comportamento antigo não for realmente uma opção, essa alternativa pode servir.

Você poderia compartilhar por que nos beneficiamos mais com essa nova maneira do que com a antiga no que diz respeito a esse argumento específico.

Posso estar me lembrando mal, mas acredito que SeriesGroupby.agg e DataFrameGroupby.agg têm significados diferentes entre a chave externa em um dicionário (é uma seleção de coluna ou uma nomenclatura de saída?). Com esta sintaxe, podemos consistentemente fazer com que a palavra-chave signifique o nome de saída.

De alguma forma, o jeito antigo era melhor nesse aspecto.

A diferença é apenas ** ? Caso contrário, acho que as mesmas limitações são compartilhadas.

Como funcionava antes (recurso dict-of-dict existente)? o pedido foi garantido de alguma forma?

Classificando as chaves, que é o que estou fazendo no PR agora.

Só para confirmar meu entendimento: o aggfunc pode ser qualquer chamável que retorne um valor válido certo?

Aqui está a diferença

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

com a proposta alternativa, estamos reservando **kwargs para nomes de colunas de saída. Portanto, você precisaria de functools.partitial(aggfunc, myarg='bar') .

Ok, obrigado, acho que a abordagem proposta é 👍 para uma primeira iteração (e realmente funcionará como uma substituição assim que a limitação de implementação lambda múltipla for removida)

Esta página foi útil?
0 / 5 - 0 avaliações