У меня есть следующий массив numpy:
import numpy as np
arr = np.array([[1,2,3,4,2000],
[5,6,7,8,2000],
[9,0,1,2,2001],
[3,4,5,6,2001],
[7,8,9,0,2002],
[1,2,3,4,2002],
[5,6,7,8,2003],
[9,0,1,2,2003]
])
Я понимаю, что np.sum(arr, axis=0)
предоставит результат:
array([ 40, 28, 36, 34, 16012])
то, что я хотел бы сделать (без цикла for), - это суммировать столбцы на основе значения последнего столбца, чтобы полученный результат был следующим:
array([[ 6, 8, 10, 12, 4000],
[ 12, 4, 6, 8, 4002],
[ 8, 10, 12, 4, 4004],
[ 14, 6, 8, 10, 4006]])
Я понимаю, что обойтись без петли, возможно, будет непросто, но надеюсь на лучшее ...
Если необходимо использовать цикл for, как это будет работать?
Я попробовал np.sum(arr[:, 4]==2000, axis=0)
(где я бы заменил 2000
на переменную из цикла for), однако он дал результат 2
Значение в правом столбце всегда повторяется ровно дважды, или в вашем примере это просто совпадение?
@ cᴏʟᴅsᴘᴇᴇᴅ Не могли бы вы снова открыть? Я работаю над чистым решением.
@MadPhysicist Хорошо, без проблем, мне тоже было бы интересно это увидеть.
совпадение (у меня в основном есть много данных, которые я хочу суммировать по годам). df.groupby(4, axis=0).sum()
дает мне именно то, что мне нужно. Я оставлю без ответа, поскольку я хотел бы знать, можно ли сделать то же самое с помощью numpy, но спасибо!
@InfinityCliff. Может. Я только что отправил
@ cᴏʟᴅsᴘᴇᴇᴅ. Спасибо за то, что отправил ответ.
@InfinityCliff, хотя решение только для numpy
может быть интересным, иногда лучше не изобретать велосипед, а просто использовать какую-нибудь библиотеку с функцией groupby
:)
Выкладываю простое решение с pandas
и одно с itertools
import pandas as pd
df = pd.DataFrame(arr)
x = df.groupby(4).sum().reset_index()[range(5)] #range(5) adjusts ordering
x[4] *= 2
np.array(x)
array([[ 6, 8, 10, 12, 4000],
[ 12, 4, 6, 8, 4002],
[ 8, 10, 12, 4, 4004],
[ 14, 6, 8, 10, 4006]])
Также можно использовать itertools
np.array([sum(x[1]) for x in itertools.groupby(arr, key = lambda k: k[-1])])
array([[ 6, 8, 10, 12, 4000],
[ 12, 4, 6, 8, 4002],
[ 8, 10, 12, 4, 4004],
[ 14, 6, 8, 10, 4006]])
Подход # 1: сокращение суммы на основе NumPy
Вот один на основе np.add.reduceat
-
def groupbycol(a, assume_sorted_col=False, colID=-1):
if assume_sorted_col==0:
# If a is not already sorted by that col, use argsort indices for
# that colID and re-arrange rows accordingly
sidx = a[:,colID].argsort()
a_s = a[sidx] # sorted by colID col of input array
else:
a_s = a
# Get group shifting indices
cut_idx = np.flatnonzero(np.r_[True, a_s[1:,colID] != a_s[:-1,colID]])
# Use those indices to setup sum reduction at intervals along first axis
return np.add.reduceat(a_s, cut_idx, axis=0)
Пробный прогон -
In [64]: arr
Out[64]:
array([[ 1, 2, 3, 4, 2000],
[ 5, 6, 7, 8, 2000],
[ 9, 0, 1, 2, 2001],
[ 3, 4, 5, 6, 2001],
[ 7, 8, 9, 0, 2002],
[ 1, 2, 3, 4, 2002],
[ 5, 6, 7, 8, 2003],
[ 9, 0, 1, 2, 2003]])
In [65]: # Shuffle rows off input array to create a generic last col (not sorted)
...: np.random.seed(0)
...: np.random.shuffle(arr)
In [66]: arr
Out[66]:
array([[ 5, 6, 7, 8, 2003],
[ 9, 0, 1, 2, 2001],
[ 5, 6, 7, 8, 2000],
[ 9, 0, 1, 2, 2003],
[ 3, 4, 5, 6, 2001],
[ 1, 2, 3, 4, 2000],
[ 1, 2, 3, 4, 2002],
[ 7, 8, 9, 0, 2002]])
In [67]: groupbycol(arr, assume_sorted_col=False, colID=-1)
Out[67]:
array([[ 6, 8, 10, 12, 4000],
[ 12, 4, 6, 8, 4002],
[ 8, 10, 12, 4, 4004],
[ 14, 6, 8, 10, 4006]])
Подход # 2: использование умножения матриц
Мы могли бы в основном заменить этот np.add.reduceat
на создание широковещательной маски + умножение матриц, следовательно, использовать быстрый BLAS, который также работает для общего несортированного столбца -
import pandas as pd
def groupbycol_matmul(a, colID=-1):
mask = pd.Series(a[:,colID]).unique()[:,None] == arr[:,colID]
return mask.dot(arr)
Хороший вызов для первой сортировки аргументов.
Хотел бы я дать еще +1 для умножения.
Вы можете сделать это в чистом numpy, используя умное приложение np.diff
и np.add.reduceat
. np.diff
предоставит вам индексы, в которых изменяется крайний правый столбец:
d = np.diff(arr[:, -1])
np.where
преобразует ваш логический индекс d
в целочисленные индексы, которые ожидает np.add.reduceat
:
d = np.where(d)[0]
reduceat
также ожидает увидеть нулевой индекс, и все должно быть сдвинуто на единицу:
indices = np.r_[0, e + 1]
Использование np.r_
здесь немного удобнее, чем np.concatenate
, потому что оно позволяет скаляры. Сумма становится такой:
result = np.add.reduceat(arr, indices, axis=0)
Конечно, это можно объединить в однострочник:
>>> result = np.add.reduceat(arr, np.r_[0, np.where(np.diff(arr[:, -1]))[0] + 1], axis=0)
>>> result
array([[ 6, 8, 10, 12, 4000],
[ 12, 4, 6, 8, 4002],
[ 8, 10, 12, 4, 4004],
[ 14, 6, 8, 10, 4006]])
Хороший ответ; Несмотря на то, что однострочный текст трудно читать, он очень хорошо объяснен :)
Спасибо. Я думаю, что ответ @Divakar - это более четкое воплощение той же идеи.
выбрав этот в качестве ответа, поскольку он отвечает на вопрос, используя только numpy
, но, честно говоря, мне больше нравится pandas.groupby
от @MadPhysicist, он действительно будет лучше работать для моего окончательного решения, поскольку мне также нужно группировать по месяцам и годам. Спасибо всем.
Вы можете взглянуть на numpy_indexed
. С его помощью можно:
import numpy as np
import numpy_indexed as npi
arr = np.array([[1,2,3,4,2000],
[5,6,7,8,2000],
[9,0,1,2,2001],
[3,4,5,6,2001],
[7,8,9,0,2002],
[1,2,3,4,2002],
[5,6,7,8,2003],
[9,0,1,2,2003]
])
result = npi.GroupBy(arr[:, 4]).sum(arr)[1]
>>>[[ 6 8 10 12 4000]
[ 12 4 6 8 4002]
[ 8 10 12 4 4004]
[ 14 6 8 10 4006]]
Думаю, вы ищете группу
pandas
.