从numpy矩阵中提取子矩阵的快速方法

Fast way to extract submatrix from numpy matrix

我有一个巨大的 .csv 文件 (~2GB),我使用 read_csv 将其导入到我的程序中,然后使用 as_matrix 将其转换为一个 numpy 矩阵。生成的矩阵的形式类似于下面给定示例中的 data_mat。我现在的问题是,我需要提取具有相同 uuid4 的块(矩阵第一列中的条目)。然后由另一个函数处理子矩阵。看来我下面的例子并不是最好的方法。欢迎使用更快的方法。

import numpy as np
data_mat = np.array([['f9f1dc71-9457-4d17-b5d1-e63b5a766f84', 4, 3, 1],\
                     ['f9f1dc71-9457-4d17-b5d1-e63b5a766f84', 3, 1, 1],\
                     ['f9f1dc71-9457-4d17-b5d1-e63b5a766f84', 3, 3, 1],\
                     ['f9f1dc71-9457-4d17-b5d1-e63b5a766f84', 6, 1, 1],\
                     ['f35fb25b-dddc-458a-9f71-0a9c2c202719', 3, 4, 1],\
                     ['f35fb25b-dddc-458a-9f71-0a9c2c202719', 3, 1, 1],\
                     ['a4cf92fc-0624-4a00-97f6-0d21547e3183', 3, 2, 1],\
                     ['a4cf92fc-0624-4a00-97f6-0d21547e3183', 3, 9, 0],\
                     ['a4cf92fc-0624-4a00-97f6-0d21547e3183', 3, 1, 0],\
                     ['a4cf92fc-0624-4a00-97f6-0d21547e3183', 5, 1, 1],\
                     ['a4cf92fc-0624-4a00-97f6-0d21547e3183', 3, 1, 1],\
                     ['d3a8a9d0-4380-42e3-b35f-733a9f9770da', 3, 6, 10]],dtype=object)

unique_ids, indices = np.unique(data_mat[:,0],return_index=True,axis=None)
length = len(data_mat)
i=0
for idd in unique_ids:
    index = indices[i]
    k=0
    while ((index+k)<length and idd == data_mat[index+k,0]):        
        k+=1
    tmp_mat=data_mat[index:(index+k),:]
    # do something with tmp_mat ...
    print(tmp_mat)
    i+=1

优化这个想法就是在我们进入循环后最小化计算。因此,考虑到这一点,我们将重新排列数组的行,按第一列排序。然后,获取定义边界的索引。最后,开始我们的循环并简单地对每个组进行切片以在每次迭代时得到一个子矩阵。使用数组时,切片实际上是免费的,所以这应该对我们有所帮助。

因此,一种实现方式是 -

a0 = data_mat[:,0]
sidx = a0.argsort()
sd = data_mat[sidx] # sorted data_mat
idx = np.flatnonzero(np.concatenate(( [True], sd[1:,0] != sd[:-1,0], [True] )))
for i,j in zip(idx[:-1], idx[1:]):
    tmp_mat = sd[i:j]
    print tmp_mat

如果您希望将每个子矩阵存储为数组以将数组列表作为最终输出,只需执行 -

[sd[i:j] for i,j in zip(idx[:-1], idx[1:])]

排序data_mat

对于示例中 data_mat 已经排序的情况,我们可以避免对整个数组进行排序并直接使用第一列,例如 -

a0 = data_mat[:,0]
idx = np.flatnonzero(np.concatenate(( [True], a0[1:] != a0[:-1], [True] )))

for i,j in zip(idx[:-1], idx[1:]):
    tmp_mat = data_mat[i:j]
    print(tmp_mat)

同样,要将所有这些子矩阵作为数组列表,请使用 -

[data_mat[i:j] for i,j in zip(idx[:-1], idx[1:])]

请注意,我们将使用此方法获得的子矩阵的顺序与之前方法中完成的排序不同。


排序的基准测试 data_mat

接近 -

# @Daniel F's soln-2
def split_app(data_mat):
    idx = np.flatnonzero(data_mat[1:, 0] != data_mat[:-1, 0]) + 1
    return np.split(data_mat, idx)

# Proposed in this post
def zip_app(data_mat):
    a0 = data_mat[:,0]
    idx = np.flatnonzero(np.concatenate(( [True], a0[1:] != a0[:-1], [True] )))
    return [data_mat[i:j] for i,j in zip(idx[:-1], idx[1:])]

计时 -

在示例中,我们有一个最大长度的子矩阵 6。所以,让我们扩展到一个更大的案例,保持相同的模式 -

In [442]: a = np.random.randint(0,100000,(6*100000,4)); a[:,0].sort()

In [443]: %timeit split_app(a)
10 loops, best of 3: 88.8 ms per loop

In [444]: %timeit zip_app(a)
10 loops, best of 3: 40.2 ms per loop

In [445]: a = np.random.randint(0,1000000,(6*1000000,4)); a[:,0].sort()

In [446]: %timeit split_app(a)
1 loop, best of 3: 917 ms per loop

In [447]: %timeit zip_app(a)
1 loop, best of 3: 414 ms per loop

您可以使用布尔索引来做到这一点。

unique_ids = np.unique(data_mat[:, 0])
masks = np.equal.outer(unique_ids, data_mat[:, 0])
for mask in masks:
    tmp_mat = data_mat[mask]
    # do something with tmp_mat ...
    print(tmp_mat)

如果唯一 ID 已经分组,您可以使用 np.split 执行此操作,类似于 @Divakar

idx = np.flatnonzero(data_mat[1:, 0] != data_mat[:-1, 0]) + 1
for tmp_mat in np.split(data_mat, idx):
    # do something with tmp_mat ...
    print(tmp_mat)