当使用groupby apply生成Series时,保留DataFrame的索引

4
当使用groupby+apply调用函数时,我希望从一个DataFrame转换为一个Series分组对象,对每个组应用一个以Series作为输入和输出的函数,并将groupby+apply调用的输出赋值为DataFrame中的一个字段。默认行为是使groupby+apply的输出按分组字段进行索引,这会使我无法干净地将它赋回到DataFrame中。我更喜欢使用Series作为输入和输出来调用apply函数,因为这比DataFrame转换为DataFrame更加简洁。(这并不是这个例子中获取结果的最佳方式;实际应用有所不同。)
import pandas as pd
df = pd.DataFrame({
 'A': [999, 999, 111, 111],
 'B': [1, 2, 3, 4],
 'C': [1, 3, 1, 3]
})
def less_than_two(series):
  # Intended for series of length 1 in this case
  # But not intended for many-to-one generally
  return series.iloc[0] < 2
output = df.groupby(['A', 'B'])['C'].apply(less_than_two)

我希望 output 上的索引与 df 相同,否则无法将值干净地分配给 df

df['Less_Than_Two'] = output

output.index = df.index 这样的写法看起来过于丑陋,而使用 group_keys 参数似乎也不起作用:

output = df.groupby(['A', 'B'], group_keys = False)['C'].apply(less_than_two)
df['Less_Than_Two'] = output
2个回答

3
transform方法返回与原始index相同的结果,正如您所要求的那样。它会将同一结果广播到组中的所有元素。请注意,dtype可能会被推断成其他类型。您可能需要自己进行转换。
在这种情况下,为了添加另一列,我将使用assign方法。
df.assign(
    Less_Than_Two=df.groupby(['A', 'B'])['C'].transform(less_than_two).astype(bool))

     A  B  C Less_Than_Two
0  999  1  1          True
1  999  2  3         False
2  111  3  1          True
3  111  4  3         False

看起来transform与输入字段保持相同的dtype。我喜欢transform保持原始的Index,虽然我不一定要广播,但也无所谓,因为结果的长度为1。尽管在完整的问题中transformboolean转换为datetime,而datetime无法转换回boolean,但这个答案在精神上是最好的。从R语言的角度来看,我觉得Index是双刃剑,而dtypes有点困难,但我喜欢很多其他的东西。 - mef jons

1
假设您的 groupby 是必需的(并且生成的 groupby 对象将比您的 DataFrame 少几行 -- 这在示例数据中并不是这种情况),那么将该 Series 分配给“Is.Even”列将导致 NaN 值(因为对于 output 的索引将比对 df 的索引更短)。
相反,根据示例数据,最简单的方法是将 output -- 作为 DataFrame -- 与 df 合并,如下所示:
output = df.groupby(['A','B'])['C'].agg({'C':is_even}).reset_index() # reset_index restores 'A' and 'B' from indices to columns
output.columns = ['A','B','Is_Even'] #rename target column prior to merging
df.merge(output, how='left', on=['A','B']) # this will support a many-to-one relationship between combinations of 'A' & 'B' and 'Is_Even'
# and will thus properly map aggregated values to unaggregated values

此外,我应该指出,在变量名称中使用下划线比使用点更好;与例如R语言不同,点作为访问对象属性的运算符,因此在变量名称中使用它们可能会阻碍功能/造成混淆。

感谢@cmaher。我的示例不是很好,刚刚更新了。意图是使用dataframe.groupby[field name].apply将系列应用于具有相同索引的系列,其中结果将与输入系列具有相同的形状,适用于多对多应用程序。 - mef jons
没问题。仍然不清楚为什么你需要groupby;根据你的描述和示例代码,你可以使用df.loc[:,'Less_Than_Two'] = df.C.apply(less_than_two)创建Less_Than_Two列。 - cmaher
我非常喜欢split-apply-combine,而且在pandas中从DataFrame到DataFrame的转换非常直观。原则上,我喜欢使用最简单的数据结构,所以我想做Series到Series,但是Index的处理有点困扰我。如果这个例子出现在现实世界中,我可能只会做df.C < 2,但我正在处理的问题有点不同。 - mef jons

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