Как обойти медленный групповик для разреженной матрицы? - PullRequest
3 голосов
/ 22 января 2020

У меня есть большая матрица (~ 200 миллионов строк), описывающая список действий, которые происходили каждый день (есть ~ 10000 возможных действий). Моя конечная цель - создать матрицу совместного появления, показывающую, какие действия происходят в те же дни.

Вот пример набора данных:

data = {'date':   ['01', '01', '01', '02','02','03'],
        'action': [100, 101, 989855552, 100, 989855552, 777]}
df = pd.DataFrame(data, columns = ['date','action'])

Я пытался создать разреженную матрицу с помощью pd.get_dummies, но распаковка матрицы и использование групповой работы на ней происходит очень медленно, что заняло 6 минут для всего 5000 строк.

# Create a sparse matrix of dummies
dum = pd.get_dummies(df['action'], sparse = True)
df = df.drop(['action'], axis = 1)
df = pd.concat([df, dum], axis = 1)

# Use groupby to get a single row for each date, showing whether each action occurred.
# The groupby command here is the bottleneck.
cols = list(df.columns)
del cols[0]
df = df.groupby('date')[cols].max()

# Create a co-occurrence matrix by using dot-product of sparse matrices
cooc = df.T.dot(df)

Я также пытался:

  1. получить пустышки в не разреженном формате;
  2. с использованием groupby для агрегирования;
  3. Я собираюсь использовать разреженный формат перед умножением матрицы.

Но я не могу выполнить шаг 1, поскольку недостаточно ОЗУ для создания такой большой матрицы.

Буду очень признателен за вашу помощь.

Ответы [ 2 ]

1 голос
/ 22 января 2020

Существует несколько простых упрощений, которые вы можете рассмотреть.

Одним из них является то, что вы можете вызывать max() непосредственно для объекта GroupBy, вам не нужен причудливый индекс для всех столбцов, так как это то, что он возвращает по умолчанию:

df = df.groupby('date').max()

Во-вторых, вы можете отключить сортировку GroupBy. Как указано в Pandas для groupby():

sort : bool, по умолчанию True

Сортировка групповых ключей. Повысьте эту производительность, отключив ее. Обратите внимание, что это не влияет на порядок наблюдений в каждой группе. Groupby сохраняет порядок строк в каждой группе.

Так что попробуйте также:

df = df.groupby('date', sort=False).max()

В-третьих, вы также можете использовать простой pivot_table() для создания тот же результат.

df = df.pivot_table(index='date', aggfunc='max')

Еще один подход - возврат к вашему DataFrame "действиям", превращение его в MultiIndex и использование его для простой серии, затем использование unstack() для него, которое должно получить результат тот же, без необходимости использовать шаг get_dummies() (но не уверен, удалит ли это некоторые свойства разреженности, на которые вы в настоящее время полагаетесь.)

actions_df = pd.DataFrame(data, columns = ['date', 'action'])
actions_index = pd.MultiIndex.from_frame(actions_df, names=['date', ''])
actions_series = pd.Series(1, index=actions_index)
df = actions_series.unstack(fill_value=0)

Ваш предоставленный образец DataFrame вполне полезно для проверки того, что все они эквивалентны и дают один и тот же результат, но, к сожалению, не так уж хороши для его сравнения ... Я предлагаю вам взять больший набор данных (но все же меньше, чем ваши реальные данные, например, в 10 раз меньше или, возможно, в 40-50 раз меньше). ), а затем выполните тестирование операций, чтобы проверить, сколько времени они занимают.

Если вы используете Jupyter (или другую оболочку I Python), вы можете использовать * 10 40 * команда для сравнения выражения.

Таким образом, вы можете ввести:

%timeit df.groupby('date').max()
%timeit df.groupby('date', sort=False).max()
%timeit df.pivot_table(index='date', aggfunc='max')
%timeit actions_series.unstack(fill_value=0)

и сравнить результаты, затем увеличить масштаб и проверить, будет ли весь цикл завершен за приемлемое количество времени.

0 голосов
/ 31 января 2020

Я придумал ответ, используя только разреженные матрицы на основе этого поста . Код быстрый, занимает около 10 секунд для 10 миллионов строк (мой предыдущий код занимал 6 минут для 5000 строк и не масштабировался).

Экономия времени и памяти достигается за счет работы с разреженными матрицами до самого последнего шага, когда необходимо распознать (уже небольшую) матрицу совместного использования перед экспортом.

## Get unique values for date and action
date_c = CategoricalDtype(sorted(df.date.unique()), ordered=True)
action_c = CategoricalDtype(sorted(df.action.unique()), ordered=True)

## Add an auxiliary variable
df['count'] = 1

## Define a sparse matrix
row = df.date.astype(date_c).cat.codes
col = df.action.astype(action_c).cat.codes
sparse_matrix = csr_matrix((df['count'], (row, col)),
                shape=(date_c.categories.size, action_c.categories.size))

## Compute dot product with sparse matrix
cooc_sparse = sparse_matrix.T.dot(sparse_matrix)

## Unravel co-occurrence matrix into dense shape
cooc = pd.DataFrame(cooc_sparse.todense(), 
       index = action_c.categories, columns = action_c.categories)
...