如何在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
相关产品推荐
相关产品推荐

