Python 在聚合函数中命名返回的列?

Python 在聚合函数中命名返回的列?,python,group-by,pandas,aggregate-functions,Python,Group By,Pandas,Aggregate Functions,熊猫的groupby功能有问题。我已经读过了,但我看不出如何将聚合函数应用于多个列,并为这些列指定自定义名称 这非常接近,但返回的数据结构具有嵌套的列标题: data.groupby("Country").agg( {"column1": {"foo": sum()}, "column2": {"mean": np.mean, "std": np.std}}) (即,我想取第2列的平均值和标准值,但将这些列作为“平均值”和“标准值”返回) 我缺少什么?这将从分层列索引中删除最外

熊猫的groupby功能有问题。我已经读过了,但我看不出如何将聚合函数应用于多个列,并为这些列指定自定义名称

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

data.groupby("Country").agg(
        {"column1": {"foo": sum()}, "column2": {"mean": np.mean, "std": np.std}})
(即,我想取第2列的平均值和标准值,但将这些列作为“平均值”和“标准值”返回)


我缺少什么?

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

df = data.groupby(...).agg(...)
df.columns = df.columns.droplevel(0)
如果要保留最外层,可以使用“多级”列上的ravel()函数来形成新标签:

df.columns = ["_".join(x) for x in df.columns.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
      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
或者,要保持索引的第一级:

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

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

对于熊猫>=0.25 为返回的聚合列命名的功能已在0.25中实现,并且是针对0.25的。新语法是
.agg(new_col_name=('col_name','agg_func')
。上面链接的PR的详细示例:

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表达式,并使用此语法和我先前(以下)建议的两步重命名语法。同样,从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

对于小于0.25的大熊猫
unutbu目前接受的答案描述了在@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的观点,即在同一位置命名和定义输出列似乎更自然、更一致(例如,与所做的一样),但pandas目前的一个解决方法是在进行聚合之前通过以下方式创建具有所需名称的新列:

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

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

如这种数据框,列名有两个级别:

 shop_id  item_id   date_block_num item_cnt_day       
                                  target              
0   0       30          1            31               
我们可以使用以下代码:

df.columns=[col[0]如果col[-1]='',则为df.columns.values中的col[-1]

结果是:

 shop_id  item_id   date_block_num target              
0   0       30          1            31 

您好@david_chouinard,我也有同样的问题。您是否认为建议的解决方案在pandas 17.1的今天仍然是最好的?工作正常,但去掉了“按列分组”,因为它处于0级:(请注意,此语法将在pandas的未来版本中被弃用。详细信息见,我在回答中对其进行了总结。@Mugen很抱歉(非常)延迟响应,但这是一个简单的修复方法,您只需执行
df.columns=[''''.'.join(x)if is instance(x,tuple)else x for x in df.columns.ravel()]
它利用了只有聚合列才是元组这一事实,因此如果列名中有其他元组,请在此处谨慎。感谢更新。我经常使用这种模式:
df=df.groupby('col_to_grpd_by').agg({'quantity':{'mu':lambda series:stats.norm.fit(series)[0],'sigma':lambda系列:stats.norm.fit(系列)[1],'active':'count',})
。这将如何处理。我能想到的唯一方法是定义两个单独的函数,从
stats.norm.fit
返回元组的相应元素。忽略我使用
norm
。这可能是一个不同的发行版。@JunkMechanic我相信您需要先定义它们,然后然后将它们作为列表传递。我用一个例子更新了答案。为什么他们要取消
.agg({'B':{'min':lambda x:x.min(),'max':lambda x:x.max()}})
语法?它似乎非常有用,而且比仅用于此目的而必须定义命名函数要简单。@sheridp来自上面链接的0.20 changelog:“然而,.agg”(..)还可以接受允许对结果列进行“重命名”的dict。这是一种复杂而混乱的语法,并且Series和DataFrame之间不一致。我们不赞成这种“重命名”功能。”@JoelOstblom已经运行了您的示例,我正在努力使用语法来访问分组数据帧。我将使用什么语法来通过分组值访问数据帧?类似于df['A']来列出[1,2]。我将使用什么语法来绘制'foo'与'A'?类似于df.plot('A','foo'))。如果没有第二行,您也可以添加一个条件检查以获得相同的列名`if n2='':new\u col\u name.append(“%s”%n1)else:new\u col\u name.append(“%s\u%s”%(n1,n2))`我最喜欢这个方法。只需稍加修改,我就得到了以下内容:[col[0]if col[-1]=''else col[-1]+'+col[0]对于路径中的列\u info.columns.values]
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'}
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()
 shop_id  item_id   date_block_num item_cnt_day       
                                  target              
0   0       30          1            31               
 shop_id  item_id   date_block_num target              
0   0       30          1            31