如何提高我在用Python填充时间序列和数据列表空缺的性能

1 投票
2 回答
1366 浏览
提问于 2025-04-17 03:26

我有一个时间序列的数据集,数据频率是每秒10次,持续了好几年。光是一年的数据就有大约3.1亿行(每行都有一个时间戳和8个浮点值)。我的数据中有一些空缺,我需要找出来并用'NaN'填补。下面的Python代码可以做到这一点,但性能太差了,处理这些数据的速度远远不够快,我根本无法在合理的时间内完成。

下面是一个简单的示例。我有一个时间序列(time-seris-data)和两个长度相同的数据列表:

series      = [1.1, 2.1, 3.1, 7.1, 8.1, 9.1, 10.1, 14.1, 15.1, 16.1, 20.1]
data_a      = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
data_b      = [1.2, 1.2, 1.2, 2.2, 2.2, 2.2, 2.2, 3.2, 3.2, 3.2, 4.2]

我希望时间序列每次增加1,所以序列中的空缺是4.1、5.1、6.1、11.1、12.1、13.1、17.1、18.1、19.1。data_a和data_b这两个列表应该用浮点数(nan)填充。比如说,data_a应该变成:

[1.2, 1.2, 1.2, nan, nan, nan, 2.2, 2.2, 2.2, 2.2, nan, nan, nan, 3.2, 3.2, 3.2, nan, nan, nan, 4.2]

我用以下方法实现了这个:

d_max = 1.0    # Normal increment in series where no gaps shall be filled
shift = 0

for i in range(len(series)-1):
    diff = series[i+1] - series[i]
    if diff > d_max:
        num_fills = round(diff/d_max)-1    # Number of fills within one gap
        for it in range(num_fills):
            data_a.insert(i+1+it+shift, float(nan))
            data_b.insert(i+1+it+shift, float(nan))
        shift = int(shift + num_fills)     # Shift the index by the number of inserts from the previous gap filling

我还寻找了其他解决这个问题的方法,但只发现了使用find()函数来找出空缺的索引。这个find()函数比我的方法快吗?如果是的话,我该如何更有效地在data_a和data_b中插入NaN呢?

2 个回答

1

如果我没记错的话,往Python列表里插入东西是比较耗费资源的,尤其是当列表变大的时候。

我建议不要把你那些巨大的数据集都加载到内存里,而是用一个生成器函数来逐个处理它们,像这样:

from itertools import izip

series      = [1.1, 2.1, 3.1, 7.1, 8.1, 9.1, 10.1, 14.1, 15.1, 16.1, 20.1]
data_a      = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
data_b      = [1.2, 1.2, 1.2, 2.2, 2.2, 2.2, 2.2, 3.2, 3.2, 3.2, 4.2]

def fillGaps(series,data_a,data_b,d_max=1.0):
  prev = None
  for s, a, b in izip(series,data_a,data_b):
    if prev is not None:
      diff = s - prev
      if s - prev > d_max:
        for x in xrange(int(round(diff/d_max))-1):
          yield (float('nan'),float('nan'))
    prev = s
    yield (a,b)

newA = []
newB = []
for a,b in fillGaps(series,data_a,data_b):
  newA.append(a)
  newB.append(b)

比如,可以把数据读入到izip中,然后再写出来,而不是一个一个地添加到列表里。

4

首先,要明白你最里面的循环其实是没必要的:

for it in range(num_fills):
    data_a.insert(i+1+it+shift, float(nan))

这和下面的代码是一样的:

data_a[i+1+shift:i+1+shift] = [float(nan)] * int(num_fills)

这样做可能会稍微快一点,因为减少了内存分配和移动数据的操作。

然后,对于大规模的数字计算问题,建议你使用 NumPy。虽然学习起来可能需要一些时间,但性能提升会非常显著。可以从这样的代码开始:

import numpy as np

series = np.array([1.1, 2.1, 3.1, 7.1, 8.1, 9.1, 10.1, 14.1, 15.1, 16.1, 20.1])
data_a = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
data_b = [1.2, 1.2, 1.2, 2.2, 2.2, 2.2, 2.2, 3.2, 3.2, 3.2, 4.2]

d_max = 1.0    # Normal increment in series where no gaps shall be filled
shift = 0

# the following two statements use NumPy's broadcasting
# to implicit run some loop at the C level
diff = series[1:] - series[:-1]
num_fills = np.round(diff / d_max) - 1
for i in np.where(diff > d_max)[0]:
    nf = num_fills[i]
    nans = [np.nan] * nf
    data_a[i+1+shift:i+1+shift] = nans
    data_b[i+1+shift:i+1+shift] = nans
    shift = int(shift + nf)

撰写回答