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

我在 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 操作,它计算“年月”栏中每个日期的所有资产的上限加权平均回报率。

这种方法正如预期的那样:

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

But then I want to sort of "broadcast" these values back to the indices in the original data frame, and save them as constant columns where the dates match.

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 操作的结果分配到父数据框中的新列中?

最后,我想要一个名为“ 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 的。

104217 次浏览
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

虽然我仍然在探索 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

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

作为使用 groupby ()时的一般规则,如果使用。转换()函数熊猫将返回一个与原始表相同长度的表。当您使用其他函数时,如。款额()或。First ()然后熊猫将返回一个表,其中每一行是一个组。

我不知道这是如何工作的应用,但实现复杂的 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 ()并不总是管用,因为大多数时候熊猫需要实例化新对象来在整个数据集范围内操作它(也就是说,如果一个列还不存在,你就不能把两个列放在一起)。

希望这对你有帮助:)

我没有找到分配到原始数据框架的方法。所以我只是存储组的结果并连接它们。然后根据索引对连接的数据帧进行排序,得到作为输入数据帧的原始顺序。下面是一个示例代码:

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

这个方法是相当快速和可扩展的。您可以在这里派生任何特性。

注意: 如果数据帧太大,concat可能会导致你的 MMO 错误。