J'ai une grande image 10000x10000 éléments, que je bin dans quelques centaines de secteurs/bacs différents. Je dois ensuite effectuer un calcul itératif sur les valeurs contenues dans chaque bac.
Comment extraire les indices de chaque groupe pour effectuer efficacement mes calculs en utilisant les valeurs des groupes?
Ce que je recherche, c’est une solution qui évite le goulet d’étranglement de devoir sélectionner chaque fois ind == j
dans mon grand tableau. Existe-t-il un moyen d'obtenir directement, en une fois, les indices des éléments appartenant à chaque casier?
Une façon de réaliser ce dont j'ai besoin est d'utiliser un code comme celui-ci (voir, par exemple, THIS réponse associée), où je numérise mes valeurs, puis une boucle en j sélectionnant des index numérisés égaux à j comme ci-dessous.
import numpy as np
# This function func() is just a place mark for a much more complicated function.
# I am aware that my problem could be easily speed up in the specific case of
# of the sum() function, but I am looking for a general solution to the problem.
def func(x):
y = np.sum(x)
return y
vals = np.random.random(1e8)
nbins = 100
bins = np.linspace(0, 1, nbins+1)
ind = np.digitize(vals, bins)
result = [func(vals[ind == j]) for j in range(1, nbins)]
Ce que je recherche, c’est une solution qui évite le goulet d’étranglement de devoir sélectionner chaque fois ind == j
dans mon grand tableau. Existe-t-il un moyen d'obtenir directement, en une fois, les indices des éléments appartenant à chaque casier?
L’approche ci-dessus s’avère être la même que celle appliquée dans scipy.stats.binned_statistic , dans le cas général d’une fonction définie par l’utilisateur. En utilisant directement Scipy, une sortie identique peut être obtenue avec ce qui suit
import numpy as np
from scipy.stats import binned_statistics
vals = np.random.random(1e8)
results = binned_statistic(vals, vals, statistic=func, bins=100, range=[0, 1])[0]
Une autre alternative à Scipy consiste à utiliser scipy.ndimage.measurements.labeled_comprehension . En utilisant cette fonction, l'exemple ci-dessus deviendrait
import numpy as np
from scipy.ndimage import labeled_comprehension
vals = np.random.random(1e8)
nbins = 100
bins = np.linspace(0, 1, nbins+1)
ind = np.digitize(vals, bins)
result = labeled_comprehension(vals, ind, np.arange(1, nbins), func, float, 0)
Malheureusement, cette forme est également inefficace et, en particulier, elle n’a aucun avantage en termes de vitesse par rapport à mon exemple initial.
Pour clarifier davantage, ce que je recherche, c’est une fonctionnalité équivalente au mot clé REVERSE_INDICES
dans la fonction HISTOGRAM
du langage IDL HERE . Cette fonctionnalité très utile peut-elle être efficacement répliquée en Python?
Plus précisément, en utilisant le langage IDL, l’exemple ci-dessus pourrait être écrit comme suit:
vals = randomu(s, 1e8)
nbins = 100
bins = [0:1:1./nbins]
h = histogram(vals, MIN=bins[0], MAX=bins[-2], NBINS=nbins, REVERSE_INDICES=r)
result = dblarr(nbins)
for j=0, nbins-1 do begin
jbins = r[r[j]:r[j+1]-1] ; Selects indices of bin j
result[j] = func(vals[jbins])
endfor
L'implémentation IDL ci-dessus est environ 10 fois plus rapide que celle de Numpy, en raison du fait qu'il n'est pas nécessaire de sélectionner les index des bacs pour chaque bac. Et la différence de vitesse en faveur de la mise en œuvre IDL augmente avec le nombre de bacs.
J'ai constaté qu'un constructeur de matrice clairsemée peut atteindre le résultat souhaité de manière très efficace. C'est un peu obscur mais on peut en abuser pour cela. La fonction ci-dessous peut être utilisée presque de la même manière que scipy.stats.binned_statistic mais peut être beaucoup plus rapide
import numpy as np
from scipy.sparse import csr_matrix
def binned_statistic(x, values, func, nbins, range):
'''The usage is nearly the same as scipy.stats.binned_statistic'''
N = len(values)
r0, r1 = range
digitized = (float(nbins)/(r1 - r0)*(x - r0)).astype(int)
S = csr_matrix((values, [digitized, np.arange(N)]), shape=(nbins, N))
return [func(group) for group in np.split(S.data, S.indptr[1:-1])]
J'ai évité np.digitize
car il n'utilise pas le fait que tous les bacs ont la même largeur et sont donc lents, mais la méthode que j'ai utilisée risque de ne pas gérer tous les cas Edge parfaitement.
Je suppose que le binning, effectué dans l'exemple avec digitize
, ne peut pas être modifié. C'est une façon de faire, où vous faites le tri une fois pour toutes.
vals = np.random.random(1e4)
nbins = 100
bins = np.linspace(0, 1, nbins+1)
ind = np.digitize(vals, bins)
new_order = argsort(ind)
ind = ind[new_order]
ordered_vals = vals[new_order]
# slower way of calculating first_hit (first version of this post)
# _,first_hit = unique(ind,return_index=True)
# faster way:
first_hit = searchsorted(ind,arange(1,nbins-1))
first_hit.sort()
#example of using the data:
for j in range(nbins-1):
#I am using a plotting function for your f, to show that they cluster
plot(ordered_vals[first_hit[j]:first_hit[j+1]],'o')
La figure montre que les bacs sont en fait des grappes comme prévu:
Vous pouvez diviser par deux le temps de calcul en triant d'abord le tableau, puis utilisez np.searchsorted
.
vals = np.random.random(1e8)
vals.sort()
nbins = 100
bins = np.linspace(0, 1, nbins+1)
ind = np.digitize(vals, bins)
results = [func(vals[np.searchsorted(ind,j,side='left'):
np.searchsorted(ind,j,side='right')])
for j in range(1,nbins)]
En utilisant 1e8
comme cas de test, je passe de 34 secondes de calcul à environ 17 secondes.
Pandas a un code de regroupement très rapide (je pense que c'est écrit en C), donc si vous voulez bien charger la bibliothèque, vous pouvez le faire:
import pandas as pd
pdata=pd.DataFrame({'vals':vals,'ind':ind})
resultsp = pdata.groupby('ind').sum().values
ou plus généralement:
pdata=pd.DataFrame({'vals':vals,'ind':ind})
resultsp = pdata.groupby('ind').agg(func).values
Bien que cette dernière soit plus lente pour les fonctions d’agrégation standard (comme somme, moyenne, etc.)
Une solution efficace consiste à utiliser le paquet numpy_indexed package (disclaimer: je suis son auteur):
import numpy_indexed as npi
npi.group_by(ind).split(vals)