【问题标题】:GroupBy on multiple columns and apply moving functionGroupBy 在多个列上并应用移动功能
【发布时间】:2019-02-22 01:40:30
【问题描述】:

假设我有这个数据集:

Country_id  Company_id  Date    Company_value
1   1   01/01/2018  1
1   1   02/01/2018  0
1   1   03/01/2018  2
1   1   04/01/2018  NA
1   2   01/01/2018  1
1   2   02/01/2018  2
1   2   03/01/2018  NA
1   2   04/01/2018  NA
2   1   01/01/2018  3
2   1   02/01/2018  0
2   1   03/01/2018  2
2   1   04/01/2018  NA
2   2   01/01/2018  1
2   2   02/01/2018  2
2   2   03/01/2018  NA
2   2   04/01/2018  NA

我想应用一个移动函数(例如移动平均值)来检索每个日期和国家/地区的聚合值。

例如,在移动平均线的情况下(窗口 = 2 和 min_periods=1,NA 不计算在内)我想要以下内容:

Country_id  Date    Companies_value
1   01/01/2018  1
1   02/01/2018  1
1   03/01/2018  1.33
1   04/01/2018  2
2   01/01/2018  2
2   02/01/2018  1.5
2   03/01/2018  1.33
2   04/01/2018  2

为方便您,计算方式如下:

Country_id  Date    Companies_value
1   01/01/2018  (1+1)/2
1   02/01/2018  (0+1+2+1)/4
1   03/01/2018  (2+0+2)/3
1   04/01/2018  (2)/1
2   01/01/2018  (3+1)/2
2   02/01/2018  (0+3+2+1)/4
2   03/01/2018  (2+0+2)/3
2   04/01/2018  (2)/1

如何使用pandas 做到这一点?

举个简单的例子,例如,我希望国家 1 在 03/01/2018 的日期是对该国家/地区的所有公司在 2018 年 2 月 1 日的价值取平均值,并且03/01/2018(在窗口大小为 2 的情况下)。

因此,这就是我想在 2018 年 3 月 1 日为国家 1 做的事情:

( Company_value(Company_1, 03/01/2018) + Company_value(Company_1, 02/01/2018) 
+ Company_value(Company_2, 03/01/2018) + Company_value(Company_2, 02/01/2018) ) / 4 =

= ( 2 + 0 + NA + 2) / 4 

= ( 2 + 0 + 2) / 3 # NAs not counted in

= 1.33

类似地,我希望对每个国家/地区的所有日期都执行相同的操作。

正如我所说,我想对我自己的移动函数做同样的事情,超出 pandas 的移动平均线,所以最好提供一个对任何自定义函数都有效的解决方案。

【问题讨论】:

  • 查看组合 DataFrame.rollingapply
  • @Yuca,我知道伙计,但我想要确切的解决方案......
  • 既然我不提供,那意味着我没有时间真正写下来,但至少我可以把你推向正确的方向
  • @Yuca,我很感激,但显然我知道组合 DataFrame.rolling 和应用。
  • 这不是移动平均线,使用 min_period 1 您在第一个期间没有任何价值

标签: python pandas


【解决方案1】:

更新了更多信息

数据

import pandas as pd
import numpy as np

df = pd.DataFrame({'date':['2018-01-01', '2018-02-01', '2018-03-01', '2018-04-01']*4,
              'country_id':[1]*8+[2]*8,
              'company_id':[1]*4+[2]*4+[1]*4+[2]*4,
              'value':[1, 0, 2, np.nan, 1, 2, np.nan, np.nan, 3, 0, 2, np.nan, 1, 2, np.nan, np.nan]})

country_id 内创建一个滚动总和

df['rolling_sum'] = df.groupby('country_id').apply(lambda x: x.value.rolling(window=2, min_periods=1).sum()).reset_index(drop=True)

country_id 内创建滚动计数

df['sum_records'] = df.groupby('country_id').apply(lambda x: x.value.rolling(window=2, min_periods=1).count()).reset_index(drop=True)

现在在country_iddate 中进行分组,求和,然后除以计数之和

summarized_df = df.groupby(['country_id', 'date']).apply(lambda x: x.rolling_sum.sum()/x.sum_records.sum()).reset_index()

country_id  date      
1           2018-01-01    1.000000
            2018-02-01    1.000000
            2018-03-01    1.333333
            2018-04-01    2.000000
2           2018-01-01    2.000000
            2018-02-01    1.500000
            2018-03-01    1.333333
            2018-04-01    2.000000

让我们更详细地看一下。由于我们是按 country_id 进行分组的,因此我们将分出一个国家/地区 id 来实践这种方法:

如果我们只取其中的一部分,请说country_id == 1

df2 = df[df['country_id'] == 1]

         date  country_id  company_id  value
0  2018-01-01           1           1    1.0
1  2018-02-01           1           1    0.0
2  2018-03-01           1           1    2.0
3  2018-04-01           1           1    NaN
4  2018-01-01           1           2    1.0
5  2018-02-01           1           2    2.0
6  2018-03-01           1           2    NaN
7  2018-04-01           1           2    NaN

如果我们想要这个的滚动平均值,我们可以这样做:

df2.value.rolling(window=2, min_periods=1).mean()
0    1.0
1    0.5
2    1.0
3    2.0
4    1.0
5    1.5
6    2.0
7    NaN

我们可以在这里看到来自我们的子集 country_id == 1 数据框的值以及它们与滚动平均值的关系:

0    1.0  = (1)/1 = 1
1    0.0  = (0 + 1)/2 = 0.5
2    2.0  = (2 + 0)/2 = 1
3    NaN  = (Nan + 2)/1 = 2
4    1.0  = (1 + Nan)/1 = 1
5    2.0  = (2 + 1)/2 = 1.5
6    NaN  = (Nan + 2)/1 = 2
7    NaN  = (Nan + Nan)/0 = Nan

这就是我们如何获得单个 country_id 分组的滚动平均值

如果我们想按日期分组,我们先按 country_id 分组,然后按日期分组,单个分组如下所示:

df3 = df[(df['country_id'] == 1) & (df['date'] == '2018-03-01')]

df3.value
2    2.0
6    NaN

df3.value.rolling(window=2, min_periods=1).mean()
2    2.0
6    2.0

df3.value
2    2.0 = (2)/1 = 2
6    NaN = (Nan + 2)/1 = 2

这里的问题是,您希望滚动平均值首先country_id,而不是与date 分组。 然后在您找到按国家/地区的滚动平均值后,您想要获取 那些 值并将它们平均。如果我们采用滚动平均,然后平均,结果会不正确。

让我们回到我们为country_id == 1 创建的原始滚动平均值,并查看日期:

2018-01-01    1.0  = (1)/1 =         1
2018-02-01    0.0  = (0 + 1)/2 =     0.5
2018-03-01    2.0  = (2 + 0)/2 =     1
2018-04-01    NaN  = (Nan + 2)/1 =   2
2018-01-01    1.0  = (1 + Nan)/1 =   1
2018-02-01    2.0  = (2 + 1)/2 =     1.5
2018-03-01    NaN  = (Nan + 2)/1 =   2
2018-04-01    NaN  = (Nan + Nan)/0 = Nan

现在棘手的部分是,此时我们不能将它们平均在一起,因为例如,如果您查看 2018 年 3 月 1 日的滚动平均值,我们有 1 和 2,即 3。将其除以2 会给我们 1.5。

我们必须先求和滚动值,然后除以记录数。

【讨论】:

  • 感谢您的回答,但老实说,我对您不理解的内容感到很困惑。首先,我认为已经很清楚我想要一个(移动)平均值,它在每个国家/地区的时间窗口内平均商店的所有价值。其次,关于您的评论“2018 年 4 月 1 日的所有 4 个数据点的值都为 NA,所以我对您如何获得 (2)/1 = 2 感到困惑”,窗口的长度为 2所以显然它也在考虑 2018 年 3 月 1 日的数据点,其中 2 值是。
  • 第三,关于您的评论“您的示例中有多个值,即一些是 (1+1)/2 用于 2 个条目,而另一些是 (1+0+1+2)/4 用于4 个条目表示这实际上不是移动平均线”,条目的数量不同,因为首先 NAs 值显然没有计入平均值,其次是日期 01/01/2018,这是数据集中的第一个日期每家公司的滚动窗口基本上都是一号,因为在此日期之前没有日期。这也是pandas.DataFrame.rolling 的方式,如果你用一个简单的例子来测试它。
  • 唯一的区别是本质上我想以 GroupBy 的方式使用移动功能,这使事情变得更加复杂。
  • 顺便说一句,您的解决方案很有意义,这是我第一次这样做。但有趣的是,这并不是我想要的。例如,我想要的国家 1 在 03/01/2018 的日期是取该国家所有公司在 02/01/2018 和 03/01/2018 日期的平均值(在窗口大小 2)。另请参阅我的编辑帖子以获取更多信息。
  • @PoeteMaudit 啊。我明白。最后的解释是有道理的。我认为那里有一个差距。我已更新答案以解决您的问题
【解决方案2】:

你可以通过以下方式达到你想要的结果:

# get company value by date
avg = df.groupby(["Country_id", "Date", "Company_id"]).sum().unstack(level=2).loc[:, "Company_value"]
avg = pd.concat([avg, avg.shift(1)], axis=1)
avg["sum"] = avg.apply("sum", axis=1)

# get company count by date
counts = df.groupby(["Country_id", "Date"]).count().loc[:, "Company_value"]
counts2 = counts + counts.shift(1)

# get the "mean"
result = avg["sum"] / counts2.fillna(counts)

【讨论】:

    猜你喜欢
    • 2019-07-22
    • 2017-10-29
    • 2019-03-19
    • 1970-01-01
    • 1970-01-01
    • 2019-12-19
    • 2017-09-11
    • 2022-11-23
    • 2017-03-14
    相关资源
    最近更新 更多