Python Pandas如何将groupby操作结果分配回父数据帧中的列?


81

我在IPython中具有以下数据框,其中每一行都是一只股票:

In [261]: bdata
Out[261]:
<class 'pandas.core.frame.DataFrame'>
Int64Index: 21210 entries, 0 to 21209
Data columns:
BloombergTicker      21206  non-null values
Company              21210  non-null values
Country              21210  non-null values
MarketCap            21210  non-null values
PriceReturn          21210  non-null values
SEDOL                21210  non-null values
yearmonth            21210  non-null values
dtypes: float64(2), int64(1), object(4)

我想应用一个groupby操作,计算“ yearmonth”列中每个日期的所有内容的上限加权平均回报。

这按预期工作:

In [262]: bdata.groupby("yearmonth").apply(lambda x: (x["PriceReturn"]*x["MarketCap"]/x["MarketCap"].sum()).sum())
Out[262]:
yearmonth
201204      -0.109444
201205      -0.290546

但是,然后我想将这些值“广播”回原始数据帧中的索引,并将它们保存为日期匹配的常量列。

In [263]: dateGrps = bdata.groupby("yearmonth")

In [264]: dateGrps["MarketReturn"] = dateGrps.apply(lambda x: (x["PriceReturn"]*x["MarketCap"]/x["MarketCap"].sum()).sum())
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
/mnt/bos-devrnd04/usr6/home/espears/ws/Research/Projects/python-util/src/util/<ipython-input-264-4a68c8782426> in <module>()
----> 1 dateGrps["MarketReturn"] = dateGrps.apply(lambda x: (x["PriceReturn"]*x["MarketCap"]/x["MarketCap"].sum()).sum())

TypeError: 'DataFrameGroupBy' object does not support item assignment

我意识到这种天真的任务不起作用。但是,将groupby操作的结果分配给父数据帧上新列的“正确” Pandas习惯用法是什么?

最后,我希望有一个名为“ MarketReturn”的列,该列将是与groupby操作的输出具有匹配日期的所有索引的重复常数值。

实现这一目标的一种方法是:

marketRetsByDate  = dateGrps.apply(lambda x: (x["PriceReturn"]*x["MarketCap"]/x["MarketCap"].sum()).sum())

bdata["MarketReturn"] = np.repeat(np.NaN, len(bdata))

for elem in marketRetsByDate.index.values:
    bdata["MarketReturn"][bdata["yearmonth"]==elem] = marketRetsByDate.ix[elem]

但这是缓慢,糟糕且不符合Python规范的。


您将分配回您的分组对象,而不是原始框架。
Wouter Overmeire

2
我知道这一点,并在错误的正下方这么说:“我意识到这种天真的分配不起作用。但是,将groupby操作的结果分配给父级的新列的“正确”的Pandas惯用法是什么?数据框?” 用我在LHS上的原始数据框进行分配也不起作用,甚至不如在GroupBy对象级别添加列那样直观。
2012年

Answers:


73
In [97]: df = pandas.DataFrame({'month': np.random.randint(0,11, 100), 'A': np.random.randn(100), 'B': np.random.randn(100)})

In [98]: df.join(df.groupby('month')['A'].sum(), on='month', rsuffix='_r')
Out[98]:
           A         B  month       A_r
0  -0.040710  0.182269      0 -0.331816
1  -0.004867  0.642243      1  2.448232
2  -0.162191  0.442338      4  2.045909
3  -0.979875  1.367018      5 -2.736399
4  -1.126198  0.338946      5 -2.736399
5  -0.992209 -1.343258      1  2.448232
6  -1.450310  0.021290      0 -0.331816
7  -0.675345 -1.359915      9  2.722156

这仍然需要我省去groupby计算,而不是直接在执行groupby操作的那一行上的LHS上进行分配。Apply可能比问题底部的hack循环要好一些,但是它们基本上是相同的想法。
2012年

联接可以做到这一点,但是您需要重命名添加的列。在这种情况下,A_r是new_col。
Wouter Overmeire 2012年

底部的连接示例确实起作用,但是并没有清楚地显示出来。如果您想删除答案的第一部分,并让其后半部分更清楚一点,除了接受之外,我还会投票。
2012年

12
我删除了第一种方法。老实说,我觉得代码本身就说明了一切,如果您想对文档添加一些解释或参考,请随时进行编辑。我不太喜欢投票系统,只是在这里有点支持熊猫。
Wouter Overmeire'9

1
我花了很长时间寻找这个答案,有点死尸了,但是谢谢!+1
丹·卡特

50

虽然我仍在探索所有巧妙的方式来apply串联给出的片段,但这是在groupby操作之后在父级中添加新列的另一种方式。

In [236]: df
Out[236]: 
  yearmonth    return
0    201202  0.922132
1    201202  0.220270
2    201202  0.228856
3    201203  0.277170
4    201203  0.747347

In [237]: def add_mkt_return(grp):
   .....:     grp['mkt_return'] = grp['return'].sum()
   .....:     return grp
   .....: 

In [238]: df.groupby('yearmonth').apply(add_mkt_return)
Out[238]: 
  yearmonth    return  mkt_return
0    201202  0.922132    1.371258
1    201202  0.220270    1.371258
2    201202  0.228856    1.371258
3    201203  0.277170    1.024516
4    201203  0.747347    1.024516

您也可以执行此操作而无需使用lambda定义函数并分配:df.groupby('yearmonth').apply(lambda grp: grp.assign(mkt_return=grp['return'].sum()))
krassowski,

30

通常,使用groupby()时,如果使用.transform()函数,pandas将返回与原始表相同长度的表。当您使用.sum()或.first()之类的其他函数时,pandas将返回一个表格,其中每一行都是一组。

我不确定应用程序如何工作,但通过转换实现复杂的lambda函数可能会非常棘手,因此我发现最有帮助的策略是创建所需的变量,将其放在原始数据集中,然后在此处进行操作。

如果我先了解您要正确执行的操作,则可以计算每个组的总市值:

bdata['group_MarketCap'] = bdata.groupby('yearmonth')['MarketCap'].transform('sum')

这将在原始数据中添加一列“ group_MarketCap”,其中将包含每个组的市值之和。然后,您可以直接计算加权值:

bdata['weighted_P'] = bdata['PriceReturn'] * (bdata['MarketCap']/bdata['group_MarketCap'])

最后,您将使用相同的变换函数计算每个组的加权平均值:

bdata['MarketReturn'] = bdata.groupby('yearmonth')['weighted_P'].transform('sum')

我倾向于以此方式构建变量。有时您可以将所有内容放到一个命令中,但这并不总是与groupby()一起使用,因为大多数时候,熊猫需要实例化新对象才能在整个数据集范围内对其进行操作(即,您不能如果尚不存在,则将两列加在一起)。

希望这可以帮助 :)


23

我可以建议使用该transform方法(而不是合计)吗?如果您在原始示例中使用它,它应该做您想要的(广播)。


我的理解是,转换产生的对象看起来像它传递的对象。因此,如果您转换DataFrame,则不仅要返回一列,还需要返回DataFrame。而就我而言,我想将新结果附加到原始数据框。或者您是在说我应该编写一个单独的函数,该函数采用一个数据帧,计算新列,然后追加新列,然后使用该函数进行转换?
2012年

2
我同意,变换是更好的选择,df ['A-month-sum'] = df.groupby('month')['A']。transform(sum)
Wouter Overmeire 2012年

但是为什么会更好呢?一样吗,不是吗?它更快吗?
K.-Michael Aye

1
恕我直言,transform看起来更干净。我没有EMS数据来确认这一点,但这可能有用(尽管可能需要修改lambda函数):bdata['mkt_return'] = bdata.groupby("yearmonth").transform(lambda x: (x["PriceReturn"]*x["MarketCap"]/x["MarketCap"].sum()).sum())
cd98 2013年

1
如果我错了,请纠正我,transform不允许在之后对多列进行操作groupby,例如df.groupby('col_3')[['col_1','col_2']].transform(lambda x: ((1-x.col_1.mean()) - x.col_2.std()))将抛出错误,抱怨“无属性XXX”
Jason Goal

0

我没有找到分配原始数据帧的方法。因此,我只存储组中的结果并将它们连接起来。然后,我们按索引对连接的数据帧进行排序,以将原始顺序作为输入数据帧。这是一个示例代码:

In [10]: df = pd.DataFrame({'month': np.random.randint(0,11, 100), 'A': np.random.randn(100), 'B': np.random.randn(100)})

In [11]: df.head()
Out[11]:
   month         A         B
0      4 -0.029106 -0.904648
1      2 -2.724073  0.492751
2      7  0.732403  0.689530
3      2  0.487685 -1.017337
4      1  1.160858 -0.025232

In [12]: res = []

In [13]: for month, group in df.groupby('month'):
    ...:     new_df = pd.DataFrame({
    ...:         'A^2+B': group.A ** 2 + group.B,
    ...:         'A+B^2': group.A + group.B**2
    ...:     })
    ...:     res.append(new_df)
    ...:

In [14]: res = pd.concat(res).sort_index()

In [15]: res.head()
Out[15]:
      A^2+B     A+B^2
0 -0.903801  0.789282
1  7.913327 -2.481270
2  1.225944  1.207855
3 -0.779501  1.522660
4  1.322360  1.161495

此方法非常快速且可扩展。您可以在此处派生任何功能。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.