Suma de datos de una matriz basada en otra matriz en Numpy

Tengo dos matrices numpy 2D (simplificadas en este ejemplo con respecto al tamaño y contenido) con tamaños idénticos.

Una matriz de identificación:

1 1 1 2 2 1 1 2 2 5 1 1 2 5 5 1 2 2 5 5 2 2 5 5 5 

y una matriz de valores:

 14.8 17.0 74.3 40.3 90.2 25.2 75.9 5.6 40.0 33.7 78.9 39.3 11.3 63.6 56.7 11.4 75.7 78.4 88.7 58.6 79.6 32.3 35.3 52.5 13.3 

Mi objective es contar y sumr los valores de la segunda matriz agrupados por los ID de la primera matriz:

 1: (8, 336.8) 2: (9, 453.4) 5: (8, 402.4) 

Puedo hacer esto en un bucle for pero cuando las matrices tienen tamaños en miles en lugar de solo 5×5 y miles de ID únicas, lleva mucho tiempo procesarlo.

¿Tiene numpy un método inteligente o una combinación de métodos para hacer esto?

Este es un enfoque vectorizado para obtener los recuentos de ID y valores sumdos ID-based para el value con una combinación de np.unique y np.bincount

 unqID,idx,IDsums = np.unique(ID,return_counts=True,return_inverse=True) value_sums = np.bincount(idx,value.ravel()) 

Para obtener la salida final como un diccionario, puede utilizar la comprensión de bucle para recostackr los valores sumdos, como así:

 {i:(IDsums[itr],value_sums[itr]) for itr,i in enumerate(unqID)} 

Ejecución de la muestra

 In [86]: ID Out[86]: array([[1, 1, 1, 2, 2], [1, 1, 2, 2, 5], [1, 1, 2, 5, 5], [1, 2, 2, 5, 5], [2, 2, 5, 5, 5]]) In [87]: value Out[87]: array([[ 14.8, 17. , 74.3, 40.3, 90.2], [ 25.2, 75.9, 5.6, 40. , 33.7], [ 78.9, 39.3, 11.3, 63.6, 56.7], [ 11.4, 75.7, 78.4, 88.7, 58.6], [ 79.6, 32.3, 35.3, 52.5, 13.3]]) In [88]: unqID,idx,IDsums = np.unique(ID,return_counts=True,return_inverse=True) ...: value_sums = np.bincount(idx,value.ravel()) ...: In [89]: {i:(IDsums[itr],value_sums[itr]) for itr,i in enumerate(unqID)} Out[89]: {1: (8, 336.80000000000001), 2: (9, 453.40000000000003), 5: (8, 402.40000000000003)} 

Esto es posible con una combinación de algunos métodos simples:

  1. usa numpy.unique para encontrar cada ID
  2. crear una máscara booleana para cada ID
  3. sumr los 1s en la máscara (conteo) y los valores donde la máscara es 1

Esto puede verse así:

 import numpy as np ids = np.array([[1, 1, 1, 2, 2], [1, 1, 2, 2, 5], [1, 1, 2, 5, 5], [1, 2, 2, 5, 5], [2, 2, 5, 5, 5]]) values = np.array([[14.8, 17.0, 74.3, 40.3, 90.2], [25.2, 75.9, 5.6, 40.0, 33.7], [78.9, 39.3, 11.3, 63.6, 56.7], [11.4, 75.7, 78.4, 88.7, 58.6], [79.6, 32.3, 35.3, 52.5, 13.3]]) for i in np.unique(ids): # loop through all IDs mask = ids == i # find entries that match current ID count = np.sum(mask) # number of matches total = np.sum(values[mask]) # values of matches print('{}: ({}, {:.1f})'.format(i, count, total)) #print result # Output: # 1: (8, 336.8) # 2: (9, 453.4) # 5: (8, 402.4) 

El paquete numpy_indexed (descargo de responsabilidad: soy su autor) tiene una funcionalidad para resolver este tipo de problemas de una manera elegante y vectorizada:

 import numpy_indexed as npi group_by = npi.group_by(ID.flatten()) ID_unique, value_sums = group_by.sum(value.flatten()) ID_count = groupy_by.count 

Nota: si desea calcular la sum y el recuento para calcular una media, también hay group_by.mean; además de muchas otras funcionalidades útiles.