Самый эффективный способ суммировать огромный массив 2D NumPy, сгруппированный по столбцу идентификаторов? - PullRequest
6 голосов
/ 17 августа 2011

У меня есть массивный массив данных (500 тыс. Строк), который выглядит следующим образом:

id  value  score
1   20     20
1   10     30
1   15     0
2   12     4
2   3      8
2   56     9
3   6      18
...

Как видите, слева находится столбец с неуникальным идентификатором, а в третьем столбце - различные оценки.

Я хочу быстро сложить все оценки, сгруппированные по идентификаторам. В SQL это будет выглядеть как SELECT sum(score) FROM table GROUP BY id

С NumPy я попытался перебрать каждый идентификатор, обрезать таблицу по каждому идентификатору, а затем подвести итог для этой таблицы.

table_trunc = table[(table == id).any(1)]
score       = sum(table_trunc[:,2])

К сожалению, я нахожу первую команду медленной. Есть ли более эффективный способ сделать это?

Ответы [ 7 ]

10 голосов
/ 17 августа 2011

вы можете использовать bincount ():

import numpy as np

ids = [1,1,1,2,2,2,3]
data = [20,30,0,4,8,9,18]

print np.bincount(ids, weights=data)

выход равен [0. 50. 21. 18.], что означает, что сумма id == 0 равна 0, сумма id == 1 - это 50.

1 голос
/ 27 октября 2016

Я заметил тег numpy, но в случае, если вы не возражаете против использования pandas (или если вы читаете эти данные с помощью этого модуля), эта задача становится однострочной:

import pandas as pd

df = pd.DataFrame({'id': [1,1,1,2,2,2,3], 'score': [20,30,0,4,8,9,18]})

Итак, ваш фрейм данных будет выглядеть так:

  id  score
0   1     20
1   1     30
2   1      0
3   2      4
4   2      8
5   2      9
6   3     18

Теперь вы можете использовать функции groupby() и sum():

df.groupby(['id'], sort=False).sum()

, который дает желаемый результат:

    score
id       
1      50
2      21
3      18

По умолчанию, фрейм данных будет отсортирован, поэтому я использую флаг sort=False, который может улучшить скорость для больших фреймов данных.

0 голосов
/ 10 июня 2017

Вы можете использовать цикл for и numba

from numba import njit

@njit
def wbcnt(b, w, k):
    bins = np.arange(k)
    bins = bins * 0
    for i in range(len(b)):
        bins[b[i]] += w[i]
    return bins

Использование переменных @ HYRY

ids = [1, 1, 1, 2, 2, 2, 3]
data = [20, 30, 0, 4, 8, 9, 18]

Тогда:

wbcnt(ids, data, 4)

array([ 0, 50, 21, 18])

Сроки

%timeit wbcnt(ids, data, 4)
%timeit np.bincount(ids, weights=data)

1000000 loops, best of 3: 1.99 µs per loop
100000 loops, best of 3: 2.57 µs per loop
0 голосов
/ 27 октября 2016

Пакет numpy_indexed имеет векторизованную функциональность для эффективного выполнения этой операции, в дополнение ко многим связанным операциям такого рода:

import numpy_indexed as npi
npi.group_by(id).sum(score)
0 голосов
/ 20 октября 2016

Если вы ищете только sum, вы, вероятно, захотите использовать bincount. Если вам также нужны другие операции группировки, такие как product, mean, std и т. Д., Посмотрите https://github.com/ml31415/numpy-groupies. Это самые быстрые операции группировки Python / NumPy, смотрите сравнение скорости там.

Ваша сумма операции будет выглядеть следующим образом:

res = aggregate(id, score)
0 голосов
/ 17 августа 2011

Вы можете попробовать использовать логические операции:

ids = [1,1,1,2,2,2,3]
data = [20,30,0,4,8,9,18]

[((ids == i)*data).sum() for i in np.unique(ids)]

Это может быть немного эффективнее, чем использование np.any, но, безусловно, возникнут проблемы, если у вас будет очень большое количество уникальных идентификаторов, которые можно использовать.с большим общим размером таблицы данных.

0 голосов
/ 17 августа 2011

Возможно, используя itertools.groupby, вы можете группировать по идентификатору, а затем перебирать сгруппированные данные.

(данные должны быть отсортированы по группе по func, в данном случае по идентификатору)

>>> data = [(1, 20, 20), (1, 10, 30), (1, 15, 0), (2, 12, 4), (2, 3, 0)]
>>> groups = itertools.groupby(data, lambda x: x[0])
>>> for i in groups:
        for y in i:
            if isinstance(y, int):
                print(y)
            else:
                for p in y:
                    print('-', p)

Выход:

1
- (1, 20, 20)
- (1, 10, 30)
- (1, 15, 0)
2
- (2, 12, 4)
- (2, 3, 0)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...