Pandas使用自定义窗口大小进行分组滚动平均值

3

问题定义:

对于一个 Pandas DataFrame,我想要获得一个按日期时间索引分组的滚动平均值,并且每行都可以指定可变窗口大小。

示例:

对于下面这个周数据的 df

Original Answer:
| week_start_date | material | location | quantity | window_size |
|-----------------|----------|----------|----------|-------------|
| 2019-01-28      | C        | A        | 870      | 1           |
| 2019-02-04      | C        | A        | 920      | 3           |
| 2019-02-18      | C        | A        | 120      | 1           |
| 2019-02-25      | C        | A        | 120      | 2           |
| 2019-03-04      | C        | A        | 120      | 1           |
| 2018-12-31      | D        | A        | 1200     | 8           |
| 2019-01-21      | D        | A        | 720      | 8           |
| 2019-01-28      | D        | A        | 480      | 8           |
| 2019-02-04      | D        | A        | 600      | 8           |
| 2019-02-11      | D        | A        | 720      | 8           |
| 2019-02-18      | D        | A        | 80       | 8           |
| 2019-02-25      | D        | A        | 600      | 8           |
| 2019-03-04      | D        | A        | 1200     | 8           |
| 2019-01-14      | E        | B        | 150      | 1           |
| 2019-01-28      | E        | B        | 1416     | 1           |
| 2019-02-04      | F        | B        | 1164     | 1           |
| 2019-01-28      | G        | B        | 11520    | 8           |

窗口需要相对于 week_start_date 中设置的实际日期,而不是将其视为整数索引。
它需要按 materiallocation 进行分组。
滚动平均值适用于列 quantity
窗口大小需要根据 window_size 列中的值变化/更改。此值随时间变化 - 它表示需要聚合数量的过去几周的数量。
当行不可用时,平均值应假定该值为 0,即:
当一个以星期为单位的行不可用时 mean(null, null, null, 1000) = 1000 但实际上应该是: mean(0,0,0,1000)=250 然而 - 这仅适用于第一次观测之后。

固定窗口,相对于日期列:

我可以使用以下内容获得 8 周(56 天)的静态窗口:
df.set_index('week_start_date').groupby(['material', 'location'])['quantity'].rolling('56D', min_periods=1).mean()

我已经尝试过使用expanding,但没有成功。

如何将窗口大小设置相对于它读取的每一行?

样本数据:

Original Answer翻译成"最初的回答"

# Example Data
df = pd.DataFrame({'week_start_date': ['2019-01-28','2019-02-04','2019-02-18','2019-02-25','2019-03-04','2018-12-31','2019-01-21','2019-01-28','2019-02-04','2019-02-11','2019-02-18','2019-02-25','2019-03-04','2019-01-14','2019-01-28','2019-02-04','2019-01-28'],
'material': ['C','C','C','C','C','D','D','D','D','D','D','D','D','E','E','F','G'],
'location': ['A','A','A','A','A','A','A','A','A','A','A','A','A','B','B','B','B'],
'quantity': ['870','920','120','120','120','1200','720','480','600','720','80','600','1200','150','1416','1164','11520'],
'min_of_pdt_or_8_weeks': ['1','3','1','2','1','8','8','8','8','8','8','8','8','1','3','1','8']})
# Fix formats
df['week_start_date'] = pd.to_datetime(df['week_start_date'])
df['actual_week_qty'] = df['quantity'].astype(float)

期望的结果:

| material | location | week_start_date | quantity | 
| C        | A        | 2019-01-28      | 870      | 
| C        | A        | 2019-04-02      | 306.6667 | 
| C        | A        | 2019-02-18      | 520      | 
| C        | A        | 2019-02-25      | 386.6667 | 
| D        | A        | 2018-12-31      | 1200     | 
| D        | A        | 2019-01-21      | 960      | 
| D        | A        | 2019-01-28      | 800      | 
| D        | A        | 2019-04-02      | 600      | 
| D        | A        | 2019-11-02      | 720      | 
| D        | A        | 2019-02-18      | 400      | 
| D        | A        | 2019-02-25      | 466.6667 | 
| D        | A        | 2019-04-03      | 650      | 
| E        | B        | 2019-01-14      | 150      | 
| E        | B        | 2019-01-28      | 783      | 
| F        | B        | 2019-04-02      | 1164     | 
| G        | B        | 2019-01-28      | 11520    |

你能展示一下预期的输出吗? - undefined
@EdekiOkoh 在帖子中添加了预期结果。 - undefined
为什么第二行是920? - undefined
@QuangHoang 920是因为窗口大小为3,但在2019年02/04之前的3周内没有数据。 - undefined
@credibly-close-mean “没有数据”为什么不算作0呢?所以等价的“慢速代码”是进行8个不同的计算(每个1w、2w、...、8w滚动窗口一个)然后根据窗口大小合并(注意:首先要做的是将window_size NaN中的fillna -> 8,这里有点噪音)。你的真实例子实际上是1-8吗?我稍微怀疑要比八次计算+合并容易多少。此外,actual_week_qty列和quantity是一样的吗?我有一个解决方案……只是我觉得愚蠢的英国日期格式有些小问题(请使用规范的ISO日期格式吧,拜托了!) - undefined
@AndyHayden 很好的观点 - 原始问题已更新:
  • 现在日期是规范的
  • 修正了示例公式中的列名
当一行不可用时,平均值应假设该值为0,即: 当周日期的行不可用时 mean(null, null, null, 1000) = 1000 但实际上应该是: mean(0,0,0,1000)=250 然而 - 这仅适用于第一个观测已被测量之后,以避免新条目对其前N个条目具有错误值。是的,它的潜在选项被硬性限制为8个。
- undefined
1个回答

1
一个天真的方法是,你可以进行8次(假设这是有界的!)计算并合并结果。
In [11]: d = {w: df.set_index('week_start_date')
                   .groupby(['material', 'location'])['quantity']
                   .rolling(f'{7*w}D', min_periods=1)
                   .mean()
                   .reset_index(name="mean")
                   .assign(window_size=w)
              for w in range(1, 9)}

然后你可以将这些DataFrame连接在一起,并与原始数据合并,因为左右两边都有window_size列,所以它会在该列上进行内连接。
In [12]: pd.concat(d.values()).merge(df, how="inner")
Out[12]:
   material location week_start_date          mean  window_size  quantity
0         C        A      2019-01-28    870.000000            1     870.0
1         C        A      2019-02-18    520.000000            1     120.0
2         C        A      2019-04-03    320.000000            1     120.0
3         E        B      2019-01-14    150.000000            1     150.0
4         F        B      2019-04-02   1164.000000            1    1164.0
5         C        A      2019-02-25    386.666667            2     120.0
6         C        A      2019-04-02    920.000000            3     920.0
7         E        B      2019-01-28    783.000000            3    1416.0
8         D        A      2018-12-31   1200.000000            8    1200.0
9         D        A      2019-01-21    960.000000            8     720.0
10        D        A      2019-01-28    800.000000            8     480.0
11        D        A      2019-04-02    600.000000            8     600.0
12        D        A      2019-11-02    720.000000            8     720.0
13        D        A      2019-02-18    400.000000            8      80.0
14        D        A      2019-02-25    466.666667            8     600.0
15        D        A      2019-04-03    650.000000            8    1200.0
16        G        B      2019-01-28  11520.000000            8   11520.0

注意:这假设您已将window_size的fillna设置为8:
df.window_size = df.window_size.replace('NaN', 8).astype(int)  # in your example

此外,您希望确保将格式传递给to_datetime,以确保避免歧义。Pandas在推断日期格式方面可能做得很好...但我不会依赖它(请明确使用format='%d/%m/%Y')。在读取日期后,您希望尽快摆脱奇怪的日期格式,这也可以在read_csv中传递(dayfirst=True)和其他参数。

我并不完全相信这是你想要的,因为你的输入df和预期结果之间存在差异(例如,预期结果中没有G B)。

不管怎样,我怀疑有一种简单的方法可以做到这一点,但这将取决于周/材料/位置的稀疏程度(如果密集的话会更容易,如果稀疏的话可能是最好的选择)...
现在我想了想,你可以完全在材料/位置子数据框上进行操作,你能简化这个问题,使它只依赖于该数据框的函数(只考虑周+值,忽略材料/位置),还是那样会太慢?


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