Vectorize finding closest value in an array for each element in another array
If the array is large, you should use searchsorted
:
import numpy as np
np.random.seed(0)
known_array = np.random.rand(1000)
test_array = np.random.rand(400)
%%time
differences = (test_array.reshape(1,-1) - known_array.reshape(-1,1))
indices = np.abs(differences).argmin(axis=0)
residual = np.diagonal(differences[indices,])
output:
CPU times: user 11 ms, sys: 15 ms, total: 26 ms
Wall time: 26.4 ms
searchsorted
version:
%%time
index_sorted = np.argsort(known_array)
known_array_sorted = known_array[index_sorted]
idx1 = np.searchsorted(known_array_sorted, test_array)
idx2 = np.clip(idx1 - 1, 0, len(known_array_sorted)-1)
diff1 = known_array_sorted[idx1] - test_array
diff2 = test_array - known_array_sorted[idx2]
indices2 = index_sorted[np.where(diff1 <= diff2, idx1, idx2)]
residual2 = test_array - known_array[indices]
output:
CPU times: user 0 ns, sys: 0 ns, total: 0 ns
Wall time: 311 µs
We can check that the results is the same:
assert np.all(residual == residual2)
assert np.all(indices == indices2)
TL;DR: use numpy.searchsorted()
.
import inspect
from timeit import timeit
import numpy as np
known_array = np.arange(-10, 10)
test_array = np.random.randint(-10, 10, 1000)
number = 1000
def base(known_array, test_array):
def find_nearest(known_array, value):
idx = (np.abs(known_array - value)).argmin()
return idx
indices = np.zeros_like(test_array, dtype=known_array.dtype)
for i in range(len(test_array)):
indices[i] = find_nearest(known_array, test_array[i])
return indices
def diffs(known_array, test_array):
differences = (test_array.reshape(1,-1) - known_array.reshape(-1,1))
indices = np.abs(differences).argmin(axis=0)
return indices
def searchsorted1(known_array, test_array):
index_sorted = np.argsort(known_array)
known_array_sorted = known_array[index_sorted]
idx1 = np.searchsorted(known_array_sorted, test_array)
idx1[idx1 == len(known_array)] = len(known_array)-1
idx2 = np.clip(idx1 - 1, 0, len(known_array_sorted)-1)
diff1 = known_array_sorted[idx1] - test_array
diff2 = test_array - known_array_sorted[idx2]
indices2 = index_sorted[np.where(diff1 <= diff2, idx1, idx2)]
return indices2
def searchsorted2(known_array, test_array):
index_sorted = np.argsort(known_array)
known_array_sorted = known_array[index_sorted]
known_array_middles = known_array_sorted[1:] - np.diff(known_array_sorted.astype('f'))/2
idx1 = np.searchsorted(known_array_middles, test_array)
indices = index_sorted[idx1]
return indices
def time_f(func_name):
return timeit(func_name+"(known_array, test_array)",
'from __main__ import known_array, test_array, ' + func_name, number=number)
print('Speedups:')
base_time = time_f('base')
for func_name in ['diffs', 'searchsorted1', 'searchsorted2']:
print func_name + ' is x%.1f faster than base.' % (base_time / time_f(func_name))
Output:
Speedups:
diffs is x29.9 faster than base.
searchsorted1 is x37.4 faster than base.
searchsorted2 is x64.3 faster than base.