
本文介绍如何基于每行记录的日期与当前日期之间的完整周数,对 dataframe 行进行智能重复,并为每一重复行生成对应的 iso 周编号(含跨年处理),避免手动循环和索引错位问题。
在数据分析中,常需将单条时间记录“展开”为按周粒度的多行数据——例如,为每个 ID 生成从其原始日期起、持续至当前周的全部周序列。关键挑战在于:既要准确计算周数差(支持跨年),又要为每行分配正确的 ISO 年份与周号(isocalendar().year 和 .week),而非简单递增数字。
原方法使用 for 循环修改 loc 并依赖 ffill(),存在两大缺陷:
- ✅ 错误复用全局索引(如 final.loc[i, 'WEEK'] 覆盖非目标行);
- ❌ 忽略 ISO 周的语义(如 2023-12-24 属于 ISO 第51周,但下一周是2023年第52周或2024年第1周,不可线性+1)。
正确解法应以时间为本体:对每行原始日期,逐周递增7天,再统一提取其 ISO 周信息。以下是推荐实现:
import pandas as pd
# 构建示例数据
df = pd.DataFrame({
'ID': ['ID001', 'ID002', 'ID003'],
'DATE': ['24/12/2023', '01/02/2024', '12/02/2024']
})
df['DATE'] = pd.to_datetime(df['DATE'], dayfirst=True)
# 步骤1:计算每行需重复的周数(含起始周)
now = pd.Timestamp('now')
n_weeks = (now.to_period('W') - df['DATE'].dt.to_period('W')).apply(lambda x: x.n)
# 步骤2:按周数重复行(+1 包含起始周本身)
repeated = df.loc[df.index.repeat(n_weeks + 1)].copy()
# 步骤3:为每组重复行添加递增的天数偏移(0, 7, 14, ...),并计算对应ISO周
group_cumcount = repeated.groupby(level=0).cumcount() # 每组内计数:0,1,2,...
offset_days = group_cumcount * 7
repeated['DATE_WEEK'] = df['DATE'].iloc[repeated.index].values + pd.to_timedelta(offset_days, unit='D')
repeated['YEAR'] = repeated['DATE_WEEK'].dt.isocalendar().year
repeated['WEEK'] = repeated['DATE_WEEK'].dt.isocalendar().week
# 最终结果(保留原始ID、原始DATE用于业务标识,但WEEK基于实际周日期计算)
result = repeated[['ID', 'DATE', 'YEAR', 'WEEK']].reset_index(drop=True)
print(result)? 关键说明: to_period('W') 将日期转为 ISO 周周期对象,相减直接得整数周差(自动处理跨年、闰周); groupby(level=0).cumcount() 精准获取每组重复行内的序号(0起始),避免索引污染; DATE_WEEK 是逻辑上的“第N周对应的实际日期”,由此派生的 YEAR/WEEK 严格符合 ISO 8601 标准(如2023-12-24所在周为2023-W51,+7天后为2023-W52,+14天后为2024-W01); 输出中 DATE 列保持原始值(便于溯源),YEAR/WEEK 列反映各周的真实归属。
✅ 优势总结:
- 向量化运算,性能远超显式循环;
- 完全规避索引操作风险;
- 原生支持 ISO 周跨年逻辑;
- 代码简洁,可读性与可维护性高。
此方法适用于销售预测归因、用户活跃周分析、计划排期展开等场景,是时间维度数据展开的标准实践。










