我检索了大量(> 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数组。我很确定有更有效的方法可以做到这一点 - 任何想法?
答案 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.append
和np.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
计算更难以可视化和记忆。