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

Dav*_*ard 69 python group-by aggregate-functions pandas

我在使用Pandas的groupby功能时遇到了麻烦.我已经阅读了文档,但是我无法弄清楚如何将聚合函数应用于多个列为这些列提供自定义名称.

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

data.groupby("Country").agg(
        {"column1": {"foo": sum()}, "column2": {"mean": np.mean, "std": np.std}})
Run Code Online (Sandbox Code Playgroud)

(即.我想取column2的mean和std,但将这些列作为"mean"和"std"返回)

我错过了什么?

unu*_*tbu 91

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

df = data.groupby(...).agg(...)
df.columns = df.columns.droplevel(0)
Run Code Online (Sandbox Code Playgroud)

如果您想保留最外层,可以使用多级列上的ravel()函数来形成新标签:

df.columns = ["_".join(x) for x in df.columns.ravel()]
Run Code Online (Sandbox Code Playgroud)

例如:

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())
Run Code Online (Sandbox Code Playgroud)

产量

      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
Run Code Online (Sandbox Code Playgroud)

或者,保持索引的第一级:

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

产量

      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
Run Code Online (Sandbox Code Playgroud)

  • 请注意,在将来的pandas版本中将不推荐使用此语法.详情见[0.20更改日志](http://pandas.pydata.org/pandas-docs/version/0.20/whatsnew.html#deprecate-groupby-agg-with-a-dictionary-when-renaming),其中我在我的回答中总结. (4认同)
  • 可行,但由于其列在级别0中而摆脱了分组列:( (2认同)
  • @Mugen 对于(非常)迟到的回复感到抱歉,但这是一个简单的解决方案,您只需执行 `df.columns = ['_'.join(x) if isinstance(x,tuple) else x for x in df 即可。 columns.ravel()]` 利用了只有聚合列才是元组这一事实,因此如果您的列名称中有其他元组,请在此处谨慎操作。 (2认同)

joe*_*lom 80

unutbu描述的当前接受的答案是在pandas版本<= 0.20中执行此操作的好方法.但是,从pandas 0.20开始,使用此方法会发出警告,指示在将来的pandas版本中将无法使用该语法.

系列:

FutureWarning:不推荐在系列上使用dict进行聚合,并且将在以后的版本中删除

DataFrames:

FutureWarning:不推荐使用带重命名的dict,将在以后的版本中删除

根据pandas 0.20 changelog,聚合时重命名列的推荐方法如下.

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
Run Code Online (Sandbox Code Playgroud)

有关其他详细信息,请参阅0.20更改日志.


更新2017-01-03以回复@ JunkMechanic的评论.

使用旧样式字典语法,可以传递多个.agg(new_col_name=('col_name', 'agg_func')函数.rename(),因为这些函数将使用传递的字典中的键重命名:

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
Run Code Online (Sandbox Code Playgroud)

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

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

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

# 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'})
)
Run Code Online (Sandbox Code Playgroud)

为了避免这种情况.agg,可以先验地定义命名函数而不是使用<lambda>.合适的函数名称也可以避免以后调用SpecificationError数据帧.这些函数可以使用与上面相同的列表语法传递:

>>> 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
Run Code Online (Sandbox Code Playgroud)

  • 他们为什么要放弃`.agg({'B':{'min':lambda x:x.min(),'max':lambda x:x.max()}})`语法?如果仅用于此目的,它似乎非常有用并且不必定义命名函数. (4认同)
  • 感谢更新.我经常使用这种模式:`df = df.groupby('col_to_grpd_by').agg({'quantity':{'mu':lambda series:stats.norm.fit(series)[0],'sigma': lambda series:stats.norm.fit(series)[1],'active':'count',}})`.如何处理这个问题.我能想到的唯一方法是定义2个单独的函数,它们从`stats.norm.fit`返回元组的相应元素.忽略我使用`norm`的事实.它可能是一个不同的分布. (3认同)

小智 6

如果您希望获得类似于JMP的行为,请创建列标题,以保留您可以使用的多索引中的所有信息:

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

它将改变您的数据框架:

    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
Run Code Online (Sandbox Code Playgroud)

    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
Run Code Online (Sandbox Code Playgroud)


use*_*389 6

我同意OP的观点,即在同一位置命名和定义输出列似乎更为自然和一致(例如,就像在R中使用tidyverse所做的summarize那样),但目前Pandas中的一种变通方法是使用进行聚合之前,通过以下所需名称: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()
Run Code Online (Sandbox Code Playgroud)

(使用reset_index'Country''f''mean',和'std'所有与一个单独的整数索引常规列。)