使用不同大小的子列表有效地将大列表转换为填充的NumPy数组

时间:2017-10-29 13:41:42

标签: python arrays numpy

我检索了大量(> 100.000)的数据库时间序列。一个时间序列是具有5到10个条目的2D列表,每个条目包含8个值:

single_time_series = [
       [  43, 1219, 1065,  929, 1233, 2604, 3101, 2196],
       [  70, 1148, 1041,  785, 1344, 2944, 3519, 3506],
       [  80, 1148,  976,  710, 1261, 2822, 3335, 3247],
       [ 103, 1236, 1090,  762, 1024, 2975, 3777, 3093],
       [ 120,  883,  937,  493, 1221, 4119, 5241, 5133],
       [ 143, 1110, 1089,  887, 1420, 2471, 2905, 2845]

] # a time series with 6 entries, each entry represents one day

为了进一步处理,我想将所有这些单独的时间序列放在一个3D numpy数组中。但由于每个系列的长度可能在5到10个条目之间变化,我需要使用零填充数组填充每个短于10的时间序列:

[
  [  43, 1219, 1065,  929, 1233, 2604, 3101, 2196],
  [  70, 1148, 1041,  785, 1344, 2944, 3519, 3506],
  [  80, 1148,  976,  710, 1261, 2822, 3335, 3247],
  [ 103, 1236, 1090,  762, 1024, 2975, 3777, 3093],
  [ 120,  883,  937,  493, 1221, 4119, 5241, 5133],
  [ 143, 1110, 1089,  887, 1420, 2471, 2905, 2845],
  [   0,    0,    0,    0,    0,    0,    0,    0],
  [   0,    0,    0,    0,    0,    0,    0,    0],
  [   0,    0,    0,    0,    0,    0,    0,    0],
  [   0,    0,    0,    0,    0,    0,    0,    0]
]

目前我通过迭代来自数据库的每个时间序列,填充它并将其附加到最终的numpy数组来实现这一目标:

MAX_SEQUENCE_LENGTH = 10
all_time_series = ... # retrieved from db

all_padded_time_series = np.array([], dtype=np.int64).reshape(0, MAX_SEQUENCE_LENGTH, 8) 

for single_time_series in all_time_series:
  single_time_series = np.array(single_time_series, dtype=np.int64)

  length_diff = MAX_SEQUENCE_LENGTH - single_time_series.shape[0]

  if length_diff > 0:
    single_time_series = np.pad(single_time_series, ((0, length_diff), (0,0)), mode='constant')

  all_padded_time_series = np.append(all_padded_time_series, [single_time_series], axis=0)

当数据库请求在几秒钟内执行时,填充和追加操作将永远占用 - 脚本需要约45分钟,在我的iMac上需要~100,000个时间序列。

由于数据库不断增长,我需要在不久的将来分析更多数据。所以我正在寻找一种更快的方法将来自db的列表转换为numpy数组。我很确定有更有效的方法可以做到这一点 - 任何想法?

2 个答案:

答案 0 :(得分:0)

import numpy as np
ll = [[43, 1219, 1065, 929, 1233, 2604, 3101, 2196],
      [70, 1148, 1041, 785, 1344, 2944, 3519, 3506],
      [80, 1148, 976, 710, 1261, 2822, 3335, 3247],
      [103, 1236, 1090, 762, 1024, 2975, 3777, 3093],
      [120, 883, 937, 493, 1221, 4119, 5241, 5133],
      [143, 1110, 1089, 887, 1420, 2471, 2905, 2845]] 
      # your input list of lists from a database

def a(l):
    a = np.zeros((10, 8), dtype=np.int64)
    np.copyto(a[0:len(l), 0:8], l)
    return a 
    # my solution for this problem.
    # this solution initializes nd-array at a time,
    # so this may enable to rid the re-creation cost of nd-array.

def b(l):
    a = np.array(l, dtype=np.int64)
    len_diff = 10 - a.shape[0]
    return np.pad(a, ((0, len_diff), (0, 0)), mode='constant') 
    # your solution for this problem

我想分析和比较这些代码,但分析不能很好地工作(因为cpu缓存)。

答案 1 :(得分:0)

您有两位主要时间消费者 - np.appendnp.pad。此追加每次都会创建一个新数组。它不像list.append那样增长列表。 pad可以,但更通用的是你所需要的,因此更慢。

由于您知道目标尺寸,因此请立即制作零填充数组并复制列表

all_padded_time_series = np.zeros((len(all_time_series, MAX_SEQUENCE_LENGTH, 8), dtype=np.int64)

for i, single_time_series in enumerate(all_time_series):
  single_time_series = np.array(single_time_series, dtype=np.int64)
  all_padded_time_series[i, :single_time_series.shape[0], :] = single_time_series   

或让副本转换为数组:

for i, single_time_series in enumerate(all_time_series):
  all_padded_time_series[i, :len(single_time_series), :] = single_time_series   

评论链接到@Divakar的良好解决方案。它使用掩码一次将所有组件数组复制到目标。如上所述,它假设组件是1d,但它可以适应这个2d情况。但逻辑更难理解和记忆(即使我已经多次重新创建它)。

itertools.zip_longest对于加入不同长度的列表也很有用,但在这个2d情况下它并不能很好地工作。

In [269]: alist = [(np.ones((i,4),int)*i).tolist() for i in range(1,5)]
In [270]: alist
Out[270]: 
[[[1, 1, 1, 1]],
 [[2, 2, 2, 2], [2, 2, 2, 2]],
 [[3, 3, 3, 3], [3, 3, 3, 3], [3, 3, 3, 3]],
 [[4, 4, 4, 4], [4, 4, 4, 4], [4, 4, 4, 4], [4, 4, 4, 4]]]
In [271]: res = np.zeros((4,4,4),int)
In [272]: for i,x in enumerate(alist):
     ...:     res[i,:len(x),:] = x
     ...:     
In [273]: res
Out[273]: 
array([[[1, 1, 1, 1],
        [0, 0, 0, 0],
        [0, 0, 0, 0],
        [0, 0, 0, 0]],

       [[2, 2, 2, 2],
        [2, 2, 2, 2],
        [0, 0, 0, 0],
        [0, 0, 0, 0]],

       [[3, 3, 3, 3],
        [3, 3, 3, 3],
        [3, 3, 3, 3],
        [0, 0, 0, 0]],

       [[4, 4, 4, 4],
        [4, 4, 4, 4],
        [4, 4, 4, 4],
        [4, 4, 4, 4]]])

改编Numpy: Fix array with rows of different lengths by filling the empty elements with zeros

mask的计算方法为:

In [291]: mask = np.arange(4)<np.array([len(x) for x in alist])[:,None]
In [292]: mask
Out[292]: 
array([[ True, False, False, False],
       [ True,  True, False, False],
       [ True,  True,  True, False],
       [ True,  True,  True,  True]], dtype=bool)

实际上它会选择res[0,:1,:], res[1,:2,:], etc;我们可以通过查看上述计算中的res进行验证:

In [293]: res[mask]
Out[293]: 
array([[1, 1, 1, 1],
       [2, 2, 2, 2],
       [2, 2, 2, 2],
       [3, 3, 3, 3],
       [3, 3, 3, 3],
       [3, 3, 3, 3],
       [4, 4, 4, 4],
       [4, 4, 4, 4],
       [4, 4, 4, 4],
       [4, 4, 4, 4]])

我们可以通过将列表连接成一个长的2d数组来获得相同的2d数组:

In [294]: arr = np.concatenate(alist, axis=0)

因此使用以下方式进行蒙版赋值:

In [295]: res[mask] = arr

mask计算更难以可视化和记忆。