本文介绍如何在 pandas 中准确计算从历史日期(如公司成立日)到当前日期之间的完整月数,避免使用简单天数除法导致的误差和整数溢出问题,并提供对缺失或无效日期的健壮处理方案。
本文介绍如何在 pandas 中准确计算从历史日期(如公司成立日)到当前日期之间的完整月数,避免使用简单天数除法导致的误差和整数溢出问题,并提供对缺失或无效日期的健壮处理方案。
在使用 Pandas 处理时间序列时,常需计算某事件(如公司成立、用户注册)距今已过去多少个月。一个常见误区是先用 today - timestamp 得到 Timedelta,再通过 .dt.days // 30 近似换算——这不仅会因月份天数不均(28–31 天)引入显著偏差,更可能在时间跨度极大时触发 OverflowError: Overflow in int64 addition(尤其当 Timedelta 超出 int64 表示范围时)。
✅ 推荐方案:使用 Period 进行月粒度运算
Pandas 的 Period 类型专为按日历单位(年/月/日)进行精确算术设计。关键在于将起始日期和当前日期统一转换为 'M'(Month-end)周期,再执行减法(rsub 表示右减,即 today_period - founded_period),结果为 PeriodIndex,其 .n 属性直接返回整数月数差:
import pandas as pd
# 示例数据
df = pd.DataFrame({
'company_name': ['a', 'b', 'c', 'd'],
'founded_on': ['2004-01-01', '2013-01-01', '2008-01-01', '1997-01-01']
})
# ✅ 精确计算月数(推荐)
df['founded_on'] = pd.to_datetime(df['founded_on'])
today_period = pd.Timestamp('today').to_period('M')
df['Time_Since_founded_on'] = (
df['founded_on'].dt.to_period('M')
.rsub(today_period)
.apply(lambda x: x.n) # 提取整数月数
)
print(df)
输出:
company_name founded_on Time_Since_founded_on 0 a 2004-01-01 242 1 b 2013-01-01 134 2 c 2008-01-01 194 3 d 1997-01-01 326
⚠️ 为什么不用 days // 30?
若强行用天数近似(如 df['founded_on'].rsub(pd.Timestamp('today')).dt.days / 30),结果会偏离真实月数:
- 1997–2024 年实际约 326 个月,但 days/30 给出约 331 个月(+5 个月误差);
- 更严谨的平均月长是 365.25/12 ≈ 30.4375 天,但仍属近似,无法处理闰年、大小月等日历逻辑。
? 处理缺失或非法日期(健壮性增强)
生产环境中,founded_on 列常含 NaT、空字符串或格式错误值。应显式启用容错解析并保留 NaN 结果:
df['Time_Since_founded_on'] = (
pd.to_datetime(df['founded_on'], errors='coerce') # 错误转为 NaT
.dt.to_period('M')
.rsub(pd.Timestamp('today').to_period('M'))
.apply(lambda x: x.n if pd.notna(x) else pd.NA) # 安全提取 n
)
? 小结
- ✅ 优先使用 .dt.to_period('M').rsub(...).apply(lambda x: x.n) 实现日历月精度;
- ❌ 避免 Timedelta.days // 30 或 / 30.4375,它们违背日历语义且易溢出;
- ?️ 始终对原始日期列调用 pd.to_datetime(..., errors='coerce') 保障鲁棒性;
- ? Period('M') 自动对齐到月末,确保跨月计算符合业务直觉(如 2023-01-31 至 2023-02-28 视为 1 个月)。










