【问题标题】:numpy time series merge and fill missing values with earlier valuesnumpy 时间序列合并并用较早的值填充缺失值
【发布时间】:2025-11-30 03:30:02
【问题描述】:

我的数据如下所示:

timedelta64 1, temp1A, temp 1B, temp1C, ...
timedelta64 2, temp2A, temp 2B, temp2C, ...

数据被摄取到两个 numpy 数组中:

  1. 一系列时间戳raw_timestampdtype=[('datetime', '<M8[s]')]

    '2009-01-01T18:41:00', 
    '2009-01-01T18:44:00',
    '2009-01-01T18:46:00', 
    '2009-01-01T18:47:00', 
    
  2. 传感器数据表raw_sensordtype=[ ('sensorA', '<u4'), ('sensorB', '<u4'), ('sensorC', '<u4'), ('sensorD', '<u4'), ('sensorE', '<u4'), ('sensorF', '<u4'), ('sensorG', '<u4'), ('sensorH', '<u4'), ('signal', '<u4')]

     (755, 855, 755, 855, 743, 843, 743, 843, 2),
     (693, 793, 693, 793, 693, 793, 693, 793, 1),
     (755, 855, 755, 855, 743, 843, 743, 843, 2),
     (693, 793, 693, 793, 693, 793, 693, 793, 1),
    

我生成一个新的filled_timestamp 并在每个时间步的每一行填充时间戳:filled_timestamp = np.arange(np.datetime64(starttime), np.datetime64(endtime), np.timedelta64(interval))

使用idxs = np.in1d(filled_timestamp,raw_timestamp),我有filled 的所有索引与raw 的时间戳相匹配。所以我可以用来自raw_sensor的匹配数据分配filled_sensor

filled_sensor[idxs] = raw_sensor

第一季度。有没有更好/更快的方法来交叉这些?

现在filled 数组看起来像:

>>> filled_timestamp, filled_sensor # shown side-by-side for convenience 
    array([ 
      1 #  ('2009-01-01T18:41:00')  (755, 855, 755, 855, 743, 843, 743, 843, 2),
      2 #  ('2009-01-01T18:42:00')  (0, 0, 0, 0, 0, 0, 0, 0, 0),
      3 #  ('2009-01-01T18:43:00')  (0, 0, 0, 0, 0, 0, 0, 0, 0),
      4 #  ('2009-01-01T18:44:00')  (693, 793, 693, 793, 693, 793, 693, 793, 1),
      5 #  ('2009-01-01T18:45:00')  (0, 0, 0, 0, 0, 0, 0, 0, 0),
      6 #  ('2009-01-01T18:46:00')  (693, 793, 693, 793, 693, 793, 693, 793, 1),
      7 #  ('2009-01-01T18:47:00')  (693, 793, 693, 793, 693, 793, 693, 793, 1)
       ],
          dtype=[('datetime', '<M8[s]')], [('sensorA', '<u4'), ('sensorB', '<u4'), ('sensorC', '<u4'), ('sensorD', '<u4'), ('sensorE', '<u4'), ('sensorF', '<u4'), ('sensorG', '<u4'), ('sensorH', '<u4'), ('signal', '<u4')]

第二季度。如何用前一个非空行中的值填充缺失的行?除了列(0 和 3 和最后一个),它是 0 用于填充

在我上面的例子中:

第 2 行和第 3 行将从第 1 行获取值,

第 5 行将从第 4 行获取值

最终结果:

>>> filled_timestamp, filled_sensor # shown side-by-side for convenience 
    array([ 
      1 #  ('2009-01-01T18:41:00')  (755, 855, 755, 855, 743, 843, 743, 843, 2),
      2 #  ('2009-01-01T18:42:00')  (0, 855, 755, 0, 743, 843, 743, 843, 0),
      3 #  ('2009-01-01T18:43:00')  (0, 855, 755, 0, 743, 843, 743, 843, 0),
      4 #  ('2009-01-01T18:44:00')  (693, 793, 693, 793, 693, 793, 693, 793, 1),
      5 #  ('2009-01-01T18:45:00')  (0, 793, 693, 0, 693, 793, 693, 793, 0),
      6 #  ('2009-01-01T18:46:00')  (693, 793, 693, 793, 693, 793, 693, 793, 1),
      7 #  ('2009-01-01T18:47:00')  (693, 793, 693, 793, 693, 793, 693, 793, 1)
       ],
          dtype=[('datetime', '<M8[s]')], [('sensorA', '<u4'), ('sensorB', '<u4'), ('sensorC', '<u4'), ('sensorD', '<u4'), ('sensorE', '<u4'), ('sensorF', '<u4'), ('sensorG', '<u4'), ('sensorH', '<u4'), ('signal', '<u4')]

【问题讨论】:

  • 您的数据有多大?
  • 数据大小为千兆字节 (GB)
  • 您是否考虑将时间拆分为一个单独的数组?
  • 是的,如果数组的尺寸不会改变。由于不依赖时间戳本身,我可以稍后拆分和重组。
  • 然后您可以使用类型为 timestamp64 的单个 t 轴和行 x 传感器 2D 阵列,而无需使用场阵列。这会让生活更轻松。

标签: python numpy


【解决方案1】:

交叉口

对于快速路口,您最好的选择可能是np.searchsorted。它将在filled_timestamp 中对raw_timestamp 的元素进行二分搜索:

idx = np.searchsorted(filled_timestamp, raw_timestamp)

这只有在raw_timestamp 的每个元素都实际出现在filled_timestamp 中时才是准确的,因为无论如何np.searchsorted 都会返回一个插入索引。

非矢量化解决方案

您想将filled_sensor 的一部分从idx[n]idx[n + 1] 设置为raw_sensor[n] 的值:

from itertools import zip_longest
for start, end, row in zip_longest(idx, idx[1:], raw_sensor):
    filled_sensor[start:end] = row

我在这里使用zip_longest,因此来自idx[1:] 的最后一个值将是None,使得最后一个切片等效于filled_sensor[idx[-1]:],而不需要特殊条件。

矢量化解

如果您知道要从 raw_sensor 重复哪些索引,则可以直接从 raw_sensor 一次性创建 filled_sensor。您可以通过将np.cumsum 应用于idx 转换为布尔数组来获取该信息:

idx_mask = np.zeros(filled_timestamp.shape, np.bool)
idx_mask[idx] = True

基本上,我们从一个与filled_timestamp 大小相同的布尔数组开始,即True (1),只要匹配来自raw_timestamp 的条目。我们可以将其转换为raw_timestamp 中的索引,方法是计算到该点为止发生的总匹配数:

indexes = np.cumsum(idx_mask) - 1

请记住,indexes 是一个整数数组,而不是布尔值。每当找到新匹配时,它都会增加。 - 1 从计数转换为索引,因为第一个匹配项的计数为 1 而不是 0。

现在你可以直接filled_sensor:

filled_sensor = raw_sensor[indexes]

这里唯一可能的警告是filled_sensor[0] 不是来自raw_sensor[0]。然后它将被替换为raw_sensor[-1]。考虑到您如何根据rawfilled 中构建时间,我不确定这是否会成为问题。

示例

这是交集矢量化解决方案步骤的示例,其中包含您在问题中显示的数据。

我们开始

raw_timestamp = np.array(['2009-01-01T18:41:00', 
                          '2009-01-01T18:44:00',
                          '2009-01-01T18:46:00',
                          '2009-01-01T18:47:00',], dtype='datetime64[s]')
raw_sensor = np.array([(755, 855, 755, 855, 743, 843, 743, 843, 2),
                       (693, 793, 693, 793, 693, 793, 693, 793, 1),
                       (755, 855, 755, 855, 743, 843, 743, 843, 2),
                       (693, 793, 693, 793, 693, 793, 693, 793, 1),],
                      dtype=[('sensorA', '<u4'), ('sensorB', '<u4'),
                             ('sensorC', '<u4'), ('sensorD', '<u4'),
                             ('sensorE', '<u4'), ('sensorF', '<u4'),
                             ('sensorG', '<u4'), ('sensorH', '<u4'),
                             ('signal', '<u4')])

我们可以生成filled_timestamp

filled_timestamp = np.arange('2009-01-01T18:41:00',
                             '2009-01-01T18:48:00', 60, dtype='datetime64[s]')

正如预期的那样:

array(['2009-01-01T18:41:00', '2009-01-01T18:42:00', '2009-01-01T18:43:00',
       '2009-01-01T18:44:00', '2009-01-01T18:45:00', '2009-01-01T18:46:00',
       '2009-01-01T18:47:00'], dtype='datetime64[s]')

我对@9​​87654363@ 有点随意,将时间戳设置为普通数组而不是结构化数组,但我认为这对您的目的没有影响。

  1. idx = np.searchsorted(filled_timestamp, raw_timestamp) 产量

    idx = np.array([0, 3, 5, 6], dtype=np.int)
    

    这意味着filled_timestamp 中的索引0, 3, 5, 6 匹配来自raw_timestamp 的值。

  2. idx_mask 然后变成

    idx_mask = np.array([True, False, False, True, False, True, True], dtype=np.bool)
    

    这基本上是idx 的同义词,除了扩​​展为与filled_timestamp 相同大小的布尔掩码。

  3. 现在是棘手的部分:indexes = np.cumsum(idx_mask) - 1:

    indexes = array([0, 0, 0, 1, 1, 2, 3], dtype=np.int)
    

    这可以解释如下:filled_sensor[0:3] 应该来自raw_sensor[0]filled_sensor[3:5] 应该来自raw_sensor[1]filled_sensor[5] 应该来自raw_sensor[2]filled_sensor[6] 应该来自raw_sensor[3]

  4. 所以现在我们使用indexes直接提取raw_sensor的正确元素使用filled_sensor = raw_sensor[indexes]

    np.array([(755, 855, 755, 855, 743, 843, 743, 843, 2),
              (755, 855, 755, 855, 743, 843, 743, 843, 2),
              (755, 855, 755, 855, 743, 843, 743, 843, 2),
              (693, 793, 693, 793, 693, 793, 693, 793, 1),
              (693, 793, 693, 793, 693, 793, 693, 793, 1),
              (755, 855, 755, 855, 743, 843, 743, 843, 2),
              (693, 793, 693, 793, 693, 793, 693, 793, 1)], 
             dtype=[('sensorA', '<u4'), ('sensorB', '<u4'),
                    ('sensorC', '<u4'), ('sensorD', '<u4'),
                    ('sensorE', '<u4'), ('sensorF', '<u4'),
                    ('sensorG', '<u4'), ('sensorH', '<u4'),
                    ('signal', '<u4')])    
    

【讨论】:

  • 缺少将掩码的 T / F 转换为索引数组的步骤,其中 T 是索引,F 是 0,然后 np.cumsum 可以正常工作。仍在研究如何做到这一点以及在填充字段中屏蔽 0。
  • 抱歉,我的更新展示了如何更快地进行相交。现在所有的步骤都在那里。我建议你打印出所有的中间步骤,看看它们到底做了什么。
  • 屏蔽填充字段中的 0 可以通过反转 idx 来获取所有错误值的索引,例如 filled_sensor[fidx][[0] = 0filled_sensor[fidx][[3] = 0filled_sensor[fidx][[1] = 0
  • 我假设idxfilled_* 中的数字(不是布尔掩码)索引。 idx_mask 的大小与filled_* 相同,并且在所有idx 位置都有True。如果cumsum 遇到False (0),则cumsum 沿着该数组复制前一个值,并在遇到True (1) 时加一。 Python 和 numpy 布尔值也是整数。这将创建一个索引列表到raw_*,这将创建filled_*。只要filled_* 中的条目与raw_* 中的条目不匹配,此列表中就会出现重复,就像您正在寻找的一样。
  • 您可以将~idx_mask 用于fidx。这正是你想要的面具。