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?
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
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
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.
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
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
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.)
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
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.