命名熊猫聚合函数中返回的列?

熊猫的分组功能出了点问题。我读过 文件,但是我不知道如何将聚合函数应用于多个列。 还有为这些列定制了自定义名称。

这个结果非常接近,但返回的数据结构具有嵌套的列标题:

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

(例如,我想取 column 2的 mean 和 std,但是将这些列返回为“ mean”和“ std”)

我错过了什么?

242322 次浏览

这将从分层列索引中删除最外层:

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

如果希望保持最外层,可以在多层列上使用 ravel ()函数来形成新的标签:

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

更新 : 在较新的熊猫使用 .tolist().to_numpy()代替 .ravel()


例如:

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

产量

      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 = data.groupby('Seed').agg(
{'age':['sum'],
'height':['mean', 'std']})
df.columns = ["_".join(x) for x in df.columns.ravel()]

产量

      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

如果你想有一个类似于 JMP 的行为,创建列标题,保持所有的信息从多索引,你可以使用:

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

它会改变你的数据框架:

    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

熊猫 > = 0.25

为返回的聚合列命名的功能是 重新引入主分支,目标是熊猫0.25。新的语法是 .agg(new_col_name=('col_name', 'agg_func')。以上链接的公关详细例子:

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

还可以使用多个 lambda 表达式和这种语法,以及我前面(下面)建议的按 这个公关的两步重命名语法。再次,照搬公关中的例子:

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

然后是 .rename(),或者一次性:

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

大熊猫 < 0.25

目前被 unutbu 接受的答案是在熊猫版本 < = 0.20中这样做的好方法。但是,从0.20开始,使用此方法会引发警告,表明该语法在熊猫的未来版本中将不可用。

系列:

将来警告: 不推荐在 Series 上使用用于聚合的 dict,将在以后的版本中删除

数据框架:

将来的警告: 使用带有重命名的阻塞字符是不推荐的,将在将来的版本中删除

根据 熊猫0.20变更记录,在聚合时重命名列的推荐方法如下。

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

有关详细信息,请参阅 0.20的变更记录

更新2017-01-03回应@JunkMachic 的评论。

使用旧式字典语法,可以将多个 lambda函数传递给 .agg,因为这些函数将用传递的字典中的键重命名:

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

多个函数也可以作为列表传递给单个列:

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


B
amin amax
A
1    0    2
2    3    4

但是,这不适用于 lambda 函数,因为它们是匿名的,并且所有函数都返回 <lambda>,这会导致名称冲突:

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

为了避免使用 SpecificationError,可以事先定义命名函数,而不使用 lambda。合适的函数名也可以避免事后在数据帧上调用 .rename。这些函数可以使用与上面相同的列表语法传递:

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

以“ Joel Ostblom”为灵感

对于那些已经有一个可用于聚合的字典的用户,您可以使用/修改下面的代码来进行更新的版本聚合,分离聚合和重命名部分。如果有多于一个条目,请注意嵌套字典。

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)

我们得到

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

如果有更聪明的办法请告诉我,谢谢。

我同意 OP 的观点,在同一个地方命名和定义输出列似乎更自然,也更一致(例如使用 Tidyverse 的 summarize在 R) ,但目前熊猫的一个解决方案是通过 assign 之前来创建具有所需名称的新列进行聚合:

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

(使用 reset_index'Country''f''mean''std'全部转换为具有单独整数索引的规则列。)

例如这种数据框架,列名有两个层次:

 shop_id  item_id   date_block_num item_cnt_day
target
0   0       30          1            31

我们可以用这个代码:

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

结果是:

 shop_id  item_id   date_block_num target
0   0       30          1            31