Vectorización de una operación de corte de Numpy

Digamos que tengo un vector Numpy,

A = zeros(100) 

y lo divido en subvectores por una lista de puntos de interrupción que se indexan en A , por ejemplo,

 breaks = linspace(0, 100, 11, dtype=int) 

Entonces, el i -ésimo subvector se encuentra entre los índices breaks[i] (inclusive) y breaks[i+1] (exclusivo). Los descansos no están necesariamente separados, esto es solo un ejemplo. Sin embargo, siempre serán estrictamente crecientes.

Ahora quiero operar en estos subvectores. Por ejemplo, si quiero establecer todos los elementos del i -ésimo subvector en i , podría hacer:

 for i in range(len(breaks) - 1): A[breaks[i] : breaks[i+1]] = i 

O tal vez quiera calcular los medios del subvector:

 b = empty(len(breaks) - 1) for i in range(len(breaks) - 1): b = A[breaks[i] : breaks[i+1]].mean() 

Y así.

¿Cómo puedo evitar el uso for bucles y en su lugar vectorizar estas operaciones?

Realmente no hay una respuesta única a tu pregunta, sino varias técnicas que puedes usar como bloques de construcción. Otro que puede encontrar útil:

Todos los ufuncs numpy tienen un método .reduceat , que puede utilizar para su ventaja para algunos de sus cálculos:

 >>> a = np.arange(100) >>> breaks = np.linspace(0, 100, 11, dtype=np.intp) >>> counts = np.diff(breaks) >>> counts array([10, 10, 10, 10, 10, 10, 10, 10, 10, 10]) >>> sums = np.add.reduceat(a, breaks[:-1], dtype=np.float) >>> sums array([ 45., 145., 245., 345., 445., 545., 645., 745., 845., 945.]) >>> sums / counts # ie the mean array([ 4.5, 14.5, 24.5, 34.5, 44.5, 54.5, 64.5, 74.5, 84.5, 94.5]) 

Puedes usar np.cumsum simple –

 import numpy as np # Form zeros array of same size as input array and # place ones at positions where intervals change A1 = np.zeros_like(A) A1[breaks[1:-1]] = 1 # Perform cumsum along it to create a staircase like array, as the final output out = A1.cumsum() 

Ejecución de la muestra

 In [115]: A Out[115]: array([3, 8, 0, 4, 6, 4, 8, 0, 2, 7, 4, 9, 3, 7, 3, 8, 6, 7, 1, 6]) In [116]: breaks Out[116]: array([ 0, 4, 9, 11, 18, 20]) In [142]: out Out[142]: array([0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 3, 3, 3, 3, 3, 3, 3, 4, 4]..) 

Si desea tener valores medios de esos subvectores de A , puede usar np.bincount

 mean_vals = np.bincount(out, weights=A)/np.bincount(out) 

Si desea ampliar esta funcionalidad y, en su lugar, utilizar una función personalizada , es posible que desee consultar el equivalente en el conjunto de MATLAB para Python/Numpy : accum cuyo código fuente está disponible aquí .

Podrías usar np.repeat

 In [35]: np.repeat(np.arange(0, len(breaks)-1), np.diff(breaks)) Out[35]: array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9]) 

Para calcular estadísticas agrupadas arbitrarias, puede usar scipy.stats.binned_statistic :

 import numpy as np import scipy.stats as stats breaks = np.linspace(0, 100, 11, dtype=int) A = np.random.random(100) means, bin_edges, binnumber = stats.binned_statistic( x=np.arange(len(A)), values=A, statistic='mean', bins=breaks) 

stats.binned_statistic puede calcular medias, medianas, cuentas, sums; o, para calcular una estadística arbitraria para cada bin, puede pasar un llamable al parámetro statistic :

 def func(values): return values.mean() funcmeans, bin_edges, binnumber = stats.binned_statistic( x=np.arange(len(A)), values=A, statistic=func, bins=breaks) assert np.allclose(means, funcmeans)