python - 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
解决方案
经过审查,可以使用更简单的解决方案。请参阅下面第 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 部分中的代码替换grouped
为df
in 或添加新行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
值。Date
Volume
Sales
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()
推荐阅读
- node.js - 有没有办法将 macOS 父子功能添加到 windows 和 linux 的电子子窗口中?
- azure - 将机器人连接到 Azure 机器人服务团队频道
- php - 使用 Require 然后在类中使用
- scala - 为 Scala 项目生成可执行 Jar
- sql - For循环从SQLite数据库中读取多个表
- r - 如何计算不同比率的 npv(净现值)?
- python - (排列的)列表的唯一组合
- flutter - 没有为类型“_CreateEventState”定义方法“FormBuilderDateTimePicker”
- python - 使用 Decompose 删除空标签
- python - 如何使用测地线创建距离表