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

rbl8hiat  于 2023-03-06  发布在  其他
关注(0)|答案(4)|浏览(127)

尝试从groupby计算中创建一个新列。在下面的代码中,我得到了每个日期的正确计算值(参见下面的组),但当我尝试创建新列时(df['Data4'])我得到了NaN,所以我尝试在 Dataframe 中创建一个新列,所有日期的总和为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
zvms9eto

zvms9eto1#

你需要使用transform,这将返回一个Series,其索引与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
xtupzzrd

xtupzzrd2#

有两种方法--一种直截了当,另一种稍微有趣一些。

每个人的最爱: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上测试了这个特性)。如果你使用GroupBy的直接函数并使用map广播它,你似乎可以始终如一地节省transform所花费的几毫秒的时间:

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')的输出进行比较:它们是一样的。
我的测试表明,如果你能负担得起直接使用GroupBy函数(如meanminmaxfirst等),map会更快一些。在大多数情况下,大约20万条记录的速度会更快一些。之后,性能就取决于数据了。

(Left:版本0.23,右侧:第0.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
)
mpbci0fu

mpbci0fu3#

一般来说,我建议使用功能更强大的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|
| - ------|- ------|- ------|- ------|- ------|- ------|
| 三个|2015年5月5日|阿普尔|十五|1个|一百二十一|
| 七|2015年5月5日|奥维|四十|一百二十|一百二十一|
| 第二章|2015年5月6日|阿普尔|十个|六个|六十六|
| 六个|2015年5月6日|奥维|一百|六十|六十六|
| 1个|2015年5月7日|阿普尔|八个|八个|一百零八|
| 五个|2015年5月7日|奥维|六十|一百|一百零八|
| 无|2015年5月8日|阿普尔|十一|五个|五十五|
| 四个|2015年5月8日|奥维|一百一十|五十|五十五|

  • (为什么python表达式要放在圆括号中?这样我们就不需要在代码中到处都是反斜杠,而且我们可以在表达式代码中添加注解来描述每一步。)*

它的强大之处在于它充分利用了"拆分-应用-组合"范式的全部力量。它允许您从"将 Dataframe 拆分为块"和"在这些块上运行任意操作"的Angular 进行思考,而无需减少/聚合,即无需减少行数。(而且不需要编写显式的冗长循环,也不需要使用代价高昂的连接或串联来将结果粘回去。)
让我们考虑一个更复杂的例子。在这个例子中,您的 Dataframe 中有多个时间序列的数据。您有一列表示一种产品,一列有时间戳,还有一列包含该产品在一年中某个时间的销售数量。您希望按产品分组并获得一个新列。它包含每个类别中销售的商品的累计总数。我们需要一个列,该列在具有相同产品的每个"块"内仍然是时间序列,并且单调递增(仅在块内)。
我们如何才能做到这一点?用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)
)

输出:
| | 日期|产品|售出商品|累计销售项目|
| - ------|- ------|- ------|- ------|- ------|
| 无|2021年3月11日|衬衫|三百|三百|
| 1个|2021年3月12日|衬衫|四百|七百|
| 第二章|2021年3月13日|衬衫|二百三十四|九三四|
| 三个|2021年3月11日|鞋|八十|八十|
| 四个|2021年3月12日|鞋|十个|九十|
| 五个|2021年3月13日|鞋|一百二十|二一零|
此方法的另一个优点是:即使必须按多个字段分组,此方法也有效!例如,如果我们的产品有一个'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

输出:
| | 日期|产品|颜色|售出商品|累计销售项目|
| - ------|- ------|- ------|- ------|- ------|- ------|
| 六个|2021年3月11日|衬衫|蓝色|一百二十三|一百二十三|
| 七|2021年3月12日|衬衫|蓝色|八十四|二百零七|
| 八个|2021年3月13日|衬衫|蓝色|九二三|小行星|
| 无|2021年3月11日|衬衫|黄色|三百|三百|
| 1个|2021年3月12日|衬衫|黄色|四百|七百|
| 第二章|2021年3月13日|衬衫|黄色|二百三十四|九三四|
| 九|2021年3月11日|鞋|蓝色|无|无|
| 十个|2021年3月12日|鞋|蓝色|二百二十|二百二十|
| 十一|2021年3月13日|鞋|蓝色|九十四|三一四|
| 三个|2021年3月11日|鞋|黄色|八十|八十|
| 四个|2021年3月12日|鞋|黄色|十个|九十|
| 五个|2021年3月13日|鞋|黄色|一百二十|二一零|

  • (这种容易扩展到对多个字段进行分组的可能性就是我喜欢将groupby的参数始终放在列表中的原因,即使它是一个名称,如上一个示例中的'Product'。)*

你可以在一个表达式中综合地完成所有这些(当然,如果python的lambdas看起来更漂亮一点,它会看起来更漂亮)。
为什么我要过一遍一般的案例呢?因为这是在谷歌上搜索"Pandas新专栏groupby"之类的东西时弹出的第一个so问题之一。

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

基于对组进行的任意计算添加列很像defining new column using aggregations over Windows in SparkSQL的好习惯用法。
例如,你可以这样想(这是Scala代码,但PySpark中的等价代码看起来几乎是一样的):

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

就像这样(用我们上面看到的Pandas的例子):

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的例子是多么的合成和漂亮。panda的等价物看起来有点笨拙。panda的API并没有使编写这种"流畅"的操作变得容易。)
这个习惯用法又来自SQL's Window Functions,PostgreSQL文档对此给出了一个非常好的定义:(着重号是我的)

窗口函数对一组表行执行计算**,这些表行以某种方式与当前行相关**。这与聚合函数可以完成的计算类型类似。但与常规聚合函数不同,使用窗口函数不会导致行被分组到单个输出行中-这些行保留其单独的标识。在幕后,窗口函数能够访问的不仅仅是查询结果的当前行。
并给出了一个漂亮的SQL一行代码示例:(组内排名)

SELECT depname, empno, salary, rank() OVER (PARTITION BY depname ORDER BY salary DESC) FROM empsalary;

| 部门名称|恩普诺|薪金|秩|
| - ------|- ------|- ------|- ------|
| 发展|八个|六千|1个|
| 发展|十个|小行星5200|第二章|
| 发展|十一|小行星5200|第二章|
| 发展|九|小行星4500|四个|
| 发展|七|小行星4200|五个|
| 人事|第二章|小行星3900|1个|
| 人事|五个|3500|第二章|
| 销售额|1个|五千|1个|
| 销售额|四个|小行星4800|第二章|
| 销售额|三个|小行星4800|第二章|
最后一件事:你可能也会对panda的pipe感兴趣,它类似于apply,但工作原理稍有不同,并且给了内部操作更大的工作范围。

7rtdyuoh

7rtdyuoh4#

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

相关问题