如何从 pandas groupby().sum() 的输出中创建新列?

2024-11-26 08:36:00
admin
原创
163
摘要:问题描述:尝试通过计算创建新列groupby。在下面的代码中,我获得了每个日期的正确计算值(参见下面的组),但当我尝试df['Data4']用它创建新列()时,我得到了 NaN。因此,我尝试在数据框中创建一个新列,其中包含Data3所有日期的总和,并将其应用于每个日期行。例如,2015-05-08 有 2 行...

问题描述:

尝试通过计算创建新列groupby。在下面的代码中,我获得了每个日期的正确计算值(参见下面的组),但当我尝试df['Data4']用它创建新列()时,我得到了 NaN。因此,我尝试在数据框中创建一个新列,其中包含Data3所有日期的总和,并将其应用于每个日期行。例如,2015-05-08 有 2 行(总计为 50+5 = 55),在这个新列中,我希望两行都包含 55。

import pandas as pd

df = pd.DataFrame({
    'Date' : ['2015-05-08', '2015-05-07', '2015-05-06', '2015-05-05', '2015-05-08', '2015-05-07', '2015-05-06', '2015-05-05'], 
    'Sym'  : ['aapl', 'aapl', 'aapl', 'aapl', 'aaww', 'aaww', 'aaww', 'aaww'], 
    'Data2': [11, 8, 10, 15, 110, 60, 100, 40],
    'Data3': [5, 8, 6, 1, 50, 100, 60, 120]
})

group = df['Data3'].groupby(df['Date']).sum()

df['Data4'] = group

group

Date
2015-05-05    121
2015-05-06     66
2015-05-07    108
2015-05-08     55
Name: Data3, dtype: int64

df在最后:

         Date   Sym  Data2  Data3  Data4
0  2015-05-08  aapl     11      5    NaN
1  2015-05-07  aapl      8      8    NaN
2  2015-05-06  aapl     10      6    NaN
3  2015-05-05  aapl     15      1    NaN
4  2015-05-08  aaww    110     50    NaN
5  2015-05-07  aaww     60    100    NaN
6  2015-05-06  aaww    100     60    NaN
7  2015-05-05  aaww     40    120    NaN

解决方案 1:

您想使用transform。这将返回一个与 df 对齐的索引的系列,因此您可以将其添加为新列:

df = pd.DataFrame({
    'Date': ['2015-05-08', '2015-05-07', '2015-05-06', '2015-05-05',
             '2015-05-08', '2015-05-07', '2015-05-06', '2015-05-05'],
    'Sym': ['aapl', 'aapl', 'aapl', 'aapl', 'aaww', 'aaww', 'aaww', 'aaww'],
    'Data2': [11, 8, 10, 15, 110, 60, 100, 40],
    'Data3': [5, 8, 6, 1, 50, 100, 60, 120]
})
​
df['Data4'] = df['Data3'].groupby(df['Date']).transform('sum')
df
         Date   Sym  Data2  Data3  Data4
0  2015-05-08  aapl     11      5     55
1  2015-05-07  aapl      8      8    108
2  2015-05-06  aapl     10      6     66
3  2015-05-05  aapl     15      1    121
4  2015-05-08  aaww    110     50     55
5  2015-05-07  aaww     60    100    108
6  2015-05-06  aaww    100     60     66
7  2015-05-05  aaww     40    120    121

解决方案 2:

如何从 pandas groupby().sum() 的输出中创建新列?

有两种方法——一种方法很简单,另一种方法稍微有趣一些。


大家最喜欢的GroupBy.transform()'sum'

@EdChum 的答案可以稍微简化一下。 调用DataFrame.groupby而不是Series.groupby。 这样语法更简单。

df.groupby('Date')['Data3'].transform('sum')

0     55
1    108
2     66
3    121
4     55
5    108
6     66
7    121
Name: Data3, dtype: int64 

速度稍微快了一点,

df2 = pd.concat([df] * 12345)

%timeit df2['Data3'].groupby(df['Date']).transform('sum')
%timeit df2.groupby('Date')['Data3'].transform('sum')

10.4 ms ± 367 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
8.58 ms ± 559 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

非常规,但值得考虑:GroupBy.sum()+Series.map()

我偶然发现了 API 中的一个有趣的特性。据我所知,你可以在 0.20 以上的任何主要版本上重现此问题(我在 0.23 和 0.24 上测试过)。transform如果你改用直接函数GroupBy并使用 进行广播,似乎可以节省几毫秒的时间map

df['Date'].map(df.groupby('Date')['Data3'].sum())

0     55
1    108
2     66
3    121
4     55
5    108
6     66
7    121
Name: Date, dtype: int64

与上面的输出进行比较df.groupby('Date')['Data3'].transform('sum'):它们是相同的。

我的测试表明,如果您可以使用直接函数(例如,,,,等map),速度会更快一些GroupBymean对于大多数一般情况,速度会更快,最多约 20 万条记录。在此之后,性能实际上取决于数据。min`max`first

IT科技 IT科技

(左:v0.23,右:v0.24)

这是一个不错的选择,如果你的框架较小,组数也较少,那就更好了,但我建议将transform其作为首选。无论如何,我觉得这值得分享。

基准测试代码,供参考:

import perfplot

perfplot.show(
    setup=lambda n: pd.DataFrame({'A': np.random.choice(n//10, n), 'B': np.ones(n)}),
    kernels=[
        lambda df: df.groupby('A')['B'].transform('sum'),
        lambda df:  df.A.map(df.groupby('A')['B'].sum()),
    ],
    labels=['GroupBy.transform', 'GroupBy.sum + map'],
    n_range=[2**k for k in range(5, 20)],
    xlabel='N',
    logy=True,
    logx=True
)

解决方案 3:

我通常建议使用功能更强大的apply,您可以用单个表达式编写查询,甚至可以用于更复杂的用途,例如定义一个新列,其值定义为对组的操作,并且同一组内也可以有不同的值!

这比为每个组定义一个具有相同值的列的简单情况更为通用(就像sum在这个问题中,它因组而异,但在同一组内是相同的)。

简单情况(组内新列的值相同,组间新列的值不同):

# I'm assuming the name of your dataframe is something long, like
# `my_data_frame`, to show the power of being able to write your
# data processing in a single expression without multiple statements and
# multiple references to your long name, which is the normal style
# that the pandas API naturally makes you adopt, but which make the
# code often verbose, sparse, and a pain to generalize or refactor

my_data_frame = pd.DataFrame({
    'Date': ['2015-05-08', '2015-05-07', '2015-05-06', '2015-05-05', '2015-05-08', '2015-05-07', '2015-05-06', '2015-05-05'], 
    'Sym': ['aapl', 'aapl', 'aapl', 'aapl', 'aaww', 'aaww', 'aaww', 'aaww'], 
    'Data2': [11, 8, 10, 15, 110, 60, 100, 40],
    'Data3': [5, 8, 6, 1, 50, 100, 60, 120]})
​
(my_data_frame
    # create groups by 'Date'
    .groupby(['Date'])
    # for every small Group DataFrame `gdf` with the same 'Date', do:
    # assign a new column 'Data4' to it, with the value being
    # the sum of 'Data3' for the small dataframe `gdf`
    .apply(lambda gdf: gdf.assign(Data4=lambda gdf: gdf['Data3'].sum()))
    # after groupby operations, the variable(s) you grouped by on
    # are set as indices. In this case, 'Date' was set as an additional
    # level for the (multi)index. But it is still also present as a
    # column. Thus, we drop it from the index:
    .droplevel(0)
)

### OR

# We don't even need to define a variable for our dataframe.
# We can chain everything in one expression

(pd
    .DataFrame({
        'Date': ['2015-05-08', '2015-05-07', '2015-05-06', '2015-05-05', '2015-05-08', '2015-05-07', '2015-05-06', '2015-05-05'], 
        'Sym': ['aapl', 'aapl', 'aapl', 'aapl', 'aaww', 'aaww', 'aaww', 'aaww'], 
        'Data2': [11, 8, 10, 15, 110, 60, 100, 40],
        'Data3': [5, 8, 6, 1, 50, 100, 60, 120]})
    .groupby(['Date'])
    .apply(lambda gdf: gdf.assign(Data4=lambda gdf: gdf['Data3'].sum()))
    .droplevel(0)
)

出去:

日期对称数据2数据3数据4
32015-05-05苹果151121
72015-05-05啊啊啊40120121
22015-05-06苹果10666
62015-05-06啊啊啊1006066
12015-05-07苹果88108
52015-05-07啊啊啊60100108
02015-05-08苹果11555
42015-05-08啊啊啊1105055

(为什么 Python 表达式在括号内?这样我们就不需要在代码中到处添加反斜杠,并且我们可以在表达式代码中添加注释来描述每个步骤。)

它有什么强大的功能?它充分利用了“拆分-应用-合并”范式的全部功能。它允许您以“将数据框拆分为块”和“在这些块上运行任意操作”的方式思考,而无需减少/聚合,即无需减少行数。(并且无需编写显式、冗长的循环并诉诸昂贵的连接或串联来将结果粘合回去。)

让我们考虑一个更复杂的例子。您的数据框中有多个时间序列数据。您有一列表示一种产品,一列包含时间戳,还有一列包含该产品在一年中某个时间售出的商品数量。您希望按产品分组并获得一个新列,其中包含每个类别售出的商品的累计总数。我们希望每列在包含相同产品的每个“块”内仍然是一个时间序列,并且单调递增(仅在一个块内)。

我们该怎么做呢?用groupby+ apply

(pd
     .DataFrame({
        'Date': ['2021-03-11','2021-03-12','2021-03-13','2021-03-11','2021-03-12','2021-03-13'], 
        'Product': ['shirt','shirt','shirt','shoes','shoes','shoes'], 
        'ItemsSold': [300, 400, 234, 80, 10, 120],
        })
    .groupby(['Product'])
    .apply(lambda gdf: (gdf
        # sort by date within a group
        .sort_values('Date')
        # create new column
        .assign(CumulativeItemsSold=lambda df: df['ItemsSold'].cumsum())))
    .droplevel(0)
)

出去:

日期产品已售商品累计售出商品
02021-03-11衬衫300300
12021-03-12衬衫400700
22021-03-13衬衫234934
32021-03-118080
42021-03-121090
52021-03-13120210

这种方法的另一个优点是,即使我们必须按多个字段分组,它也能正常工作!例如,如果我们有一个'Color'产品字段,并且我们希望按累计系列分组(Product, Color),我们可以:

(pd
     .DataFrame({
        'Date': ['2021-03-11','2021-03-12','2021-03-13','2021-03-11','2021-03-12','2021-03-13',
                 '2021-03-11','2021-03-12','2021-03-13','2021-03-11','2021-03-12','2021-03-13'], 
        'Product': ['shirt','shirt','shirt','shoes','shoes','shoes',
                    'shirt','shirt','shirt','shoes','shoes','shoes'], 
        'Color': ['yellow','yellow','yellow','yellow','yellow','yellow',
                  'blue','blue','blue','blue','blue','blue'], # new!
        'ItemsSold': [300, 400, 234, 80, 10, 120,
                      123, 84, 923, 0, 220, 94],
        })
    .groupby(['Product', 'Color']) # We group by 2 fields now
    .apply(lambda gdf: (gdf
        .sort_values('Date')
        .assign(CumulativeItemsSold=lambda df: df['ItemsSold'].cumsum())))
    .droplevel([0,1]) # We drop 2 levels now

出去:

日期产品颜色已售商品累计售出商品
62021-03-11衬衫蓝色的123123
72021-03-12衬衫蓝色的84207
82021-03-13衬衫蓝色的9231130
02021-03-11衬衫黄色的300300
12021-03-12衬衫黄色的400700
22021-03-13衬衫黄色的234934
92021-03-11蓝色的00
102021-03-12蓝色的220220
112021-03-13蓝色的94314
32021-03-11黄色的8080
42021-03-12黄色的1090
52021-03-13黄色的120210

(这种轻松扩展到多个字段分组的可能性就是我喜欢将参数放在列表中的原因groupby,即使它是一个名称,如前面例子中的“产品”。)

而且,您可以在一个表达式中综合完成所有这些操作。(当然,如果 Python 的 lambda 看起来更美观一些,它会看起来更美观。)


我为什么要讨论一般情况?因为这是在谷歌搜索“pandas new column groupby”等内容时弹出的第一个 SO 问题之一。


关于此类操作的 API 的其他想法

根据对组进行的任意计算添加列非常类似于在 SparkSQL 中使用 Windows 上的聚合定义新列的优雅用法。

例如,你可以想到这个(这是 Scala 代码,但 PySpark 中的等效代码看起来实际上相同):

val byDepName = Window.partitionBy('depName)
empsalary.withColumn("avg", avg('salary) over byDepName)

类似于(以我们上面看到的方式使用熊猫):

empsalary = pd.DataFrame(...some dataframe...)
(empsalary
    # our `Window.partitionBy('depName)`
    .groupby(['depName'])
    # our 'withColumn("avg", avg('salary) over byDepName)
    .apply(lambda gdf: gdf.assign(avg=lambda df: df['salary'].mean()))
    .droplevel(0)
)

(请注意 Spark 示例是多么的综合和优秀。pandas 等效示例看起来有点笨重。pandas API 并不容易编写这些“流畅”的操作)。

这个习语来自SQL 的窗口函数,PostgreSQL 文档对其给出了非常好的定义:(重点是我的)

窗口函数对与当前行有某种关联的一组表行执行计算。这与使用聚合函数可以执行的计算类型相当。但与常规聚合函数不同,使用窗口函数不会导致行分组为单个输出行 -行保留其单独的标识。在后台,窗口函数能够访问的不仅仅是查询结果的当前行。

并给出了一个漂亮的 SQL 单行示例:(组内排名)

SELECT depname, empno, salary, rank() OVER (PARTITION BY depname ORDER BY salary DESC) FROM empsalary;
部门名称员工编号薪水
发展860001
发展1052002
发展1152002
发展945004
发展742005
人员239001
人员535002
销售量150001
销售量448002
销售量348002

最后一件事:您可能还对 pandas 感兴趣pipe,它与 类似apply,但工作方式略有不同,并为内部操作提供了更大的工作范围。有关更多信息,请参见此处

解决方案 4:

df = pd.DataFrame({
'Date' : ['2015-05-08', '2015-05-07', '2015-05-06', '2015-05-05', '2015-05-08', '2015-05-07', '2015-05-06', '2015-05-05'], 
'Sym'  : ['aapl', 'aapl', 'aapl', 'aapl', 'aaww', 'aaww', 'aaww', 'aaww'], 
'Data2': [11, 8, 10, 15, 110, 60, 100, 40],
'Data3': [5, 8, 6, 1, 50, 100, 60, 120]
})
print(pd.pivot_table(data=df,index='Date',columns='Sym',     aggfunc={'Data2':'sum','Data3':'sum'}))

输出

Data2      Data3     
Sym         aapl aaww  aapl aaww
Date                            
2015-05-05    15   40     1  120
2015-05-06    10  100     6   60
2015-05-07     8   60     8  100
2015-05-08    11  110     5   50
相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   1579  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1355  
  信创产品在政府采购中的占比分析随着信息技术的飞速发展以及国家对信息安全重视程度的不断提高,信创产业应运而生并迅速崛起。信创,即信息技术应用创新,旨在实现信息技术领域的自主可控,减少对国外技术的依赖,保障国家信息安全。政府采购作为推动信创产业发展的重要力量,其对信创产品的采购占比情况备受关注。这不仅关系到信创产业的发展前...
信创和国产化的区别   8  
  信创,即信息技术应用创新产业,旨在实现信息技术领域的自主可控,摆脱对国外技术的依赖。近年来,国货国用信创发展势头迅猛,在诸多领域取得了显著成果。这一发展趋势对科技创新产生了深远的推动作用,不仅提升了我国在信息技术领域的自主创新能力,还为经济社会的数字化转型提供了坚实支撑。信创推动核心技术突破信创产业的发展促使企业和科研...
信创工作   9  
  信创技术,即信息技术应用创新产业,旨在实现信息技术领域的自主可控与安全可靠。近年来,信创技术发展迅猛,对中小企业产生了深远的影响,带来了诸多不可忽视的价值。在数字化转型的浪潮中,中小企业面临着激烈的市场竞争和复杂多变的环境,信创技术的出现为它们提供了新的发展机遇和支撑。信创技术对中小企业的影响技术架构变革信创技术促使中...
信创国产化   8  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用