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

如何按年/月拆分时间序列DataFrame,支持闰年补NaN

如何将小时级时间序列数据按年份拆分到不同列,自动处理闰年填充NaN?

我有一个包含2015至2020年小时级数据的时间序列DataFrame,想要创建新的DataFrame,把每年的时间序列值作为单独列来分别分析。因为存在闰年,要求各列共享同一索引,非闰年的2月29日位置自动填充NaN。

我之前尝试通过创建month和day_of_month列,用merge来实现,但结果索引混乱,数据量从约4万条膨胀到数百万条,内存占用超20GB后程序崩溃,代码及错误结果如下:

years = pd.DataFrame(index=pd.date_range('2016-01-01', '2017-01-01', freq='1H'))
years['month'] = years.index.month
years['day_of_month'] = years.index.day
gp = data_md[['value', 'month', 'day_of_month']].groupby(pd.Grouper(freq='1Y'))
for name, group in gp:
    years = years.merge(group, right_on=['month', 'day_of_month'], left_on=['month', 'day_of_month'])

运行结果:

month   day_of_month    value
0   1   1   0
1   1   1   6
2   1   1   2
3   1   1   0
4   1   1   1
...     ...     ...     ...
210259  12  31  6
210260  12  31  2
210261  12  31  4
210262  12  31  5
210263  12  31  1

原始DataFrame示例

核心仅需value列,其余为附加信息:

value    month  day_of_month    week    day_name    year    hour    season  dailyp  day_of_week     ...     hourly_no_noise     daily_trend     daily_seasonal  daily_residuals     daily_no_noise  daily_trend_h   daily_seasonal_h    daily_residuals_h   daily_no_noise_h    Total
date                                                                                    
2015-01-01 00:00:00     0   1   1   1   Thursday    2015    0   Invierno    165.0   3   ...     NaN     NaN     -9.053524   NaN     NaN     NaN     -3.456929   NaN     NaN     6436996.0
2015-01-01 01:00:00     6   1   1   1   Thursday    2015    1   Invierno    NaN     3   ...     NaN     NaN     -9.053524   NaN     NaN     NaN     -4.879983   NaN     NaN     NaN
2015-01-01 02:00:00     2   1   1   1   Thursday    2015    2   Invierno    NaN     3   ...     NaN     NaN     -9.053524   NaN     NaN     NaN     -5.895367   NaN     NaN     NaN
2015-01-01 03:00:00     0   1   1   1   Thursday    2015    3   Invierno    NaN     3   ...     NaN     NaN     -9.053524   NaN     NaN     NaN     -6.468616   NaN     NaN     NaN
2015-01-01 04:00:00     1   1   1   1   Thursday    2015    4   Invierno    NaN     3   ...     NaN     NaN     -9.053524   NaN     NaN     NaN     -6.441830   NaN     NaN     NaN
...     ...     ...     ...     ...     ...     ...     ...     ...     ...     ...     ...     ...     ...     ...     ...     ...     ...     ...     ...     ...     ...
2019-12-31 19:00:00     6   12  31  1   Tuesday     2019    19  Invierno    NaN     1   ...     11.529465   230.571429  -4.997480   -11.299166  237.299166  9.613095    2.805720    1.176491    17.823509   NaN
2019-12-31 20:00:00     3   12  31  1   Tuesday     2019    20  Invierno    NaN     1   ...     11.314857   230.571429  -4.997480   -11.299166  237.299166  9.613095    2.928751    1.176491    17.823509   NaN
2019-12-31 21:00:00     3   12  31  1   Tuesday     2019    21  Invierno    NaN     1   ...     10.141139   230.571429  -4.997480   -11.299166  237.299166  9.613095    1.774848    1.176491    17.823509   NaN
2019-12-31 22:00:00     3   12  31  1   Tuesday     2019    22  Invierno    NaN     1   ...     8.823152    230.571429  -4.997480   -11.299166  237.299166  9.613095    0.663344    1.176491    17.823509   NaN
2019-12-31 23:00:00     6   12  31  1   Tuesday     2019    23  Invierno    NaN     1   ...     6.884636    230.571429  -4.997480   -11.299166  237.299166  9.613095    -1.624980   1.176491    17.823509   NaN

期望结果示例

按年份拆分后的DataFrame:

2015      2016      2017      2018      2019
2016-01-01 00:00:00  0.074053  0.218161  0.606810  0.687365  0.352672
2016-01-01 01:00:00  0.465167  0.210297  0.722825  0.683341  0.885175
2016-01-01 02:00:00  0.175964  0.610560  0.722479  0.016842  0.205916
2016-01-01 03:00:00  0.945955  0.807490  0.627525  0.187677  0.535116
2016-01-01 04:00:00  0.757608  0.797835  0.639215  0.455989  0.042285
...                       ...       ...       ...       ...       ...
2016-12-30 20:00:00  0.046138  0.139100  0.397547  0.738687  0.335306
2016-12-30 21:00:00  0.672800  0.802090  0.617625  0.787601  0.007535
2016-12-30 22:00:00  0.698141  0.776686  0.423712  0.667808  0.298338
2016-12-30 23:00:00  0.198089  0.642073  0.586527  0.106567  0.514569
2016-12-31 00:00:00  0.367572  0.390791  0.105193  0.592167  0.007365

非闰年的2月29日位置自动填充NaN:

df['2016-02']
                         2015      2016      2017      2018      2019
2016-02-01 00:00:00  0.656703  0.348784  0.383639  0.208786  0.183642
2016-02-01 01:00:00  0.488729  0.909498  0.873642  0.122028  0.547563
2016-02-01 02:00:00  0.210427  0.912393  0.505873  0.085149  0.358841
2016-02-01 03:00:00  0.281107  0.534750  0.622473  0.643611  0.258437
2016-02-01 04:00:00  0.187434  0.327459  0.701008  0.887041  0.385816
...                       ...       ...       ...       ...       ...
2016-02-29 19:00:00       NaN  0.742402       NaN       NaN       NaN
2016-02-29 20:00:00       NaN  0.013419       NaN       NaN       NaN
2016-02-29 21:00:00       NaN  0.517194       NaN       NaN       NaN
2016-02-29 22:00:00       NaN  0.003136       NaN       NaN       NaN
2016-02-29 23:00:00       NaN  0.128406       NaN       NaN       NaN

解决方案

方法一:利用pivot_table快速转换

核心思路是给每条数据标记“年中时间戳”(将原日期年份替换为基准闰年2016),再通过pivot_table将年份转为列,自动对齐索引并填充NaN:

import pandas as pd

# 1. 生成年中时间戳:保留月日时分,年份替换为2016(闰年)
data_md['doy_hour'] = data_md.index.map(lambda x: x.replace(year=2016))

# 2. 透视转换:年中时间戳为索引,年份为列,提取value值
result = data_md.pivot_table(index='doy_hour', columns='year', values='value', aggfunc='first')

# 3. 清理索引和列名称(可选)
result.index.name = None
result.columns.name = None

方法二:分组+重索引实现

如果原始数据没有year列,可先提取年份,再按年份分组后逐个重索引到基准闰年的小时序列:

# 1. 定义基准索引:2016年全年小时级时间序列(包含2月29日)
base_index = pd.date_range('2016-01-01', '2017-01-01', freq='1H')

# 2. 按年份分组,每个分组重索引到基准索引
groups = []
for year, group in data_md.groupby(data_md.index.year):
    # 将分组索引替换为基准年份的对应日期
    group.index = group.index.map(lambda x: x.replace(year=2016))
    # 重索引到基准序列,缺失值自动填充NaN
    groups.append(group['value'].reindex(base_index).rename(year))

# 3. 合并所有分组为一个DataFrame
result = pd.concat(groups, axis=1)

关键说明

  • 两种方法都避免了merge导致的笛卡尔积问题,内存占用与原数据量级相当
  • 自动处理闰年:非闰年无2月29日数据,重索引后对应位置自动填充NaN
  • 如需按月拆分,只需将基准索引改为对应月份的小时序列,分组时按(year, month)分组即可

内容的提问来源于stack exchange,提问作者Apollo

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 02:50:35