web-dev-qa-db-fra.com

Nommer les colonnes renvoyées dans Pandas?

J'ai des problèmes avec la fonctionnalité groupby de Pandas. J'ai lu la documentation , mais je ne vois pas comment appliquer des fonctions d'agrégation à plusieurs colonnes et avoir des noms personnalisés pour ces colonnes.

Ceci est très proche, mais la structure de données renvoyée a des en-têtes de colonne imbriqués:

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

(C'est-à-dire que je veux prendre les valeurs moyenne et standard de la colonne2, mais renvoie ces colonnes sous la forme "moyenne" et "standard")

Qu'est-ce que je rate?

63
David Chouinard

Cela supprimera le niveau le plus externe de l'index de colonne hiérarchique:

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

Si vous souhaitez conserver le niveau le plus externe, vous pouvez utiliser la fonction ravel () de la colonne à plusieurs niveaux pour former de nouvelles étiquettes:

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

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

les 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

Sinon, 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()]

les 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
82
unutbu

Pour pandas> = 0.25

La fonctionnalité permettant de nommer les colonnes d'agrégats renvoyées a été réintroduite dans la branche principale et est ciblée pour pandas 0.25. La nouvelle syntaxe est .agg(new_col_name=('col_name', 'agg_func'). Détaillé exemple du PR lié au lien 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 plus haut (ci-dessous) selon this PR . Encore une fois, copie de 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 d'un coup:

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 pandas <0.25

La réponse actuellement acceptée par unutbu décrit est un excellent moyen de faire cela dans pandas versions <= 0.20. Cependant, à partir de pandas 0.20, en utilisant cette méthode avertissement indiquant que la syntaxe ne sera pas disponible dans les futures versions de pandas.

Séries:

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

DataFrames:

FutureWarning: l'utilisation d'un dict avec un changement de nom est obsolète et sera supprimée dans une version ultérieure

Selon le 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 ,20 changelog pour plus de détails.

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

Avec l'ancienne syntaxe de dictionnaire de style, il était possible de passer plusieurs fonctions lambda à .agg, Car celles-ci seraient renommées 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 forme de 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 retournent 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 le SpecificationError, les fonctions nommées peuvent être définies a priori au lieu d'utiliser lambda. Les noms de fonction appropriés évitent également d'appeler .rename Sur le bloc de données par la suite. Ces fonctions peuvent être passées 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
53
joelostblom

Si vous souhaitez avoir un comportement similaire à JMP, créez des titres de colonne qui conservent toutes les informations du multi-index que vous pouvez utiliser:

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

Cela changera votre dataframe 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
6
Gadi Oron

Je suis d'accord avec le PO 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 cela est fait avec tidyverse's summarize dans R ), mais un travail -around dans pandas pour l’instant est de créer les nouvelles colonnes avec les noms souhaités via assign avant faisant 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()

(En utilisant reset_index tourne 'Country', 'f', 'mean', et 'std' le tout en colonnes régulières avec un index entier distinct.)

4
user3780389

comme ce type de cadre de données, il existe deux niveaux de nom de colonne:

 shop_id  item_id   date_block_num item_cnt_day       
                                  target              
0   0       30          1            31               

nous pouvons utiliser ce code:

df.columns = [col[0] if col[-1]=='' else col[-1] for col in df.columns.values]

le résultat est:

 shop_id  item_id   date_block_num target              
0   0       30          1            31 
0
saneryee

Avec l'inspiration de @Joel Ostblom

Pour ceux qui ont déjà un dictionnaire utilisable pour une simple agrégation, vous pouvez utiliser/modifier le code suivant pour l'agrégation de version plus récente, séparant l'agrégation et renommer une partie. S'il vous plaît soyez conscient du 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)

On a

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

S'il vous plaît laissez-moi savoir s'il existe un moyen plus intelligent de le faire. Merci.

0
udothemath1984