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

如何在Pandas中将起始时间不同的时间序列转换为相对起始偏移?

如何将Pandas DataFrame各列的第一个非空值移至首行,NaN移至末尾?

问题描述

我有一个存储月度数值的Pandas DataFrame,每列对应一条独立的时间序列,但各序列的起始月份不一样,起始前的数值都是NaN,示例代码和输出如下:

import pandas as pd
import numpy as np

df = pd.DataFrame({ 
    'a': [np.nan, np.nan, 1, 2, 3, 4], 
    'b': [2, 4, 6, 8, 10, 12], 
    'c': [np.nan, -1, -2, -3, np.nan, -5]
}, index=pd.date_range('2018-01', '2018-06', freq='MS') )

输出:

a   b     c
2018-01-01 NaN   2   NaN
2018-02-01 NaN   4  -1.0
2018-03-01 1.0   6  -2.0
2018-04-01 2.0   8  -3.0
2018-05-01 3.0  10   NaN
2018-06-01 4.0  12  -5.0

我想要把每列的第一个非空值移到首行,把原来的NaN都推到列的末尾。目前我用的代码是:

result = df.reset_index(drop=True).apply(lambda c: c.shift(-c.first_valid_index()))

得到的结果符合预期:

a   b     c
0  1.0   2  -1.0
1  2.0   4  -2.0
2  3.0   6  -3.0
3  4.0   8   NaN
4  NaN  10  -5.0
5  NaN  12   NaN

想请教下:Pandas里有没有更原生的内置方法或者更符合Pandas风格的实现方式?这个操作有没有专门的名称?


解答

操作名称

这个操作一般叫做**"序列起始对齐",或者更直白点说就是"将每列非空值向前平移至首行"**。Pandas并没有专门的内置函数直接完成这个操作,但确实有几种更高效、更贴合Pandas惯用写法的实现方式。

方法1:简化你的原始思路

其实你的思路已经很清晰了,甚至可以再简化一点——不需要reset_index(drop=True),直接在原DataFrame上操作就行,因为shift本身就是按列处理的:

result = df.apply(lambda col: col.shift(-col.first_valid_index()))

这个写法更简洁,结果和你的代码完全一致。不过apply本质是逐列循环,在处理超大DataFrame的时候效率会有点受限,下面给你两个更高效的方案。

方法2:向量化实现(基于Numpy)

用Numpy的索引操作一次性搞定所有列,完全避开循环,速度快很多:

import numpy as np

# 先拿到每列第一个非空值的位置
first_valid_pos = df.notna().idxmax(axis=0)
# 把日期索引转换成对应的整数位置
shift_counts = df.index.get_indexer(first_valid_pos)

# 生成一个二维索引矩阵:每列的行索引 = 原行号 - 该列需要平移的步数
row_indices = np.arange(len(df))[:, None] - shift_counts.values
# 把超出范围的索引限制在合法范围内(越界的位置最后会被赋值成NaN)
valid_indices = row_indices.clip(0, len(df)-1)
# 用Numpy索引直接取值,再转回DataFrame
result = pd.DataFrame(
    df.to_numpy()[valid_indices, np.arange(df.shape[1])],
    columns=df.columns
)

这个方法在数据量很大的时候优势特别明显,比apply快好几倍都有可能。

方法3:用stack/unstack重塑数据

另一种很有Pandas风格的方法,通过堆叠-分组-重塑的流程来实现:

# 把DataFrame堆叠成长格式,自动去掉NaN
stacked = df.stack(dropna=True)
# 重置列索引,给每个列的非空值按顺序分配新的行号
stacked = stacked.reset_index(level=1, drop=False)
stacked['new_row'] = stacked.groupby('level_1').cumcount()
# 重新展开成宽格式,缺失的位置自动填充NaN
result = stacked.pivot(
    index='new_row', 
    columns='level_1', 
    values=0
).rename_axis(None, axis=1)

这个方法逻辑很直观,不需要搞复杂的索引计算,适合中等规模的数据处理。

总结一下

  • 如果数据量小,你的原始方法或者简化版apply就足够简洁好用;
  • 处理大数据集的话,优先选基于Numpy的向量化方法;
  • 要是你更喜欢Pandas的重塑操作,stack/unstack的方式可读性很强。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.29 08:58:15