Sum matrix elements group by indices in Python
Asked Answered
M

3

6

I have two matrix (same row and column): one with float values, which are grouped by indices in the other matrix. As a result, I want a dictionary or a list with the sums of the elements for each index. Indices always start at 0.

A = np.array([[0.52,0.25,-0.45,0.13],[-0.14,-0.41,0.31,-0.41]])
B = np.array([[1,3,1,2],[3,0,2,2]])

RESULT = {0: -0.41, 1: 0.07, 2: 0.03, 3: 0.11}

I found this solution, but I'm searching for a faster one. I'm working with matrix with 784 x 300 cells and this algorithm takes ~28ms to complete.

import numpy as np

def matrix_sum_by_indices(indices,matrix):
    a = np.hstack(indices)
    b = np.hstack(matrix)
    sidx = a.argsort()
    split_idx = np.flatnonzero(np.diff(a[sidx])>0)+1
    out = np.split(b[sidx], split_idx)
    return [sum(x) for x in out]

If you can help me find a better and plain solution to this problem, I'll be grateful!

EDIT: I made a mistake, time to complete is ~8ms in a 300*10 matrix, but ~28ms in a 784x300.

EDIT2: My A elements are float64, so bincount give me ValueError.

Molecule answered 10/7, 2018 at 14:54 Comment(3)
8ms? I would say that is pretty fast. How fast are you looking for?Foreclosure
@Foreclosure I'm looking for something like under the ms. Because this procedure is in a ~6000*100 loops.Molecule
You're probably looking for bincount in some form.Dampier
D
3

You can make use of bincount here:

a = np.array([[0.52,0.25,-0.45,0.13],[-0.14,-0.41,0.31,-0.41]])
b = np.array([[1,3,1,2],[3,0,2,2]])

N = b.max() + 1
id = b + (N*np.arange(b.shape[0]))[:, None] # since you can't apply bincount to a 2D array
np.sum(np.bincount(id.ravel(), a.ravel()).reshape(a.shape[0], -1), axis=0)

Output:

array([-0.41,  0.07,  0.03,  0.11])

As a function:

def using_bincount(indices, matrx):
    N = indices.max() + 1
    id = indices + (N*np.arange(indices.shape[0]))[:, None] # since you can't apply bincount to a 2D array
    return np.sum(np.bincount(id.ravel(), matrx.ravel()).reshape(matrx.shape[0], -1), axis=0)

Timings on this sample:

In [5]: %timeit using_bincount(b, a)
31.1 µs ± 1.74 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

In [6]: %timeit matrix_sum_by_indices(b, a)
61.3 µs ± 2.62 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

In [88]: %timeit scipy.ndimage.sum(a, b, index=[0,1,2,3])
54 µs ± 218 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

(scipy.ndimage.sum should be faster on much larger samples)

Dampier answered 10/7, 2018 at 15:16 Comment(7)
This is working fine, but if my a elements are float64?Molecule
@Molecule can you post an example of how you're creating your matrix?Dampier
@user348302 I'm working with a K-Means Neural Network, so i don't have a generator for the indices matrix.Molecule
And are you positive you are passing the arguments in the correct order? indices are b, values are a? I'll post a function that matches your input namesDampier
Yeah for sure, I'm passing the args in the correct order.. the problem (known in the Python Doc, under bincount) are the float64Molecule
But you are passing an integer array, while using floats as the weightsDampier
Integer array and float64 weightsMolecule
C
1

The numpy_indexed package has efficient and simple solutions to this problem (disclaimer: I am its author):

import numpy_indexed as npi
keys, values = npi.group_by(B.flatten()).sum(A.flatten())
Crocodile answered 10/7, 2018 at 15:16 Comment(2)
How can i use this in Jupyter Notebook?Molecule
There is a conda-forge package and a pip installer available; notebook or not should make no difference.Crocodile
S
1

The following solution, relying on scipy.ndimage.sum is highly optimized for speed:

import numpy as np
A = np.array([[0.52,0.25,-0.45,0.13], [-0.14,-0.41,0.31,-0.41]])
B = np.array([[1,3,1,2], [3,0,2,2]])
import scipy.ndimage
print(scipy.ndimage.sum(A, B, index=[0,1,2,3]))

You may have to work a little for having the index parameter be exactly what you want. It is the list of the indices you want to get in the result. Maybe the following will be a good starting point:

print(scipy.ndimage.sum(A,B, index=np.unique(B)))

but if you know by advance the list of all indices, it will be more efficient to hard-code it here.

Shirker answered 10/7, 2018 at 15:40 Comment(3)
How can I use this with float64?Molecule
You can have whatever you want in the A array; is it what you are asking for?Shirker
Nevermind, It works with float64. It's faster than mine (~10ms), but I'm looking for a faster one, if it's possibleMolecule

© 2022 - 2024 — McMap. All rights reserved.