Суммарный массив по номеру в numpy - PullRequest
17 голосов
/ 07 декабря 2010

Предполагая, что у меня есть такой массив: [1,2,3,4,5,6] и другой массив: [0,0,1,2,2,1] Я хочу суммировать элементы в первом массиве по группам (второй массив) и получить результаты n-групп в порядке номеров групп (в этом случае результат будет [3, 9, 9]). Как мне сделать это в NumPy?

Ответы [ 8 ]

27 голосов
/ 15 мая 2014

Функция numpy bincount была создана именно для этой цели, и я уверен, что она будет намного быстрее, чем другие методы для всех размеров входов:

data = [1,2,3,4,5,6]
ids  = [0,0,1,2,2,1]

np.bincount(ids, weights=data) #returns [3,9,9] as a float64 array

i-й элемент вывода является суммой всех data элементов, соответствующих «id» i.

Надеюсь, это поможет.

27 голосов
/ 04 января 2012

Это векторизованный метод получения этой суммы, основанный на реализации numpy.unique. Согласно моим временам, это до 500 раз быстрее, чем метод цикла и до 100 раз быстрее, чем метод гистограммы.

def sum_by_group(values, groups):
    order = np.argsort(groups)
    groups = groups[order]
    values = values[order]
    values.cumsum(out=values)
    index = np.ones(len(groups), 'bool')
    index[:-1] = groups[1:] != groups[:-1]
    values = values[index]
    groups = groups[index]
    values[1:] = values[1:] - values[:-1]
    return values, groups
9 голосов
/ 07 декабря 2010

Есть несколько способов сделать это, но вот один из способов:

import numpy as np
data = np.arange(1, 7)
groups = np.array([0,0,1,2,2,1])

unique_groups = np.unique(groups)
sums = []
for group in unique_groups:
    sums.append(data[groups == group].sum())

Вы можете векторизовать вещи, чтобы вообще не было цикла for, но я бы рекомендовал против этого. Он становится нечитаемым и потребует нескольких двумерных временных массивов, которые могут потребовать большого объема памяти, если у вас много данных.

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

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

import numpy as np
data = np.arange(1, 7)
groups = np.array([0,0,1,2,2,1])

unique_groups = np.unique(groups)

# Forgive the bad naming here...
# I can't think of more descriptive variable names at the moment...
x, y = np.meshgrid(groups, unique_groups)
data_stack = np.tile(data, (unique_groups.size, 1))

data_in_group = np.zeros_like(data_stack)
data_in_group[x==y] = data_stack[x==y]

sums = data_in_group.sum(axis=1)
7 голосов
/ 08 декабря 2010

Если группы индексируются последовательными целыми числами, вы можете использовать функцию numpy.histogram(), чтобы получить результат:

data = numpy.arange(1, 7)
groups = numpy.array([0,0,1,2,2,1])
sums = numpy.histogram(groups, 
                       bins=numpy.arange(groups.min(), groups.max()+2), 
                       weights=data)[0]
# array([3, 9, 9])

Это позволит избежать любых петель Python.

5 голосов
/ 25 июля 2014

Я пробовал скрипты от всех, и мои соображения:

Джо. Работает, только если у вас мало групп.

kevpie: слишком медленный из-за петель (это не питонический способ)

Bi_Rico и Sven: работают хорошо, но будут работать только для Int32 (если сумма превысит 2 ^ 32/2, она не получится)

Алекс: самый быстрый, хороший по сумме.

Но если вы хотите большей гибкости и возможности группировки по другой статистике, используйте SciPy :

from scipy import ndimage

data = np.arange(10000000)
groups = np.arange(1000).repeat(10000)
ndimage.sum(data, groups, range(1000))

Это хорошо, потому что у вас много статистики для группировки (сумма, среднее, дисперсия, ...).

3 голосов
/ 09 сентября 2016

Вы все не правы! Лучший способ сделать это:

a = [1,2,3,4,5,6]
ix = [0,0,1,2,2,1]
accum = np.zeros(np.max(ix)+1)
np.add.at(accum, ix, a)
print accum
> array([ 3.,  9.,  9.])
0 голосов
/ 07 июля 2016

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

import pandas as pd
import numpy as np

data = np.arange(1, 7)
groups = np.array([0, 0, 1, 2, 2, 1])

df = pd.DataFrame({'data': data, 'groups': groups})

Так что df тогда выглядит так:

   data  groups
0     1       0
1     2       0
2     3       1
3     4       2
4     5       2
5     6       1

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

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

, которые дают желаемый вывод

        data
groups      
0          3
1          9
2          9

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

0 голосов
/ 07 декабря 2010

Чистая реализация Python:

l = [1,2,3,4,5,6]
g = [0,0,1,2,2,1]

from itertools import izip
from operator import itemgetter
from collections import defaultdict

def group_sum(l, g):
    groups = defaultdict(int)
    for li, gi in izip(l, g):
        groups[gi] += li
    return map(itemgetter(1), sorted(groups.iteritems()))

print group_sum(l, g)

[3, 9, 9]
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...