You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于数据集月份跨度的日期分箱函数问题求助

日期分箱函数问题解决

问题背景

编写一个根据数据集总月份数对日期数据分箱的函数,要求固定分为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

原函数运行时遇到两个问题:

  1. 分箱边界存在重叠,部分观测同时属于两个分组
  2. 首尾日期的观测未被分箱(如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生成的区间与实际数据脱节,导致边界逻辑混乱(如左边界大于部分数据日期),看似“重叠”的实际是数据未被正确归类,而非真的边界重叠。


修正后的函数实现

核心调整:

  1. 基于df的实际最小/最大日期计算分箱边界,确保覆盖全部数据
  2. 分箱边界对齐到目标周期(年度/季度/月度)的标准边界
  3. 修复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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 14:19:55