2010-08-04 4 views
30

J'ai un très large éventail numpy (contenant jusqu'à un million d'éléments) comme celui ci-dessous:Remplacement rapide des valeurs dans un tableau numpy

[ 0 1 6 5 1 2 7 6 2 3 8 7 3 4 9 8 5 6 11 10 6 7 12 11 7 
    8 13 12 8 9 14 13 10 11 16 15 11 12 17 16 12 13 18 17 13 14 19 18 15 16 
21 20 16 17 22 21 17 18 23 22 18 19 24 23] 

et une petite carte de dictionnaire pour remplacer certains des éléments en le tableau ci-dessus

{4: 0, 9: 5, 14: 10, 19: 15, 20: 0, 21: 1, 22: 2, 23: 3, 24: 0} 

Je voudrais remplacer certains éléments selon la carte ci-dessus. Le tableau numpy est vraiment grand, et seulement un petit sous-ensemble des éléments (se produisant comme des clés dans le dictionnaire) sera remplacé par les valeurs correspondantes. Quel est le moyen le plus rapide de le faire?

Répondre

28

Je crois qu'il ya la méthode encore plus efficace, mais pour l'instant, essayez

from numpy import copy 

newArray = copy(theArray) 
for k, v in d.iteritems(): newArray[theArray==k] = v 

Microbenchmark et test pour la décision correcte:

#!/usr/bin/env python2.7 

from numpy import copy, random, arange 

random.seed(0) 
data = random.randint(30, size=10**5) 

d = {4: 0, 9: 5, 14: 10, 19: 15, 20: 0, 21: 1, 22: 2, 23: 3, 24: 0} 
dk = d.keys() 
dv = d.values() 

def f1(a, d): 
    b = copy(a) 
    for k, v in d.iteritems(): 
     b[a==k] = v 
    return b 

def f2(a, d): 
    for i in xrange(len(a)): 
     a[i] = d.get(a[i], a[i]) 
    return a 

def f3(a, dk, dv): 
    mp = arange(0, max(a)+1) 
    mp[dk] = dv 
    return mp[a] 


a = copy(data) 
res = f2(a, d) 

assert (f1(data, d) == res).all() 
assert (f3(data, dk, dv) == res).all() 

Résultat:

$ python2.7 -m timeit -s 'from w import f1,f3,data,d,dk,dv' 'f1(data,d)' 
100 loops, best of 3: 6.15 msec per loop 

$ python2.7 -m timeit -s 'from w import f1,f3,data,d,dk,dv' 'f3(data,dk,dv)' 
100 loops, best of 3: 19.6 msec per loop 
+3

'numpy.place' Je pense ... – katrielalex

+0

L'itération comme' pour k in d' rendrait ceci aussi vite que possible' – jamylak

+0

Un vote contre 'numpy.place' comme mentionné par @katrielalex, comme il a juste gaspillé une vingtaine de trente heures de mon temps en étant buggy; Apparemment, son utilisation est découragée. "Je suggère généralement d'utiliser' np.copyto' ou (dans ce cas) l'indexation booléenne de fantaisie pour obtenir la même chose et éviter 'np.place' ou' np.putmask' Je réalise que dans certains cas, ces fonctions ne sont pas tout à fait 1: 1 remplace par ceux-ci. " FWIW Je n'avais pas ce bug, mais un autre où il ne fonctionnait pas en silence. – ijoseph

0

Eh bien, vous devez faire un passage à travers theArray, et pour chaque élément le remplacer s'il est dans le dictionnaire.

for i in xrange(len(theArray)): 
    if foo[ i ] in dict: 
     foo[ i ] = dict[ foo[ i ] ] 
+0

Il serait préférable de mettre len (theArray) dans la variable, et utiliser xrange. – fuwaneko

+0

@fuw: Oui xrange, mais mettre 'len (theArray)' dans une variable ne va pas aider car l'itérateur est évalué une fois seulement. – kennytm

+0

La gamme de Py3k est un générateur. – katrielalex

0
for i in xrange(len(the_array)): 
    the_array[i] = the_dict.get(the_array[i], the_array[i]) 
16

En supposant que les valeurs sont comprises entre 0 et un entier maximal, on pourrait mettre en oeuvre un remplacement rapide en utilisant le numpy-réseau comme int->int dict, comme ci-dessous

mp = numpy.arange(0,max(data)+1) 
mp[replace.keys()] = replace.values() 
data = mp[data] 

où premier

data = [ 0 1 6 5 1 2 7 6 2 3 8 7 3 4 9 8 5 6 11 10 6 7 12 11 7 
    8 13 12 8 9 14 13 10 11 16 15 11 12 17 16 12 13 18 17 13 14 19 18 15 16 
21 20 16 17 22 21 17 18 23 22 18 19 24 23] 

et en remplaçant par

replace = {4: 0, 9: 5, 14: 10, 19: 15, 20: 0, 21: 1, 22: 2, 23: 3, 24: 0} 

on obtient

data = [ 0 1 6 5 1 2 7 6 2 3 8 7 3 0 5 8 5 6 11 10 6 7 12 11 7 
    8 13 12 8 5 10 13 10 11 16 15 11 12 17 16 12 13 18 17 13 10 15 18 15 16 
    1 0 16 17 2 1 17 18 3 2 18 15 0 3] 
+0

Notez également la fonction 'digitize', montré dans la réponse acceptée à cette question: http://stackoverflow.com/questions/13572448/change-values-in-a-numpy-array –

3

Une autre façon plus générale pour y parvenir est fonction vectorisation:

import numpy as np 

data = np.array([0, 1, 6, 5, 1, 2, 7, 6, 2, 3, 8, 7, 3, 4, 9, 8, 5, 6, 11, 10, 6, 7, 12, 11, 7, 8, 13, 12, 8, 9, 14, 13, 10, 11, 16, 15, 11, 12, 17, 16, 12, 13, 18, 17, 13, 14, 19, 18, 15, 16, 21, 20, 16, 17, 22, 21, 17, 18, 23, 22, 18, 19, 24, 23]) 
mapper_dict = {4: 0, 9: 5, 14: 10, 19: 15, 20: 0, 21: 1, 22: 2, 23: 3, 24: 0} 

def mp(entry): 
    return mapper_dict[entry] if entry in mapper_dict else entry 
mp = np.vectorize(mp) 

print mp(data) 
+2

ou simplement' return mapper_dict.get (entrée, entrée) ' – grisaitis

2

Aucune solution a été publié encore sans une boucle python sur le tableau (à l'exception Celil de l'un, qui supposent cependant les chiffres sont "petit"), alors voici une alternative:

def replace(arr, rep_dict): 
    """Assumes all elements of "arr" are keys of rep_dict""" 

    # Removing the explicit "list" breaks python3 
    rep_keys, rep_vals = array(list(zip(*sorted(rep_dict.items())))) 

    idces = digitize(arr, rep_keys, right=True) 
    # Notice rep_keys[digitize(arr, rep_keys, right=True)] == arr 

    return rep_vals[idces] 

la voie "idces" est créée vient de here.

2

Je benchmarkée des solutions, et le résultat est sans appel:

import timeit 
import numpy as np 

array = 2 * np.round(np.random.uniform(0,10000,300000)).astype(int) 
from_values = np.unique(array) # pair values from 0 to 2000 
to_values = np.arange(from_values.size) # all values from 0 to 1000 
d = dict(zip(from_values, to_values)) 

def method_for_loop(): 
    out = array.copy() 
    for from_value, to_value in zip(from_values, to_values) : 
     out[out == from_value] = to_value 
    print('Check method_for_loop :', np.all(out == array/2)) # Just checking 
print('Time method_for_loop :', timeit.timeit(method_for_loop, number = 1)) 

def method_list_comprehension(): 
    out = [d[i] for i in array] 
    print('Check method_list_comprehension :', np.all(out == array/2)) # Just checking 
print('Time method_list_comprehension :', timeit.timeit(method_list_comprehension, number = 1)) 

def method_bruteforce(): 
    idx = np.nonzero(from_values == array[:,None])[1] 
    out = to_values[idx] 
    print('Check method_bruteforce :', np.all(out == array/2)) # Just checking 
print('Time method_bruteforce :', timeit.timeit(method_bruteforce, number = 1)) 

def method_searchsort(): 
    sort_idx = np.argsort(from_values) 
    idx = np.searchsorted(from_values,array,sorter = sort_idx) 
    out = to_values[sort_idx][idx] 
    print('Check method_searchsort :', np.all(out == array/2)) # Just checking 
print('Time method_searchsort :', timeit.timeit(method_searchsort, number = 1)) 

et moi avons eu les résultats suivants:

Check method_for_loop : True 
Time method_for_loop : 2.6411612760275602 

Check method_list_comprehension : True 
Time method_list_comprehension : 0.07994363596662879 

Check method_bruteforce : True 
Time method_bruteforce : 11.960559037979692 

Check method_searchsort : True 
Time method_searchsort : 0.03770717792212963 

La méthode "searchsort" est presque cent fois plus rapide que la boucle «pour», et environ 3600 fois plus rapide que la méthode bruteforce. La méthode de compréhension de liste est également un très bon compromis entre la simplicité et la rapidité du code.

+2

Ahem ... cent fois? –

0

façon Pythonic sans qu'il soit nécessaire que les données soient entier, peuvent même être des chaînes:

from scipy.stats import rankdata 
import numpy as np 

data = np.random.rand(100000) 
replace = {data[0]: 1, data[5]: 8, data[8]: 10} 

arr = np.vstack((replace.keys(), replace.values())).transpose() 
arr = arr[arr[:,1].argsort()] 

unique = np.unique(data) 
mp = np.vstack((unique, unique)).transpose() 
mp[np.in1d(mp[:,0], arr),1] = arr[:,1] 
data = mp[rankdata(data, 'dense')-1][:,1] 
2

Le paquet numpy_indexed (disclaimer: Je suis son auteur) fournit une solution vectorisé élégante et efficace à ce type de problème :

import numpy_indexed as npi 
remapped_array = npi.remap(theArray, list(dict.keys()), list(dict.values())) 

le procédé mis en oeuvre est similaire à l'approche searchsorted mentionnée par Jean Lescut, mais encore plus générale. Par exemple, les éléments du tableau n'ont pas besoin d'être ints, mais peuvent être de n'importe quel type, même nd-subarrays eux-mêmes; Pourtant, il devrait atteindre le même type de performance.

+0

pourriez-vous modifier ce one-liner pour réaliser des éléments de remplacement de la liste d'origine qui ne constituent pas des clés de dictionnaire avec quelque chose d'autre, disons par exemple une constante? (au lieu de laisser la valeur d'origine) – Tony

+0

Pas avec la verrsion courante du paquet, mais la fonction de remappage a un kwarg 'manquant', et si vous ouvrez la source de la fonction, vous verrez que l'ajout de ce type de comportement en effet soyez facile. Je fais ça pour une prochaine sortie; jusque là, n'hésitez pas à copier-coller la source si vous le souhaitez. –

0

Une solution entièrement vectorisé à l'aide np.in1d et np.searchsorted:

replace = numpy.array([list(replace.keys()), list(replace.values())]) # Create 2D replacement matrix 
mask = numpy.in1d(data, replace[0, :])         # Find elements that need replacement 
data[mask] = replace[1, numpy.searchsorted(replace[0, :], data[mask])] # Replace elements 
Questions connexes