基于数据集月份跨度的日期分箱函数问题求助
日期分箱函数问题解决
问题背景
编写一个根据数据集总月份数对日期数据分箱的函数,要求固定分为3个箱,分箱规则:
- 总月份数>48:使用年度日历边界分箱
- 总月份数≥27且<48:使用季度边界分箱
- 总月份数<27:使用月度边界分箱
测试数据:
import pandas as pd month = pd.date_range(start = "2016-01-03", freq = "6D", periods = 100) quarter = pd.date_range(start = "2016-03-01", freq = "28D", periods = 50) year = pd.date_range(start = "2016-03-01", end = "2022-08-09", freq = "9D") small = pd.DataFrame(month, columns = ["date"]) # < 27 months, use month boundaries medium = pd.DataFrame(quarter, columns = ["date"]) # >= 27 & < 48 months, use quarter boundaries large = pd.DataFrame(year, columns = ["date"]) # > 48 months use year boundaries
原函数运行时遇到两个问题:
- 分箱边界存在重叠,部分观测同时属于两个分组
- 首尾日期的观测未被分箱(如
medium数据集的2016Q1和2019Q4对应date_group为NaN)
问题分析与解决
问题2:首尾日期未被分箱的原因
原代码通过pd.date_range(start=df["date"].min(), end=df["date"].max(), freq=frequency)生成周期边界,但freq="Y"/"Q"/"M"会生成日历周期的标准边界(如季度末、年末),而非从df的实际最小日期开始。例如medium数据集的最小日期是2016-03-01,freq="Q"生成的第一个边界是2016-03-31(Q1末),导致早于该日期的记录无法匹配分箱,最终date_group为NaN;同理,最大日期可能晚于生成的最后一个周期边界,导致尾部记录遗漏。
问题1:分箱边界重叠
原代码依赖周期序列的首尾计算分箱区间,但该序列未覆盖df的完整时间范围,再通过np.linspace生成的区间与实际数据脱节,导致边界逻辑混乱(如左边界大于部分数据日期),看似“重叠”的实际是数据未被正确归类,而非真的边界重叠。
修正后的函数实现
核心调整:
- 基于df的实际最小/最大日期计算分箱边界,确保覆盖全部数据
- 分箱边界对齐到目标周期(年度/季度/月度)的标准边界
- 修复
pd.cut的区间覆盖逻辑,避免数据遗漏
import pandas as pd import numpy as np def time_cohort(df): # 计算数据集的总月份跨度(更准确的方式) min_date = df["date"].min() max_date = df["date"].max() months_diff = (max_date.year - min_date.year) * 12 + (max_date.month - min_date.month) # 选择分箱周期并确定周期边界范围 if months_diff > 48: chosen_period = "year" frequency = "Y" # 对齐到年度边界:最小日期所在年初,最大日期所在年末 start_bound = min_date.replace(month=1, day=1) end_bound = max_date.replace(month=12, day=31) elif 27 <= months_diff < 48: chosen_period = "quarter" frequency = "Q" # 对齐到季度边界:最小日期所在季度第一天,最大日期所在季度最后一天 start_quarter = (min_date.month - 1) // 3 * 3 + 1 start_bound = min_date.replace(month=start_quarter, day=1) end_quarter = ((max_date.month - 1) // 3 + 1) * 3 end_bound = max_date.replace(month=end_quarter, day=pd.Period(max_date, freq="Q").end_time.day) else: chosen_period = "month" frequency = "M" # 对齐到月度边界:最小日期所在月第一天,最大日期所在月最后一天 start_bound = min_date.replace(day=1) end_bound = max_date.replace(day=pd.Period(max_date, freq="M").end_time.day) # 生成3个均匀分箱的边界 bins = 3 edges = np.linspace(start=start_bound.value, stop=end_bound.value, num=bins + 1) bin_dates = pd.to_datetime(edges) # 调整边界确保覆盖全部数据:左边界设为实际最小日期,右边界设为最大日期+1天(适配右开区间) bin_dates[0] = min_date bin_dates[-1] = max_date + pd.Timedelta(days=1) # 执行分箱 df["date_group"] = pd.cut( df["date"], bins=bin_dates, labels=["Earliest", "Middle", "Latest"], right=False, include_lowest=True ) # 添加周期列 df[chosen_period] = df["date"].dt.to_period(frequency) # 生成汇总统计 summary = df.groupby(by="date_group", as_index=True).agg( earliest_period=(chosen_period, "min"), latest_period=(chosen_period, "max"), count=("date_group", "count") ).rename(columns={ "earliest_period": f"earliest {chosen_period}", "latest_period": f"latest {chosen_period}" }).reset_index() return summary
验证效果
small数据集:按月度边界分箱,所有日期均被归入3个箱,无NaN值medium数据集:按季度边界分箱,2016Q1和2019Q4的记录被正确分箱large数据集:按年度边界分箱,边界无重叠,全部数据被覆盖
内容的提问来源于stack exchange,提问作者JoMcGee
相关产品推荐
相关产品推荐

