【问题标题】:Naming returned columns in Pandas aggregate function? [duplicate]在 Pandas 聚合函数中命名返回的列? [复制]
【发布时间】:2013-10-05 09:04:25
【问题描述】:

我在使用 Pandas 的 groupby 功能时遇到问题。我已经阅读了the documentation,但我无法弄清楚如何将聚合函数应用于多个列并且这些列具有自定义名称。

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

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

(即我想取 column2 的均值和标准差,但将这些列返回为“均值”和“标准差”)

我错过了什么?

【问题讨论】:

  • 嗨@david_chouinard 实际上我也有同样的问题。您认为目前建议的解决方案对于 pandas 17.1 来说仍然是最好的吗?

标签: python group-by pandas aggregate-functions


【解决方案1】:

对于熊猫 >= 0.25

命名返回的聚合列的功能是 reintroduced in the master branch 并且针对 pandas 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

根据this PR,还可以使用此语法和我之前建议的两步重命名语法(如下)使用多个 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

然后.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

对于熊猫

unutbu 描述的当前接受的答案是在熊猫版本

系列:

FutureWarning:不推荐在 Series 上使用 dict 进行聚合,并将在未来的版本中删除

数据帧:

FutureWarning:不推荐使用带有重命名的字典,并将在未来的版本中删除

根据pandas 0.20 changelog,建议在聚合时重命名列的方式如下。

# 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 changelog 了解更多详情。

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

使用旧式字典语法,可以将多个 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 函数,因为它们是匿名的并且都返回 &lt;lambda&gt;,这会导致名称冲突:

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

【讨论】:

  • 感谢您的更新。我经常使用这种模式: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 的事实。它可能是不同的分布。
  • @JunkMechanic 我相信您需要先定义它们,然后将它们作为列表传递。我用一个例子更新了答案。
  • 他们为什么要取消 .agg({'B': {'min': lambda x: x.min(), 'max': lambda x: x.max()}}) 语法?如果仅用于此目的,它似乎比必须定义命名函数非常有用且不那么混乱。
  • @sheridp 来自上面链接的 0.20 变更日志:“但是,.agg(..) 也可以接受允许“重命名”结果列的字典。这是一个复杂且令人困惑的语法,因为Series 和 DataFrame 之间也不一致。我们正在弃用这种“重命名”功能。”
  • @JoelOstblom 运行您的示例后,我正在努力使用访问分组数据框的语法。我将使用什么语法按分组值访问数据框?像 df['A'] 列出 [1,2] 之类的东西。我将使用什么语法来绘制“foo”与“A”?像 df.plot('A','foo') 之类的东西。
【解决方案2】:

比如这种dataframe,列名有两层:

 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 

【讨论】:

  • 我最喜欢这种方法。稍作修改,我得到以下内容:[col[0] if col[-1]=='' else col[-1]+'_'+col[0] for col in path_info.columns.values]跨度>
【解决方案3】:

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

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

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

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

【讨论】:

  • 工作,但摆脱了按列分组,因为它在级别 0 :(
  • 请注意,此语法将在未来版本的 pandas 中被弃用。详细信息在0.20 changelog 中,我在回答中进行了总结。
  • @Mugen 对(非常)迟到的回复感到抱歉,但这是一个简单的解决方法,您可以使用 df.columns = ['_'.join(x) if isinstance(x,tuple) else x for x in df.columns.ravel()] 这样做,它利用了只有聚合列将是元组这一事实,所以如果您的列名中有其他元组,请在此处谨慎行事。
【解决方案4】:

我同意 OP 的观点,在同一位置命名和定义输出列似乎更自然和一致(例如,与 tidyverse's summarize in R 一样),但目前在 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()

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

【讨论】:

    【解决方案5】:

    受@Joel Ostblom 的启发

    对于那些已经有一个仅用于聚合的可用字典的人,您可以使用/修改以下代码用于较新版本的聚合,分离聚合和重命名部分。如果超过 1 项,请注意嵌套字典。

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

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

    【讨论】:

    • 似乎是 4 年后唯一可行的解​​决方案。
    • 或者这也应该工作df.columns = ['_'.join(a) for a in df.columns.to_flat_index()]
    【解决方案6】:

    如果您希望具有类似于 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
    

    【讨论】:

    • 如果没有第二行,也可以添加条件检查来获取相同的列名` if n2 == '': new_col_name.append("%s" % n1) else: new_col_name. append("%s_%s" % (n1, n2))`
    猜你喜欢
    • 2021-12-29
    • 2020-05-01
    • 1970-01-01
    • 2021-10-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-10-16
    相关资源
    最近更新 更多