Как эффективно создавать итерации с помощью большого списка списков в python?

У меня есть мои данные как таковые:

data = {'x':Counter({'a':1,'b':45}), 'y':Counter({'b':1, 'c':212})}

где мои метки являются ключами data, а ключ внутреннего словаря - это функции:

all_features = ['a','b','c']
all_labels = ['x','y']

Мне нужно создать список списка как таковой:

[[data[label][feat] for feat in all_features] for label in all_labels]

[выход]:

[[1, 45, 0], [0, 1, 212]]

Мой len(all_features) составляет ~ 5 000 000 и len(all_labels) составляет ~ 100 000

Конечная цель заключается в создании scipy разреженной матрицы, например:

from collections import Counter
from scipy.sparse import csc_matrix
import numpy as np


all_features = ['a','b','c']
all_labels = ['x','y']

csc_matrix(np.array([[data[label][feat] for feat in all_features] for label in all_labels]))

но цикл через большой список списков довольно неэффективен.

Итак, , как я могу эффективно просматривать большой список списков?

Есть ли другой способ создать scipy-матрицу из data без циклирования всех функций и меток?

Ответ 1

Преобразование словаря словарей в массив numpy или scipy - это, как вы переживаете, не слишком весело. Если вы знакомы с all_features и all_labels перед началом работы, вам, скорее всего, лучше использовать scipy редкую матрицу COO с самого начала, чтобы сохранить ваши счета.

Если это возможно или нет, вы хотите сохранить списки списков функций и меток в отсортированном порядке, чтобы ускорить поиск. Поэтому я собираюсь предположить, что следующее не изменяет ни один массив:

all_features = np.array(all_features)
all_labels = np.array(all_labels)
all_features.sort()
all_labels.sort()

Позволяет извлекать метки в data в порядке их хранения в словаре и видеть, где в all_labels падает каждый элемент:

labels = np.fromiter(data.iterkeys(), all_labels.dtype, len(data))
label_idx = np.searchsorted(all_labels, labels)

Теперь подсчитайте, сколько функций имеет каждая метка, и вычислить из нее количество ненулевых элементов в вашем разреженном массиве:

label_features = np.fromiter((len(c) for c in data.iteritems()), np.intp,
                             len(data))
indptr = np.concatenate(([0], np.cumsum(label_features)))
nnz = indptr[-1]

Теперь мы извлекаем функции для каждой метки и их соответствующие значения

import itertools
features_it = itertools.chain(*(c.iterkeys() for c in data.itervalues()))
features = np.fromiter(features_it, all_features.dtype, nnz)
feature_idx = np.searchsorted(all_features, features)
counts_it = itertools.chain(*(c.itervalues() for c in data.itervalues()))
counts = np.fromiter(counts_it, np.intp, nnz)

С помощью того, что у нас есть, мы можем создать CSR-матрицу напрямую, с метками в виде строк и функций в виде столбцов:

sps_data = csr_matrix((counts, feature_idx, indptr),
                      shape=(len(all_labels), len(all_features)))

Единственная проблема заключается в том, что строки этого разреженного массива находятся не в порядке all_labels, а в том порядке, в котором они появлялись при итерации над data. Но мы feature_idx сообщим нам, где каждая метка закончилась, и мы можем изменить порядок строк, выполнив:

sps_data = sps_data[np.argsort(label_idx)]

Да, это беспорядочно, сбивает с толку и, вероятно, не очень быстро, но это работает, и это будет гораздо более эффективно с памятью, чем то, что вы предложили в своем вопросе:

>>> sps_data.A
array([[  1,  45,   0],
       [  0,   1, 212]], dtype=int64)
>>> all_labels
array(['x', 'y'], 
      dtype='<S1')
>>> all_features
array(['a', 'b', 'c'], 
      dtype='<S1')

Ответ 2

Набор данных довольно велик, поэтому я не считаю целесообразным создание временного массива numpy (при использовании 32-битных целых чисел матрица 1e5 x 5e6 потребует ~ 2 терабайта памяти).

Я предполагаю, что вы знаете верхнюю границу для количества меток.

Код может выглядеть так:

import scipy.sparse
n_rows = len(data.keys())
max_col = int(5e6)
temp_sparse = scipy.sparse.lil_matrix((n_rows, max_col), dtype='int')

for i, (features, counts) in enumerate(data.iteritems()):
    for label, n in counts.iteritem():
        j = label_pos[label]
        temp_sparse[i, j] = n
csc_matrix = temp_sparse.csc_matrix(temp_matrix)

Где label_pos возвращает индекс столбца метки. Если окажется, что использовать словарь для хранения индекса в 5 миллионов ярлыков, который должен делать база данных жесткого диска, нецелесообразно. Словарь можно создавать онлайн, поэтому предыдущее знание всех ярлыков не требуется.

Итерация через 100 000 функций займет разумное время, поэтому я думаю, что это решение может работать, если набор данных достаточно редок. Удачи!

Ответ 3

s там другой способ создания scipy матрицы из данных без цикла через все функции и метки?

Я не думаю, что есть сокращение, которое уменьшает общее количество поисков. Вы начинаете со словаря Counters (подкласса dict), поэтому оба уровня вложенности являются неупорядоченными коллекциями. Единственный способ вернуть их в требуемом порядке - это выполнить поиск data[label][feat] для каждой точки данных.

Вы можете сократить время примерно наполовину, убедившись, что поиск data[label] выполняется только один раз на метку:

>>> counters = [data[label] for label in all_labels]
>>> [[counter[feat] for feat in all_features] for counter in counters]
[[1, 45, 0], [0, 1, 212]]

Вы также можете попробовать ускорить время выполнения, используя map() вместо понимания списка (сопоставление может использовать внутреннюю длину length_hint для предварительного размера массива результатов):

>>> [map(counter.__getitem__, all_features) for counter in counters]
[[1, 45, 0], [0, 1, 212]]

Наконец, обязательно запустите код внутри функции (поиск локальных переменных в CPython быстрее, чем поиск по глобальной переменной):

def f(data, all_features, all_labels):
    counters = [data[label] for label in all_labels]
    return [map(counter.__getitem__, all_features) for counter in counters]