Быстрый способ извлечь подматрицу из матрицы numpy

У меня есть огромный файл .csv (~ 2 ГБ), который я импортирую в свою программу с помощью read_csv, а затем конвертирую в матрицу numpy с помощью as_matrix. Сгенерированная матрица имеет форму, подобную 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

person nerdizzle    schedule 03.09.2017    source источник
comment
Вероятно, лучший подход — делать это в пандах, которые для этого очень оптимизированы. groupby — это то, что вам нужно. Также см. это, чтобы обсудить здесь отсутствующую функциональность numpy (и потенциально восхвалять панд).   -  person sascha    schedule 03.09.2017
comment
уникальные идентификаторы всегда сгруппированы таким образом (т. е. отсортированы по data_mat[:,0] или иначе)?   -  person Daniel F    schedule 03.09.2017
comment
да, уникальные идентификаторы всегда группируются таким образом. Я должен был упомянуть об этом..   -  person nerdizzle    schedule 03.09.2017


Ответы (3)


Чтобы оптимизировать идею, нужно минимизировать вычисления, как только мы окажемся внутри цикла. Итак, имея это в виду, мы бы переставили строки массива, отсортированные по первому столбцу. Затем получите индексы, определяющие границы. Наконец, запустите наш цикл и просто нарежьте каждую группу, чтобы получить подматрицу на каждой итерации. Нарезка практически бесплатна при работе с массивами, так что это должно нам помочь.

Таким образом, одна реализация будет -

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
person Divakar    schedule 03.09.2017

Вы можете сделать это с помощью логического индексирования.

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)
person Daniel F    schedule 03.09.2017

Если уникальные идентификаторы уже сгруппированы, вы можете сделать это с помощью 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)
person Daniel F    schedule 03.09.2017
comment
Вот почему вы можете избегать np.split для повышения производительности. - person Divakar; 03.09.2017
comment
Мои тайминги показывают, что это примерно так же быстро, как и твое, @Divakar. Никакая конкатенация не поможет - person Daniel F; 03.09.2017
comment
Не сравнивая мое с вашим :) Просто в целом излагаю, почему можно избежать split. Что касается сравнения, я думаю, нам нужен больший набор данных, чем образец. - person Divakar; 03.09.2017
comment
Если вас интересовали тайминги, добавлю бенчмаркинг в мой пост. Вам потребуется намного меньше группировок, чтобы помочь этому подходу избежать конкатенации + разделения работать лучше. - person Divakar; 03.09.2017