Суммирование выбранных элементов матрицы в Python

У меня есть матрица [n x n], содержащая значения, принадлежащие разным группам, и вектор [1 x n], определяющий, к какой группе принадлежит каждый элемент. (n обычно ~1E4, в этом примере n=4)

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

Я использую np.where() для вычисления индексов, где расположены элементы каждой группы. Когда я использую рассчитанные индексы, я не получаю ожидаемых элементов, потому что я выбираю пары позиций вместо диапазонов (я привык к Matlab, где я могу просто выбрать M(idx1,idx2) ).

import numpy as np

n=4
M = np.random.rand(n,n)
print(M)

# This vector defines to which group each element belong
belongToGroup = np.array([0, 1, 0, 2])

nGroups=np.max(belongToGroup);

# Calculate a matrix obtained by summing elements belonging to the same group
M_sum = np.zeros((nGroups+1,nGroups+1))
for g1 in range(nGroups+1):
    idxG1 = np.where(belongToGroup==g1)
    for g2 in range(nGroups+1):
        idxG2 = np.where(belongToGroup==g2)
        print('g1 = ' + str(g1))
        print('g2 = ' + str(g2))
        print(idxG1[0])
        print(idxG2[0])
        print(M[idxG1[0],idxG2[0]])
        print(np.sum(M[idxG1[0],idxG2[0]]))
        M_sum[g1,g2]=np.sum(M[idxG1[0],idxG2[0]])

print('')
print('Example of the problem:')
print('Elements I would like to sum to obtain M_sum[0,0]')
print(M[0:2,0:2])
print('Elements that are summed instead')
print(M[[0,1],[0,1]])

Пример проблемы: в приведенном выше примере элемент M_sum[0,0] должен быть суммой M[0,0], M[0,1], M[1,0] и M[1,1 ] Вместо этого он рассчитывается как сумма M[0,0] и M[1,1]


person user9998992    schedule 02.04.2019    source источник


Ответы (2)


В MATLAB индексация с двумя списками (фактически матрицами) выбирает блок. numpy, с другой стороны, пытается транслировать массивы индексации друг против друга и возвращает выбранные точки. Его поведение близко к тому, что делает sub2ind в MATLAB.

In [971]: arr = np.arange(16).reshape(4,4)                                      
In [972]: arr                                                                   
Out[972]: 
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11],
       [12, 13, 14, 15]])
In [973]: i1, i2 = np.array([0,2,3]), np.array([1,2,0])                         

Индексирование двумя массивами 1d одинакового размера:

In [974]: arr[i1,i2]
Out[974]: array([ 1, 10, 12])

Фактически это возвращает [arr[0,1], arr[2,2], arr[3,0]], по одному элементу для каждой точки совпадающих индексов.

Но если я превращаю один индекс в «вектор-столбец», он выбирает из строк, а i2 выбирает из столбцов.

In [975]: arr[i1[:,None], i2]                                                   
Out[975]: 
array([[ 1,  2,  0],
       [ 9, 10,  8],
       [13, 14, 12]])

MATLAB упрощает индексирование блоков, в то время как индивидуальный доступ усложняется. В numpy доступ к блоку немного сложнее, хотя основная механика такая же.

В вашем примере i1[0] и i2[0] могут быть такие массивы, как:

array([0, 2]), array([3])
(2,) (1,)

Массив формы (1,) может также транслироваться с массивом (2,) или с массивом (2,1). Ваш код потерпит неудачу, если вместо is[0] будет np.array([0,1,2]), массив (3,), который не может быть сопряжен с массивом (2,). Но с (2,1) получается блок (2,3).

person hpaulj    schedule 02.04.2019
comment
Спасибо за ответ. Я попытался изменить следующую строку: M_sum[g1,g2]=np.sum(M[idxG1[0],idxG2[0]]) на следующую: M_sum[g1,g2]=np.sum(M[idxG1[ :,None],idxG2[0]]) Возможно, я неправильно понял. Я получаю сообщение об ошибке: индексы кортежа должны быть целыми числами, а не кортежем - person user9998992; 02.04.2019
comment
Вам все еще нужно использовать [0]. idxG1, созданный where, является tuple, по одному элементу на измерение источника. idxG1[0] дает вам массив индексации для 1-го измерения. idxG1[0][:,None] добавляет к этому измерение. Это :,None выражение на самом деле не является tuple. В Python почти каждое выражение, содержащее запятую, является кортежем :) - person hpaulj; 02.04.2019

Вы можете использовать np.ix_ для получения поведения в стиле Matlab:

A = np.arange(9).reshape(3, 3)
A[[1,2],[0,2]]
# array([3, 8])
A[np.ix_([1,2],[0,2])]
# array([[3, 5],
#        [6, 8]])

Под капотом np.ix_ делает то, что подробно описывает @hpaulj:

np.ix_([1,2],[0,2])
# (array([[1],
#        [2]]), array([[0, 2]]))

Вы можете применить это к вашей конкретной проблеме следующим образом:

M = np.random.randint(0, 10, (n, n))
M
# array([[6, 2, 7, 1],
#        [6, 7, 9, 5],
#        [9, 4, 3, 2],
#        [3, 1, 7, 9]])
idx = np.array([0, 1, 0, 2])

ng = idx.max() + 1
out = np.zeros((ng, ng), M.dtype)
np.add.at(out, np.ix_(idx, idx), M)
out
# array([[25,  6,  3],
#        [15,  7,  5],
#        [10,  1,  9]])

Кстати: есть более быстрое, но менее очевидное решение, основанное на плоской индексации:

np.bincount(np.ravel_multi_index(np.ix_(idx, idx), (ng, ng)).ravel(), M.ravel(), ng*ng).reshape(ng, ng)
# array([[25.,  6.,  3.],
#        [15.,  7.,  5.],
#        [10.,  1.,  9.]])
person Paul Panzer    schedule 02.04.2019
comment
Спасибо, это тоже сработало. Подводя итог: НЕПРАВИЛЬНО: M_sum[g1,g2]=np.sum(M[idxG1[0],idxG2[0]]) ПРАВИЛЬНО 1 (спасибо @hpaulj) M_sum[g1,g2]=np.sum(M[ idxG1[0][:,None],idxG2[0]]) ПРАВИЛЬНО 2 (спасибо, Пол Панцер) M_sum[g1,g2]=np.sum(M[np.ix_(idxG1[0],idxG2[0] )]) - person user9998992; 03.04.2019