Pandas groupby:计算(相对)大小并保存到原始数据框中

2 投票
2 回答
1398 浏览
提问于 2025-04-18 03:43

我的数据库结构是这样的:我有一些单位,这些单位属于几个不同的组,并且有不同的变量(这次我主要关注一个变量,叫做X)。然后我们有基于年份的记录。所以数据库看起来像这样:

    unitid, groupid, year, X
0        1        1, 1990, 5
1        2        1, 1990, 2
2        2        1, 1991, 3
3        3        2, 1990, 10

等等。现在我想做的是测量一个“强度”变量,这个变量是每个组和每年的单位数量,我想把这个数据放回数据库里。

到目前为止,我的做法是:

asd = df.drop_duplicates(cols=['unitid', 'year'])
groups = asd.groupby(['year', 'groupid'])
intensity = groups.size()

然后强度看起来像这样:

year groupid
1961    2000    4
        2030    3
        2040    1
        2221    1
        2300    2

但是,我不知道怎么把它们放回原来的数据框中。我可以通过 intensity[0] 访问它们,但 intensity.loc() 却给了我一个“LocIndexer不可调用”的错误。

其次,如果我能对强度进行缩放,那就太好了。也就是说,不是“每组每年的单位数”,而是“每组每年的单位数,按该年每组每年的平均值/最大值进行缩放”。如果 {t,g} 表示一个组-年的单元格,那么就是:

relative intensity

也就是说,如果我的简单强度变量(针对时间和组)叫做 intensity(t, g),我想创建 relativeIntensity(t,g) = intensity(t,g)/mean(intensity(t=t,g=:)) - 如果这个伪代码能帮助我更清楚地表达我的意思。

谢谢!

更新

为了可读性,我在这里明确写出答案。第一部分的问题通过以下方式解决:

intensity = intensity.reset_index()
df['intensity'] = intensity[0]

2 个回答

1

这是一种多重索引。你可以通过调用 .reset_index() 来重置你的数据框的索引。或者在进行分组操作时,可以通过在 groupby() 中指定 as_index=False 来禁用索引,像这样:

intensity = asd.groupby(["year", "groupid"], as_index=False).size()

关于你的第二个问题,我不太明白你在说 Instead of "units per group-year", it would be "units per group-year, scaled by average/max units per group-year in that year". 的意思。如果你想通过 intensity / mean(intensity) 来计算“强度”,你可以使用 transform 方法,像这样:

asd.groupby(["year", "groupid"])["X"].transform(lambda x: x/mean(x))

这就是你想要的吗?

更新

如果你想计算 intensity / mean(intensity),其中 mean(intensity) 仅基于 year 而不是 year/groupid 的子集,那么你首先需要仅基于 year 创建 mean(intensity),像这样:

intensity["mean_intensity_only_by_year"] = intensity.groupby(["year"])["X"].transform(mean)

然后计算所有 year/groupid 子集的 intensity / mean(intensity),其中 mean(intensity) 仅来自 year 子集:

intensity["relativeIntensity"] = intensity.groupby(["year", "groupid"]).apply(lambda x: pd.DataFrame(
                        {"relativeIntensity": x["X"] / x["mean_intensity_only_by_year"] }
                    ))

也许这就是你想要的,对吧?

1

其实,几天后我发现这个双重问题的第一个回答是错的。也许有人可以详细解释一下 .size() 实际上是做什么的,但我写这个是为了防止有人在谷歌搜索这个问题时走上我错误的道路。

结果发现 .size() 返回的行数远远少于原始对象的行数(即使我用了 reset_index(),而且我尝试把这些大小重新放回原来的对象中,还是有很多行是 NaN)。不过,下面的做法是有效的。

groups = asd.groupby(['year', 'groupid'])
intensity = groups.apply(lambda x: len(x))
asd.set_index(['year', 'groupid'], inplace=True)
asd['intensity'] = intensity

另外,你也可以这样做:

groups = asd.groupby(['fyearq' , 'sic'])
# change index to save groupby-results
asd= asd.set_index(['fyearq', 'sic'])
asd['competition'] = groups.size()

我的问题的第二部分通过以下方式得到了回答:

# relativeSize
def computeMeanInt(group):
    group = group.reset_index()
    # every group has exactly one weight in the mean:
    sectors = group.drop_duplicates(cols=['group'])
    n = len(sectors)
    val = sum(sectors.competition)
    return float(val) / n


result = asd.groupby(level=0).apply(computeMeanInt)
asd= asd.reset_index().set_index('fyearq')
asd['meanIntensity'] = result
# if you don't reset index, everything crashes (too intensive, bug, whatever)
asd.reset_index(inplace=True)
asd['relativeIntensity'] = asd['intensity']/asd['meanIntensity']

撰写回答