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

122

我在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)

我想对“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

我意识到这种简单的赋值不应该有效。但是在 Pandas 中,将 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语言的风格。


解决方案:由于您正在按照“yearmonth”列进行分组,如果您在每个组内使用apply或aggregate函数,您可以将结果合并/连接到“yearmonth”上。 - undefined
6个回答

97
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

解决方案:由于它们正在按照'yearmonth'列进行分组,如果在每个组内使用apply或aggregate函数,您可以将结果合并/连接到'on='yearmonth''上。 - undefined

70

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

60

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

我不确定 apply 如何工作,但是通过 transform 实现复杂的 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()一起使用,因为大多数情况下,pandas需要实例化新对象以便在完整的数据集规模上对其进行操作(例如,如果一个列不存在,则不能将两个列相加)。

希望这有所帮助 :)


4
我认为这是最好的答案。使用“transform”,您可以跳过标准“df.groupby('yearmonth')['weighted_P'].sum()”后面的连接。 - Arturo Sbr

32

我可以建议你使用transform方法(而不是aggregate)吗? 如果您在原始示例中使用它,它应该会实现您想要的操作(即广播)。


2

我没有找到一种方法来对原始数据框进行赋值。因此,我只是将来自分组的结果存储并连接它们。然后,我们按索引对连接的数据框进行排序,以获得与输入数据框相同的原始顺序。以下是样例代码:

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错误。

0
如果我们想在每个组上计算函数并将结果分配回列中,可以使用apply函数实现。
df = pd.DataFrame({
    'x': list(range(6)),
    'group': [0, 1, 0, 1, 0, 1],
})
print(df)

def process_group(df):
    # It is not recommended to mutate the object we're iterating on, thus the copy:
    # https://pandas.pydata.org/docs/user_guide/gotchas.html#mutating-with-user-defined-function-udf-methods
    df = df.copy() 
    df['y'] = df['x'] / df['x'].sum()
    df['x'] = df['x'] * 2
    return df


print(df.groupby(['group']).apply(process_group).reset_index(drop=True))

df:

   x  group
0  0      0
1  1      1
2  2      0
3  3      1
4  4      0
5  5      1

result:

    x  group         y
0   0      0  0.000000
1   4      0  0.333333
2   8      0  0.666667
3   2      1  0.111111
4   6      1  0.333333
5  10      1  0.555556

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接