17370845950

如何在 Pandas 中仅对完整月份进行重采样求和(跳过起始/结束不完整的月)

本文介绍一种精准的 pandas 时间序列处理技巧:使用 `resample('ms')` 结合 `days_in_month` 属性过滤,确保仅对起止日期覆盖整月(即包含该月全部天数)的数据执行月度求和,自动排除首尾不完整的月份。

在实际时间序列分析中,常遇到每日数据跨越多个自然月但不严格对齐月边界的情况(例如从 2025-10-18 开始,到 2025-02-07 结束)。此时若直接使用 df.resample('MS').sum()('MS' 表示 Month Start),Pandas 会将每个日历月的第一天作为分组锚点,并对当月所有可用数据求和——包括仅含部分天数的首月(如 2025-10)和末月(如 2025-02),导致结果不可比、不具统计代表性。

理想方案是:仅保留那些实际数据覆盖了整个月全部天数的月份。核心思路是:对每个重采样后的月份,检查其原始数据记录数是否等于该月应有的总天数(如 2025-02 对应 28 天,2025-02 对应 29 天)。Pandas 的 DatetimeIndex.days_in_month 可直接获取各月天数,而 resample(...).size() 可统计每组有效行数。

以下是完整实现步骤:

import pandas as pd
import numpy as np

# 示例数据:非整月起止的每日数据
df = pd.DataFrame(
    {'gas': np.random.uniform(1.5, 6.5, 60)},
    index=pd.date_range('2025-10-18', periods=60, freq='D')
)

# 步骤1:按月起始重采样,同时计算每月记录数(size)和求和(sum)
monthly_agg = df.resample('MS').agg({'gas': ['size', 'sum']})
monthly_agg.columns = ['count', 'gas_sum']

# 步骤2:生成对应月份的 DatetimeIndex,并提取各月天数
month_index = monthly_agg.index
days_in_month = month_index.days_in_month

# 步骤3:布尔筛选——仅保留 count 等于该月天数的行
complete_months = monthly_agg[monthly_agg['count'] == days_in_month]

# 步骤4:清理结果:丢弃计数列,保留纯月度求和
result = complete_months[['gas_sum']].rename(columns={'gas_sum': 'gas'})

print(result)

关键说明

  • resample('MS') 确保按标准日历月分组(如 '2025-10-01', '2025-11-01');
  • agg({'gas': ['size', 'sum']}) 避免多级列名混乱,显式指定聚合操作;
  • month_index.days_in_month 是向量化属性,无需循环,高效可靠;
  • 该方法天然兼容闰年、大小月,且不受缺失值影响(size 统计非空行数,若需严格要求每日非空,可改用 count() 并配合 dropna=False 控制)。

⚠️ 注意事项

  • 若原始数据存在某月中断(如缺某几天),即使起止为月初月末,count 也会小于 days_in_month,该月将被自动剔除——这恰是设计所需;
  • 不建议使用 df.resample('M').sum()(Month End),因其锚点为月末,可能导致跨月对齐偏差;
  • 如需保留原始索引格式(如 PeriodIndex),可在最后用 .set_index(month_index.to_period('M')

    ) 转换。

通过这一模式,你可确保所有输出月度汇总值均基于完整、可比、无截断的数据窗口,显著提升时序聚合结果的严谨性与业务解释力。