首页 > 解决方案 > Pandas 将每月数据重新采样为每周在组内和拆分值

问题描述

我有一个数据框,如下:

ID Date     Volume Sales
1  2020-02   10     4
1  2020-03   8      6
2  2020-02   6      8
2  2020-03   4      10

有没有一种简单的方法可以使用重采样将其转换为每周数据?并将数量和销售列除以该月的周数?

我已经开始了我的过程,其中的代码如下所示:

import pandas as pd
df['Date'] = pd.to_datetime(df['Date'])
df = df.set_index('date')
grouped = df.groupby('ID').resmaple('W').ffill().reset_index() 
print(grouped)

完成此步骤后,我收到一条错误消息:无法插入 ID,已存在

还有一个代码可用于查找一个月中的周数,用于将数量和销售列除以该月的周数。

预期输出为:

ID      Volume  Sales      Weeks
0   1      2.5    1.0     2020-02-02
0   1      2.5    1.0     2020-02-09
0   1      2.5    1.0     2020-02-16
0   1      2.5    1.0     2020-02-23
1   1      1.6    1.2     2020-03-01
1   1      1.6    1.2     2020-03-08
1   1      1.6    1.2     2020-03-15
1   1      1.6    1.2     2020-03-22
1   1      1.6    1.2     2020-03-29
2   2      1.5    2       2020-02-02
2   2      1.5    2       2020-02-09
2   2      1.5    2       2020-02-16
2   2      1.5    2       2020-02-23
3   2      0.8    2       2020-03-01
3   2      0.8    2       2020-03-08
3   2      0.8    2       2020-03-15
3   2      0.8    2       2020-03-22
3   2      0.8    2       2020-03-29

标签: pythonpandasdataframenumpy

解决方案


经过审查,可以使用更简单的解决方案。请参阅下面第 1 部分中标有新解决方案的小节。

此任务需要多个步骤。让我们分解如下:

第 1 部分:转换日期和重新采样

新解决方案

考虑到所需的每周频率,基于星期日(即 freq='W-SUN')对于每个月都是独立的,并且与任何相邻月份无关或受其影响,我们可以直接使用列中的年月值Date来生成日期范围每周一次,而不是分成两步,首先生成从年到月的每日日期范围,然后将每日日期范围重新采样为每周。

新的程序逻辑只需要在生成一个月的每周频率的帮助下pd.date_range()使用。总而言之,它不需要调用或喜欢其他解决方案。 实际上,with正在为我们完成重采样任务。freq='W'pd.offsets.MonthEnd().resample().asfreq()pd.date_range()freq='W'

代码如下:

df['Weeks'] = df['Date'].map(lambda x: 
                             pd.date_range(
                                 start=pd.to_datetime(x), 
                                 end=(pd.to_datetime(x) + pd.offsets.MonthEnd()),
                                 freq='W'))

df = df.explode('Weeks')

结果:

print(df)


   ID     Date  Volume  Sales      Weeks
0   1  2020-02      10      4 2020-02-02
0   1  2020-02      10      4 2020-02-09
0   1  2020-02      10      4 2020-02-16
0   1  2020-02      10      4 2020-02-23
1   1  2020-03       8      6 2020-03-01
1   1  2020-03       8      6 2020-03-08
1   1  2020-03       8      6 2020-03-15
1   1  2020-03       8      6 2020-03-22
1   1  2020-03       8      6 2020-03-29
2   2  2020-02       6      8 2020-02-02
2   2  2020-02       6      8 2020-02-09
2   2  2020-02       6      8 2020-02-16
2   2  2020-02       6      8 2020-02-23
3   2  2020-03       4     10 2020-03-01
3   2  2020-03       4     10 2020-03-08
3   2  2020-03       4     10 2020-03-15
3   2  2020-03       4     10 2020-03-22
3   2  2020-03       4     10 2020-03-29

通过上面两行代码,我们已经得到了 Part 1 所需的结果。我们不需要再看旧解决方案中.groupby()的复杂代码了。.resample()

我们可以继续进行第 2 部分。由于我们还没有创建grouped对象,我们可以将第 2 部分中的代码替换groupeddfin 或添加新行grouped = df继续。

旧解决方案

我们使用pd.date_range()withfreq='D'pd.offsets.MonthEnd()生成整个月的每日条目。然后将这些完整的月份范围转换为索引,然后再重新采样为周频率。重新采样以排除在默认参数closed='left'下生成的不需要的 2020-04-05 周。resample()

df['Weeks'] = df['Date'].map(lambda x: 
                             pd.date_range(
                                 start=pd.to_datetime(x), 
                                 end=(pd.to_datetime(x) + pd.offsets.MonthEnd()),
                                 freq='D'))

df = df.explode('Weeks').set_index('Weeks')

grouped = (df.groupby(['ID', 'Date'], as_index=False)
             .resample('W', closed='left')
             .ffill().dropna().reset_index(-1))

结果:

print(grouped)


       Weeks   ID     Date  Volume  Sales
0 2020-02-02  1.0  2020-02    10.0    4.0
0 2020-02-09  1.0  2020-02    10.0    4.0
0 2020-02-16  1.0  2020-02    10.0    4.0
0 2020-02-23  1.0  2020-02    10.0    4.0
1 2020-03-01  1.0  2020-03     8.0    6.0
1 2020-03-08  1.0  2020-03     8.0    6.0
1 2020-03-15  1.0  2020-03     8.0    6.0
1 2020-03-22  1.0  2020-03     8.0    6.0
1 2020-03-29  1.0  2020-03     8.0    6.0
2 2020-02-02  2.0  2020-02     6.0    8.0
2 2020-02-09  2.0  2020-02     6.0    8.0
2 2020-02-16  2.0  2020-02     6.0    8.0
2 2020-02-23  2.0  2020-02     6.0    8.0
3 2020-03-01  2.0  2020-03     4.0   10.0
3 2020-03-08  2.0  2020-03     4.0   10.0
3 2020-03-15  2.0  2020-03     4.0   10.0
3 2020-03-22  2.0  2020-03     4.0   10.0
3 2020-03-29  2.0  2020-03     4.0   10.0

在这里,我们保留该列Date以供以后使用。

第 2 部分:将销量和销售额除以每月的周数

在这里,用于划分 Volume 和 Sales 数据的月份周数实际上应该是该月内重新采样的周数,如上面的中间结果所示。

如果我们使用实际的周数,那么对于 2020 年 2 月,由于闰年,该月有 29 天,因此它实际上跨越 5 周,而不是上述中间结果中的 4 个重新采样周。然后它会导致不一致的结果,因为上面只有 4 周的条目,而我们将每个 Volume 和 Sales 数字除以 5。

那么让我们看一下代码:

我们按列分组,然后按列和组大小(即重采样周数)划分每个ID值。DateVolumeSales

grouped[['Volume', 'Sales']] = (grouped.groupby(['ID', 'Date'])[['Volume', 'Sales']]
                                       .transform(lambda x: x / x.count()))

/=或使用如下简化形式:

grouped[['Volume', 'Sales']] /= (grouped.groupby(['ID', 'Date'])[['Volume', 'Sales']]
                                        .transform('count'))

结果:

print(grouped)


       Weeks   ID     Date  Volume  Sales
0 2020-02-02  1.0  2020-02     2.5    1.0
0 2020-02-09  1.0  2020-02     2.5    1.0
0 2020-02-16  1.0  2020-02     2.5    1.0
0 2020-02-23  1.0  2020-02     2.5    1.0
1 2020-03-01  1.0  2020-03     1.6    1.2
1 2020-03-08  1.0  2020-03     1.6    1.2
1 2020-03-15  1.0  2020-03     1.6    1.2
1 2020-03-22  1.0  2020-03     1.6    1.2
1 2020-03-29  1.0  2020-03     1.6    1.2
2 2020-02-02  2.0  2020-02     1.5    2.0
2 2020-02-09  2.0  2020-02     1.5    2.0
2 2020-02-16  2.0  2020-02     1.5    2.0
2 2020-02-23  2.0  2020-02     1.5    2.0
3 2020-03-01  2.0  2020-03     0.8    2.0
3 2020-03-08  2.0  2020-03     0.8    2.0
3 2020-03-15  2.0  2020-03     0.8    2.0
3 2020-03-22  2.0  2020-03     0.8    2.0
3 2020-03-29  2.0  2020-03     0.8    2.0

或者,如果您愿意,您可以做一些装饰性的工作来删除列Date并将列重新排列Weeks到您想要的位置。

编辑:(与其他问题逐月重新采样的相似性和不同之处)

在这篇评论中,我搜索了一些类似标题的其他问题,并比较了问题和解决方案。

还有另一个类似要求的问题,即根据重新采样月份中的周数将每月值平均分割为每周值。在那个问题中,月份表示为月份的第一个日期,它们采用日期时间格式并用作数据框中的索引,而在这个问题中,月份表示为YYYY-MM可以是字符串类型的月份。

一个重大而关键的区别是,在该问题中,实际上没有处理值为 22644 的上个月期间索引 2018-05-01。 也就是说,2018-05 月份不会在 2018 年 5 月重新采样为周,并且值 22644 从未被处理以拆分为每周比例。使用的已接受解决方案.asfreq()根本不显示 2018-05 的任何条目,而使用的另一个解决方案.resample()仍保留 2018-05 的一个(未重新采样)条目,并且值 22644 未拆分为每周比例。

但是,在我们这里的问题中,每个组中列出的最后一个月仍然需要重新采样为周,并且重新采样的周的值平均分配。

查看解决方案,我的新解决方案没有调用.resample()nor .asfreq()。它只是在 'YYYY-MM' 值pd.date_range()freq='W'帮助下pd.offsets.MonthEnd()生成一个月的每周频率。这是我在使用旧的解决方案时无法想象的.resample()


推荐阅读