Быстрая замена значений в массиве numpy
У меня очень большой массив 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]
и небольшую карту словаря для замены некоторых элементов в указанном выше массиве
{4: 0, 9: 5, 14: 10, 19: 15, 20: 0, 21: 1, 22: 2, 23: 3, 24: 0}
Я хотел бы заменить некоторые элементы в соответствии с приведенной выше картой. Массив numpy действительно большой, и только небольшое подмножество элементов (встречающихся как ключи в словаре) будет заменено соответствующими значениями. Каков самый быстрый способ сделать это?
Ответы
Ответ 1
Я считаю, что есть еще более эффективный метод, но на данный момент попробуйте
from numpy import copy
newArray = copy(theArray)
for k, v in d.iteritems(): newArray[theArray==k] = v
Microbenchmark и проверка правильности:
#!/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()
Результат:
$ 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
Ответ 2
Предполагая, что значения находятся между 0 и некоторым максимальным целым числом, можно было бы реализовать быструю замену с помощью numpy-массива как int->int
dict, как показано ниже
mp = numpy.arange(0,max(data)+1)
mp[replace.keys()] = replace.values()
data = mp[data]
где сначала
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]
и заменяя
replace = {4: 0, 9: 5, 14: 10, 19: 15, 20: 0, 21: 1, 22: 2, 23: 3, 24: 0}
получаем
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]
Ответ 3
Еще один общий способ достижения этой цели - векторизация функций:
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)
Ответ 4
Никакое решение не было опубликовано еще без цикла python в массиве (кроме Celil one, который, однако, считается, что числа "маленькие" ), так что вот альтернатива:
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]
способ создания "idces" происходит от здесь.
Ответ 5
Я сравнивал некоторые решения, и результат не имеет апелляции:
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))
И я получил следующие результаты:
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
Метод "searchsort" почти в сто раз быстрее, чем цикл "for", и 3600 раз быстрее, чем метод numless bruteforce.
Метод понимания списка также является очень хорошим компромиссом между простотой и скоростью кода.
Ответ 6
Пакет numpy_indexed (отказ от ответственности: я являюсь его автором) предоставляет элегантное и эффективное векторизованное решение этой проблемы:
import numpy_indexed as npi
remapped_array = npi.remap(theArray, list(dict.keys()), list(dict.values()))
Реализованный метод похож на подход, основанный на поиске на основе поиска, упомянутый Жаном Лесктом, но еще более общий. Например, элементы массива не должны быть ints, но могут быть любыми типами, даже nd-subarrays сами; но он должен достичь такого же качества.
Ответ 7
Ну, вам нужно сделать один проход через theArray
, и для каждого элемента замените его, если он находится в словаре.
for i in xrange( len( theArray ) ):
if foo[ i ] in dict:
foo[ i ] = dict[ foo[ i ] ]
Ответ 8
for i in xrange(len(the_array)):
the_array[i] = the_dict.get(the_array[i], the_array[i])
Ответ 9
Питонический путь без необходимости в том, чтобы данные были целыми, могут быть четными строками:
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]
Ответ 10
Полностью векторизованное решение с использованием np.in1d
и 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