2010-08-04 67 views
30

Tengo un arsenal muy grande numpy (que contienen hasta un millón de elementos) como la siguiente:sustitución rápida de los valores en una matriz 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] 

y un pequeño mapa diccionario para la sustitución de algunos de los elementos en la matriz anterior

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

Me gustaría reemplazar algunos de los elementos de acuerdo con el mapa de arriba. La matriz numpy es realmente grande, y solo un pequeño subconjunto de los elementos (que aparecen como claves en el diccionario) se reemplazará por los valores correspondientes. ¿Cuál es la forma más rápida de hacer esto?

Respuesta

28

Creo que hay método aún más eficaz, pero por ahora, tratar

from numpy import copy 

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

microbenchmark y prueba para la corrección:

#!/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() 

Resultado:

$ 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' Creo ... – katrielalex

+0

Iterar como' para k en d' haría que esto sea lo más rápido posible' – jamylak

+0

Un voto en contra 'numpy.place' como lo menciona @katrielalex, ya que solo desperdicia alrededor de veinte a treinta horas de mi tiempo por tener errores; aparentemente su uso está desaconsejado. "Por lo general, sugiero usar' np.copyto' o (en este caso) boolean indice de fantasía para lograr lo mismo y evitar 'np.place' o' np.putmask'. Me doy cuenta de que en algunos casos esas funciones no son del todo 1: 1 reemplaza por estos ". FWIW No tenía este error, sino otro en el que silenciosamente no funcionaba. – ijoseph

0

Bueno, debe hacer una pasada a través de theArray, y para cada elemento, reemplácela si está en el diccionario.

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

Sería mejor poner len (theArray) en la variable, y utilizar xrange. – fuwaneko

+0

@fuw: Sí xrange, pero poner 'len (theArray)' en una variable no ayudará porque el iterador se evalúa una sola vez. – kennytm

+0

El 'rango' de Py3k es un generador. – katrielalex

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

Suponiendo que los valores se encuentran entre 0 y algún entero máximo, se podría implementar un reemplazar rápida mediante el uso de la numpy-matriz como int->int dict, como a continuación

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

donde primero

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] 

y reemplazando con

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

obtenemos

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

También tenga en cuenta la función 'digitalizar', que se muestra en la respuesta aceptada a esta pregunta: http://stackoverflow.com/questions/13572448/change-values-in-a-numpy-array –

3

Otra forma más general para lograr esto es la función de vectorización:

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

o simplemente' return mapper_dict.get (entrada, entrada) ' – grisaitis

2

Ninguna solución fue publicada aún sin un bucle pitón en la matriz (excepto Celil de uno, lo que sin embargo suponen cifras son "pequeño"), asi que aquí hay una alternativa:

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 forma se crea "idces" proviene de here.

2

I Benchmarked algunas soluciones, y el resultado es inapelable:

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)) 

y me dieron los siguientes resultados:

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 

Método "searchsort" es casi cien veces más rápidas que el bucle "for", y aproximadamente 3600 veces más rápido que el método nude bruteforce. El método de comprensión de listas también es una muy buena compensación entre la simplicidad y la velocidad del código.

+2

ejem ... cien veces? –

0

manera Pythonic sin la necesidad de que los datos sean enteros, pueden ser incluso cadenas:

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

El paquete numpy_indexed (exención de responsabilidad: yo soy su autor) proporciona una solución vectorizado elegante y eficiente para este tipo de problema :

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

el método implementado es similar al enfoque basado searchsorted mencionado por Jean Lescut, pero aún más general. Por ejemplo, los elementos de la matriz no necesitan ser enteros, pero pueden ser de cualquier tipo, incluso los subdramas nd mismos; sin embargo, debe lograr el mismo tipo de rendimiento.

+0

¿podría usted modificar esta línea única para lograr reemplazar elementos de la lista original que no constituyen las teclas del diccionario con otra cosa, por ejemplo, una constante? (en lugar de dejar el valor original) – Tony

+0

No con la verión actual del paquete, pero la función de reasignación tiene un kwarg 'faltante', y si abre el origen de la función, verá que agregar este tipo de comportamiento lo haría de hecho, será fácil. Lo haré para un próximo lanzamiento; hasta entonces, siéntase libre de copiar y pegar la fuente si lo desea. –

0

una solución totalmente vectorizado usando np.in1d y 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 
Cuestiones relacionadas