Numpy: условная сумма

У меня есть следующий массив 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

Думаю, вы ищете группу pandas.

Mad Physicist 01.05.2018 20:43

Значение в правом столбце всегда повторяется ровно дважды, или в вашем примере это просто совпадение?

Mad Physicist 01.05.2018 20:47

@ cᴏʟᴅsᴘᴇᴇᴅ Не могли бы вы снова открыть? Я работаю над чистым решением.

Mad Physicist 01.05.2018 20:51

@MadPhysicist Хорошо, без проблем, мне тоже было бы интересно это увидеть.

cs95 01.05.2018 20:52

совпадение (у меня в основном есть много данных, которые я хочу суммировать по годам). df.groupby(4, axis=0).sum() дает мне именно то, что мне нужно. Я оставлю без ответа, поскольку я хотел бы знать, можно ли сделать то же самое с помощью numpy, но спасибо!

Infinity Cliff 01.05.2018 20:54

@InfinityCliff. Может. Я только что отправил

Mad Physicist 01.05.2018 21:06

@ cᴏʟᴅsᴘᴇᴇᴅ. Спасибо за то, что отправил ответ.

Mad Physicist 01.05.2018 21:07

@InfinityCliff, хотя решение только для numpy может быть интересным, иногда лучше не изобретать велосипед, а просто использовать какую-нибудь библиотеку с функцией groupby :)

rafaelc 01.05.2018 21:20
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
8
8
15 231
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Выкладываю простое решение с 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)

Хороший вызов для первой сортировки аргументов.

Mad Physicist 01.05.2018 21:08

Хотел бы я дать еще +1 для умножения.

Mad Physicist 01.05.2018 22:27
Ответ принят как подходящий

Вы можете сделать это в чистом 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]])

Хороший ответ; Несмотря на то, что однострочный текст трудно читать, он очень хорошо объяснен :)

rafaelc 01.05.2018 21:12

Спасибо. Я думаю, что ответ @Divakar - это более четкое воплощение той же идеи.

Mad Physicist 01.05.2018 22:24

выбрав этот в качестве ответа, поскольку он отвечает на вопрос, используя только numpy, но, честно говоря, мне больше нравится pandas.groupby от @MadPhysicist, он действительно будет лучше работать для моего окончательного решения, поскольку мне также нужно группировать по месяцам и годам. Спасибо всем.

Infinity Cliff 04.05.2018 22:38

Вы можете взглянуть на 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]]

Другие вопросы по теме