Можно ли объединить (добавить) значения вектора в соответствии с целочисленным значением другого вектора - PullRequest
0 голосов
/ 03 октября 2018

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

например, если у меня есть:

import numpy as np
a = np.array([0.1,0.2,0.3,0.4,0.5,0.6,07.3,0.8,0.9,1.,1.2,1.4])
b = np.array([0,0,0,0,0,1,1,1,2,2,2,2]).astype(int)

Я хотел бы сложить вместе 5 первых значений вектора a (потому что 5 первых значений b равны 0), 3 следующиезначения вместе (потому что 3 следующих значения b равны 1) и так далее.Так что в конце я ожидаю, что получу

c = function(a,b)
c = [0.1+0.2+0.3+0.4+0.5,  0.6+7.3+0.8, 0.9+1.+1.2+1.4]

Ответы [ 5 ]

0 голосов
/ 03 октября 2018

Подход № 1: Мы можем использовать np.bincount с b в качестве бинов и a в качестве массива весов -

In [203]: np.bincount(b,a)
Out[203]: array([1.5, 8.7, 4.5])

Подход № 2: Еще одно использование matrix-multiplication -

In [210]: (b == np.arange(b.max()+1)[:,None]).dot(a)
Out[210]: array([1.5, 8.7, 4.5])
0 голосов
/ 03 октября 2018

Для чистого решения с пустыми краями вы можете проверить np.diff() из b, что даст вам новый массив нулей везде, кроме случаев, когда значения изменяются.Однако для этого требуется небольшая настройка, так как np.diff() уменьшает размер вашего массива на один элемент, поэтому ваши индексы будут отключены на один.На самом деле в настоящее время ведется разработка numpy, чтобы сделать это лучше (предоставив новые аргументы для дополнения вывода до исходного размера; см. Проблему здесь: https://github.com/numpy/numpy/issues/8132)

С учетом сказанного ... вот кое-что, что должно быть поучительным:

In [100]: a
Out[100]: array([0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 7.3, 0.8, 0.9, 1. , 1.2, 1.4])

In [101]: b
Out[101]: array([0, 0, 0, 0, 0, 1, 1, 1, 2, 2, 2, 2])

In [102]: np.diff(b) # note it is one element shorter than b
Out[102]: array([0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0])

In [103]: np.flatnonzero(np.diff(b))
Out[103]: array([4, 7]) 

In [104]: np.flatnonzero(np.diff(b)) + 1
Out[104]: array([5, 8])

In [105]: np.insert(np.flatnonzero(np.diff(b)) + 1, 0, 0)
Out[105]: array([0, 5, 8]) # these are the indices of the start of each group

In [106]: indices = _

In [107]: np.add.reduceat(a, indices)
Out[107]: array([1.5, 8.7, 4.5])

In [108]: def sumatchanges(a, b):
     ...:     indices = np.insert(np.flatnonzero(np.diff(b)) + 1, 0, 0)
     ...:     return np.add.reduceat(a, indices)
     ...:

In [109]: sumatchanges(a, b)
Out[109]: array([1.5, 8.7, 4.5])

Я бы определенно предпочел использовать Pandas groupby в качестве ответа jpp, используемого в большинстве настроек, так как это уродливо. Надеюсь, с этими изменениями в numpy это может быть немного приятнее на вид и более естественным вбудущее.


Обратите внимание, что этот ответ эквивалентен ответу itertools.groupby, который дал Мартен (на выходе). В частности, предполагается, что группы предполагаются последовательными. Т.е. это

b = np.array([0,0,0,0,0,1,1,1,2,2,2,2]).astype(int)

выдаст тот же результат, что и с

b = np.array([0,0,0,0,0,1,1,1,0,0,0,0]).astype(int)

Число не имеет значения, пока оно меняется. Однако для другого решения, которое дал Мартен, и решения для панд с помощью jpp, этибудет суммировать все вещи с одинаковой меткой, независимо от местоположения. ОП не ясно, какой вы предпочитаете.


Сроки:

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

In [115]: import timeit
In [116]: import pandas as pd

In [117]: def sumatchangespd(a, b):
     ...:     return pd.Series(a).groupby(b).sum().values
     ...:

In [125]: l = 100_000

In [126]: a = np.random.rand(l)

In [127]: b = np.cumsum(np.random.randint(2, size=l))

In [128]: sumatchanges(a, b)
Out[128]:
array([2.83528234e-01, 6.66182064e-01, 9.32624292e-01, ...,
       2.98379765e-01, 1.97586484e+00, 8.65103445e-04])

In [129]: %timeit sumatchanges(a, b)
1.91 ms ± 47.5 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

In [130]: %timeit sumatchangespd(a, b)
6.33 ms ± 267 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

Также просто чтобы убедиться, что они эквивалентны:

In [139]: all(np.isclose(sumatchanges(a, b), sumatchangespd(a, b)))
Out[139]: True

Так что версия numpy быстрее (не слишком удивительно).Опять же, эти функции могут выполнять несколько разные вещи, в зависимости от вашего ввода:

In [120]: b  # numpy solution grabs each chunk as a separate piece
Out[120]: array([0, 0, 0, 0, 0, 1, 1, 1, 2, 2, 2, 2])

In [121]: b[-4:] = 0

In [122]: b   # pandas will sum the vals in a that have same vals in b
Out[122]: array([0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0])

In [123]: sumatchanges(a, b)
Out[123]: array([1.5, 8.7, 4.5])

In [124]: sumatchangespd(a, b)
Out[124]: array([6. , 8.7])

Основное решение Divakar - блестящее и лучшее из всего вышеперечисленного в отношении скорости:

In [144]: def sumatchangesbc(a, b):
     ...:     return np.bincount(b,a)
     ...:

In [145]: %timeit sumatchangesbc(a, b)
175 µs ± 1.16 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

На порядок быстрее, чем моё решение.

0 голосов
/ 03 октября 2018

Только с numpy

c = [sum(a[b==i]) for i in sorted(set(b))]

примечание: как указано @jpp, вероятно, лучше написать np.unique вместо sorted(set(b))

0 голосов
/ 03 октября 2018

нативное решение Python, использующее itertools.groupby

from itertools import groupby

groups = (group for key, group in groupby(zip(a, b), key=lambda x: x[1]))
totals = [sum(a for a, b in group) for group in groups]
[1.5, 8.7, 4.5]

альтернатива numpy, похожая на решение @ blue_note, но использующая силу numpy вместо нативного python

[(a * (b == group)).sum() for group in np.unique(b)]

Это работает, только если b = np.array([2,2,0,0,0,1,1,1,2,2,2,2]) не содержит 2 отличительные группы 2

0 голосов
/ 03 октября 2018

Вы можете использовать Pandas, который построен на NumPy:

import pandas as pd

c = pd.Series(a).groupby(b).sum().values

# array([ 1.5,  8.7,  4.5])

Или более подробный вариант:

c = pd.DataFrame({'a': a, 'b': b})\
      .groupby('b')['a'].sum().values
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...