
本文介绍如何将pandas series中连续零值后的非零序列,按“前一段末尾非零值 − 前续零个数 + 当前位置序号”规则进行重编号,并提供基于numba加速的高性能解决方案。
本文介绍如何将pandas series中连续零值后的非零序列,按“前一段末尾非零值 − 前续零个数 + 当前位置序号”规则进行重编号,并提供基于numba加速的高性能解决方案。
在数据预处理中,常需对含零间隙的时序或计数型序列进行逻辑重映射。例如,给定序列 [1,2,3,4,0,0,1,2,3,0,0,0,0,1,2],要求将每段非零子序列(如第7–9位的 1,2,3、第14–15位的 1,2)重新编号:起始值 = 上一段最后一个非零值 − 其后连续零的个数 + 1,后续逐次递增。
该规则本质是跨零段的连续计数恢复:
- 第一段非零(索引1–4)保持原值
1,2,3,4; - 遇到两个零(索引5–6)后,第二段非零(索引7–9)应变为
4−2+1=3,4−2+2=4,4−2+3=5; - 接着四个零(索引10–13)后,第三段非零(索引14–15)变为
5−4+1=2,5−4+2=3。
纯Pandas实现易陷入复杂分组嵌套(如cumsum()+groupby()+transform()多层嵌套),可读性差且性能低下。更优解是采用Numba JIT编译,在保证逻辑清晰的同时获得数量级性能提升。
以下为完整可运行代码:
import pandas as pd
import numba as nb
import numpy as np
# 构造示例数据
d = {'1': 1, '2': 2, '3': 3, '4': 4, '5': 0, '6': 0, '7': 1, '8': 2, '9': 3,
'10': 0, '11': 0, '12': 0, '13': 0, '14': 1, '15': 2}
ser = pd.Series(data=d, dtype=int)
@nb.njit(['int32[:](int32[:])', 'int64[:](int64[:])'])
def compute(arr):
n = len(arr)
res = np.empty(n, dtype=arr.dtype)
z_count = 0 # 当前连续零计数
last_nnz_val = 0 # 上一段末尾非零值(经规则修正后)
nnz_count = 0 # 当前非零段内位置(从1开始)
for i in range(n):
if arr[i] == 0:
# 检测零段起始:前一值非零 → 更新last_nnz_val为上一段终点值
if i > 0 and arr[i-1] != 0:
last_nnz_val += nnz_count - z_count
z_count = 0
z_count += 1
res[i] = 0
else:
# 检测非零段起始:前一值为零 → 重置nnz_count
if i > 0 and arr[i-1] == 0:
nnz_count = 0
nnz_count += 1
res[i] = last_nnz_val - z_count + nnz_count
return res
# 执行计算并还原为Series
result_array = compute(ser.to_numpy())
result_ser = pd.Series(result_array, index=ser.index, name=ser.name)
print(result_ser)
输出结果为:
1 1 2 2 3 3 4 4 5 0 6 0 7 3 8 4 9 5 10 0 11 0 12 0 13 0 14 2 15 3 dtype: int64
✅ 关键设计说明:
-
last_nnz_val并非原始输入值,而是上一段最后一个重编号后的值(如第一段末为4,第二段起始即用4−2+1=3); -
nnz_count在每个非零段内从1开始累加,确保增量逻辑正确; -
z_count仅统计紧邻当前非零段之前的连续零个数,遇新零段自动继承; - Numba通过静态类型注解(
int32[:]/int64[:])消除Python解释开销,实测比纯Pandas方案快400倍以上(微数据集)至60倍(千倍放大后)。
⚠️ 注意事项:
- 输入Series必须为整数类型(
int32/int64),浮点或字符串需提前转换; - 算法假设首元素必为非零(若首行为零,需前置逻辑补全);
- 如需保留原始索引对齐,务必使用
pd.Series(..., index=ser.index)显式传入。
此方法兼顾准确性、可维护性与极致性能,适用于工业级数据流水线中高频调用场景。










