199 votes

Nommer les colonnes retournées dans une fonction d'agrégation Pandas ?

J'ai des difficultés avec la fonctionnalité groupby de Pandas. J'ai lu la documentation mais je n'arrive pas à comprendre comment appliquer des fonctions d'agrégation à plusieurs colonnes y ont des noms personnalisés pour ces colonnes.

Ce résultat est très proche, mais la structure de données renvoyée comporte des en-têtes de colonnes imbriquées :

data.groupby("Country").agg(
        {"column1": {"foo": sum()}, "column2": {"mean": np.mean, "std": np.std}})

(par exemple, je veux prendre la moyenne et la std de la colonne2, mais retourner ces colonnes en tant que "mean" et "std").

Qu'est-ce que je manque ?

333voto

joelostblom Points 7189

Pour pandas >= 0.25

La fonctionnalité permettant de nommer les colonnes agrégées retournées a été réintroduit dans la branche master et est prévue pour pandas 0.25. La nouvelle syntaxe est .agg(new_col_name=('col_name', 'agg_func') . Exemple détaillé tiré du rapport de presse mentionné ci-dessus :

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

Il sera également possible d'utiliser plusieurs expressions lambda avec cette syntaxe et la syntaxe de renommage en deux étapes que j'ai suggérée précédemment (ci-dessous), comme suit cette RP . Encore une fois, je copie l'exemple du PR :

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

In [3]: df.groupby("A").agg({'B': [lambda x: 0, lambda x: 1]})
Out[3]:
         B
  <lambda> <lambda 1>
A
a        0          1

et ensuite .rename() ou en une seule fois :

In [4]: df.groupby("A").agg(b=('B', lambda x: 0), c=('B', lambda x: 1))
Out[4]:
   b  c
A
a  0  0

Pour les pandas < 0.25

La réponse actuellement acceptée par unutbu décrit une bonne façon de faire cela dans les versions de pandas <= 0.20. Cependant, à partir de pandas 0.20, l'utilisation de cette méthode soulève un avertissement indiquant que la syntaxe ne sera pas disponible dans les versions futures de pandas.

Série :

FutureWarning : l'utilisation d'un dict sur une série pour l'agrégation est obsolète et sera supprimée dans une prochaine version.

Les cadres de données :

Avertissement futur : l'utilisation d'un dict avec le renommage est dépréciée et sera supprimée dans une prochaine version.

Selon la pandas 0.20 changelog La méthode recommandée pour renommer les colonnes lors de l'agrégation est la suivante.

# Create a sample data frame
df = pd.DataFrame({'A': [1, 1, 1, 2, 2],
                   'B': range(5),
                   'C': range(5)})

# ==== SINGLE COLUMN (SERIES) ====
# Syntax soon to be deprecated
df.groupby('A').B.agg({'foo': 'count'})
# Recommended replacement syntax
df.groupby('A').B.agg(['count']).rename(columns={'count': 'foo'})

# ==== MULTI COLUMN ====
# Syntax soon to be deprecated
df.groupby('A').agg({'B': {'foo': 'sum'}, 'C': {'bar': 'min'}})
# Recommended replacement syntax
df.groupby('A').agg({'B': 'sum', 'C': 'min'}).rename(columns={'B': 'foo', 'C': 'bar'})
# As the recommended syntax is more verbose, parentheses can
# be used to introduce line breaks and increase readability
(df.groupby('A')
    .agg({'B': 'sum', 'C': 'min'})
    .rename(columns={'B': 'foo', 'C': 'bar'})
)

Veuillez consulter le 0.20 changelog pour plus de détails.

Mise à jour 2017-01-03 en réponse au commentaire de @JunkMechanic.

Avec l'ancienne syntaxe des dictionnaires, il était possible de passer plusieurs lambda les fonctions de .agg puisqu'ils seront renommés avec la clé du dictionnaire transmis :

>>> df.groupby('A').agg({'B': {'min': lambda x: x.min(), 'max': lambda x: x.max()}})

    B    
  max min
A        
1   2   0
2   4   3

Plusieurs fonctions peuvent également être transmises à une seule colonne sous la forme d'une liste :

>>> df.groupby('A').agg({'B': [np.min, np.max]})

     B     
  amin amax
A          
1    0    2
2    3    4

Cependant, cela ne fonctionne pas avec les fonctions lambda, car elles sont anonymes et renvoient toutes <lambda> ce qui provoque une collision de noms :

>>> df.groupby('A').agg({'B': [lambda x: x.min(), lambda x: x.max]})
SpecificationError: Function names must be unique, found multiple named <lambda>

Pour éviter la SpecificationError Les fonctions nommées peuvent être définies a priori au lieu d'utiliser des lambda . Des noms de fonctions appropriés permettent également d'éviter d'appeler .rename sur la base de données. Ces fonctions peuvent être transmises avec la même syntaxe de liste que ci-dessus :

>>> def my_min(x):
>>>     return x.min()

>>> def my_max(x):
>>>     return x.max()

>>> df.groupby('A').agg({'B': [my_min, my_max]})

       B       
  my_min my_max
A              
1      0      2
2      3      4

148voto

unutbu Points 222216

Cette opération supprime le niveau le plus éloigné de l'index hiérarchique des colonnes :

df = data.groupby(...).agg(...)
df.columns = df.columns.droplevel(0)

Si vous souhaitez conserver le niveau le plus extérieur, vous pouvez utiliser la fonction ravel() sur la colonne multiniveau pour former de nouvelles étiquettes :

df.columns = ["_".join(x) for x in df.columns.ravel()]

mise à jour : dans les pandas plus récents, au lieu de .ravel() utiliser .tolist() ou .to_numpy() use df.columns = ["_".join(x) for x in df.columns.tolist()]


Par exemple :

import pandas as pd
import pandas.rpy.common as com
import numpy as np

data = com.load_data('Loblolly')
print(data.head())
#     height  age Seed
# 1     4.51    3  301
# 15   10.89    5  301
# 29   28.72   10  301
# 43   41.74   15  301
# 57   52.70   20  301

df = data.groupby('Seed').agg(
    {'age':['sum'],
     'height':['mean', 'std']})
print(df.head())
#       age     height           
#       sum        std       mean
# Seed                           
# 301    78  22.638417  33.246667
# 303    78  23.499706  34.106667
# 305    78  23.927090  35.115000
# 307    78  22.222266  31.328333
# 309    78  23.132574  33.781667

df.columns = df.columns.droplevel(0)
print(df.head())

rendements

      sum        std       mean
Seed                           
301    78  22.638417  33.246667
303    78  23.499706  34.106667
305    78  23.927090  35.115000
307    78  22.222266  31.328333
309    78  23.132574  33.781667

Alternativement, pour conserver le premier niveau de l'index :

df = data.groupby('Seed').agg(
    {'age':['sum'],
     'height':['mean', 'std']})
df.columns = ["_".join(x) for x in df.columns.ravel()]

rendements

      age_sum   height_std  height_mean
Seed                           
301        78    22.638417    33.246667
303        78    23.499706    34.106667
305        78    23.927090    35.115000
307        78    22.222266    31.328333
309        78    23.132574    33.781667

12voto

user3780389 Points 514

Je suis d'accord avec l'OP pour dire qu'il semble plus naturel et cohérent de nommer et de définir les colonnes de sortie au même endroit (par exemple, comme c'est le cas avec la fonction de tidyverse summarize en R ), mais une solution de contournement dans pandas consiste pour l'instant à créer les nouvelles colonnes avec les noms souhaités via assign avant qui effectue l'agrégation :

data.assign(
    f=data['column1'],
    mean=data['column2'],
    std=data['column2']
).groupby('Country').agg(dict(f=sum, mean=np.mean, std=np.std)).reset_index()

(Utilisation de reset_index tournants 'Country' , 'f' , 'mean' y 'std' dans des colonnes régulières avec un index entier séparé).

8voto

Gadi Oron Points 680

Si vous voulez avoir un comportement similaire à celui de JMP, en créant des titres de colonnes qui conservent toutes les informations de l'index multiple, vous pouvez utiliser :

newidx = []
for (n1,n2) in df.columns.ravel():
    newidx.append("%s-%s" % (n1,n2))
df.columns=newidx

Il modifiera votre cadre de données de :

    I                       V
    mean        std         first
V
4200.0  25.499536   31.557133   4200.0
4300.0  25.605662   31.678046   4300.0
4400.0  26.679005   32.919996   4400.0
4500.0  26.786458   32.811633   4500.0

à

    I-mean      I-std       V-first
V
4200.0  25.499536   31.557133   4200.0
4300.0  25.605662   31.678046   4300.0
4400.0  26.679005   32.919996   4400.0
4500.0  26.786458   32.811633   4500.0

1voto

udothemath1984 Points 52

Avec l'inspiration de @Joel Ostblom

Pour ceux qui disposent déjà d'un dictionnaire fonctionnel pour l'agrégation simple, vous pouvez utiliser/modifier le code suivant pour la nouvelle version de l'agrégation, en séparant l'agrégation et en renommant la partie. Attention au dictionnaire imbriqué s'il y a plus d'un élément.

def agg_translate_agg_rename(input_agg_dict):
    agg_dict = {}
    rename_dict = {}
    for k, v in input_agg_dict.items():
        if len(v) == 1:
            agg_dict[k] = list(v.values())[0]
            rename_dict[k] = list(v.keys())[0]
        else:
            updated_index = 1
            for nested_dict_k, nested_dict_v in v.items():
                modified_key = k + "_" + str(updated_index)
                agg_dict[modified_key] = nested_dict_v
                rename_dict[modified_key] = nested_dict_k
                updated_index += 1
    return agg_dict, rename_dict

one_dict = {"column1": {"foo": 'sum'}, "column2": {"mean": 'mean', "std": 'std'}}
agg, rename = agg_translator_aa(one_dict)

Nous obtenons

agg = {'column1': 'sum', 'column2_1': 'mean', 'column2_2': 'std'}
rename = {'column1': 'foo', 'column2_1': 'mean', 'column2_2': 'std'}

N'hésitez pas à me faire savoir s'il existe une façon plus intelligente de procéder. Je vous remercie.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X