如何在Pandas中计算不跨两个月的周数并生成周变量?
解决Pandas中生成不跨月的年内周数问题
要实现周数不跨月的需求(比如2023年1月30-31日归为第5周,2月1日起归为第6周),需要自定义周区间逻辑,替代默认的ISO周计算,同时用Pandas矢量化操作提升效率。
核心思路
确保每个周的所有日期都属于同一个月份:
- 以周一作为周起始(和你原函数逻辑一致)
- 当标准周一到周日的周跨月时,拆分该周:当月剩余日期单独算一个周,下月起始日期算下一个周
- 周数按年内累计编号
完整实现代码
import pandas as pd import datetime def generate_non_crossing_weeks(year): weeks = [] current_week_num = 1 # 找到当年第一个周一 first_day_of_year = datetime.date(year, 1, 1) first_monday_of_year = first_day_of_year + datetime.timedelta(days=(0 - first_day_of_year.weekday()) % 7) # 处理1月1日到第一个周一前的单独日期(可选,这里设为第0周) jan_first = datetime.date(year, 1, 1) if jan_first < first_monday_of_year: weeks.insert(0, (0, jan_first, first_monday_of_year - datetime.timedelta(days=1))) current_day = first_monday_of_year while current_day.year == year: month = current_day.month last_day_of_month = datetime.date(current_day.year, month, 1) + pd.offsets.MonthEnd(0) last_day_of_month = last_day_of_month.date() week_end = current_day + datetime.timedelta(days=6) if week_end <= last_day_of_month: weeks.append((current_week_num, current_day, week_end)) current_week_num += 1 current_day = week_end + datetime.timedelta(days=1) else: # 拆分跨月周:当月部分单独成周 weeks.append((current_week_num, current_day, last_day_of_month)) current_week_num += 1 # 跳到下月第一天继续 current_day = datetime.date(current_day.year, month+1, 1) if month <12 else datetime.date(year+1,1,1) return weeks # ---------------------- # 测试用例 # ---------------------- # 创建测试数据集 dates = pd.date_range(start='2023-01-29', end='2023-02-02') dataset = pd.DataFrame({'starttime': dates}) # 转换日期列格式 dataset['starttime'] = pd.to_datetime(dataset['starttime']) # 生成2023年不跨月周的区间 weeks_df = pd.DataFrame(generate_non_crossing_weeks(2023), columns=['week_DP_2023', 'week_start', 'week_end']) weeks_df['week_start'] = pd.to_datetime(weeks_df['week_start']) weeks_df['week_end'] = pd.to_datetime(weeks_df['week_end']) # 排序后匹配周数(merge_asof要求左表按on字段排序) dataset = dataset.sort_values('starttime') dataset = pd.merge_asof(dataset, weeks_df, left_on='starttime', right_on='week_start', direction='backward') dataset = dataset[dataset['starttime'] <= dataset['week_end']] # 查看结果 print(dataset[['starttime', 'week_DP_2023']])
运行结果
starttime week_DP_2023 0 2023-01-29 4 1 2023-01-30 5 2 2023-01-31 5 3 2023-02-01 6 4 2023-02-02 6
完全符合需求:1月30-31日为第5周,2月1日起为第6周。
原代码的问题说明
- 跨月周问题:你原函数
getDateRangeFromWeek用的是ISO周逻辑,会生成跨月的周(比如2023年第5周是2023-01-30到2023-02-05),不符合不跨月的要求。 - 效率问题:循环赋值的方式在大数据集下效率极低,Pandas的
merge_asof是矢量化操作,速度快得多。 - 语法错误:Python没有内置的
frange函数,应该用range(1, 54)来遍历1-53周。
内容的提问来源于stack exchange,提问作者Ajay Mishra
相关产品推荐
相关产品推荐

