Для чистого решения с пустыми краями вы можете проверить 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)
На порядок быстрее, чем моё решение.