
本文介绍一种精准的 pandas 时间序列处理技巧:使用 `resample('ms')` 结合 `days_in_month` 属性过滤,确保仅对起止日期覆盖整月(即包含该月全部天数)的数据执行月度求和,自动排除首尾不完整的月份。
在实际时间序列分析中,常遇到每日数据跨越多个自然月但不严格对齐月边界的情况(例如从 2022-10-18 开始,到 2024-02-07 结束)。此时若直接使用 df.resample('MS').sum()('MS' 表示 Month Start),Pandas 会将每个日历月的第一天作为分组锚点,并对当月所有可用数据求和——包括仅含部分天数的首月(如 2022-10)和末月(如 2024-02),导致结果不可比、不具统计代表性。
理想方案是:仅保留那些实际数据覆盖了整个月全部天数的月份。核心思路是:对每个重采样后的月份,检查其原始数据记录数是否等于该月应有的总天数(如 2023-02 对应 28 天,2024-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('2022-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') 确保按标准日历月分组(如 '2022-10-01', '2022-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')) 转换。
通过这一模式,你可确保所有输出月度汇总值均基于完整、可比、无截断的数据窗口,显著提升时序聚合结果的严谨性与业务解释力。










