Найти уникальные элементы массива с плавающей запятой в numpy (с использованием значения delta)
У меня есть значение ndarray
значений с плавающей запятой в numpy, и я хочу найти уникальные значения этого массива. Конечно, у этого есть проблемы из-за точности с плавающей запятой... поэтому я хочу иметь возможность устанавливать значение дельта для использования при сравнении при разработке тех элементов, которые уникальны.
Есть ли способ сделать это? На данный момент я просто делаю:
unique(array)
Что дает мне что-то вроде:
array([ -Inf, 0.62962963, 0.62962963, 0.62962963, 0.62962963,
0.62962963])
где значения, которые выглядят одинаково (с количеством отображаемых десятичных знаков), явно немного отличаются.
Ответы
Ответ 1
Не в состоянии ли floor
и round
выполнить требование OP в некоторых случаях?
np.floor([5.99999999, 6.0]) # array([ 5., 6.])
np.round([6.50000001, 6.5], 0) #array([ 7., 6.])
Я бы это сделал (и это может быть не оптимальным (и, конечно, медленнее, чем другие ответы)) примерно так:
import numpy as np
TOL = 1.0e-3
a = np.random.random((10,10))
i = np.argsort(a.flat)
d = np.append(True, np.diff(a.flat[i]))
result = a.flat[i[d>TOL]]
Конечно, этот метод исключит всех, кроме самого большого члена пробега значений, которые соответствуют допускам любого другого значения, что означает, что вы не можете найти какие-либо уникальные значения в массиве, если все значения значительно близки, max-min больше, чем допуск.
Здесь по существу тот же алгоритм, но его легче понять и он должен быть быстрее, так как он избегает шага индексации:
a = np.random.random((10,))
b = a.copy()
b.sort()
d = np.append(True, np.diff(b))
result = b[d>TOL]
OP также может захотеть заглянуть в scipy.cluster
(для причудливой версии этого метода) или numpy.digitize
(для причудливой версии двух других методов)
Ответ 2
Другая возможность - просто округлить до ближайшего желаемого допуска:
np.unique(a.round(decimals=4))
где a
- ваш исходный массив.
Изменить: Просто отметим, что мое решение и @unutbu почти идентичны по скорости (мой может быть на 5% быстрее) в соответствии с моими таймингами, так что либо это хорошее решение.
Изменить # 2: Это предназначено для решения проблемы Павла. Это определенно медленнее, и могут быть некоторые оптимизации, которые можно сделать, но я отправляю их как есть, чтобы продемонстрировать stratgey:
def eclose(a,b,rtol=1.0000000000000001e-05, atol=1e-08):
return np.abs(a - b) <= (atol + rtol * np.abs(b))
x = np.array([6.4,6.500000001, 6.5,6.51])
y = x.flat.copy()
y.sort()
ci = 0
U = np.empty((0,),dtype=y.dtype)
while ci < y.size:
ii = eclose(y[ci],y)
mi = np.max(ii.nonzero())
U = np.concatenate((U,[y[mi]]))
ci = mi + 1
print U
Это должно быть прилично быстро, если в диапазоне точности много повторяющихся значений, но если многие из значений уникальны, то это будет медленным. Кроме того, может быть лучше установить U
вверх как список и добавить через цикл while, но это подпадает под "дальнейшую оптимизацию".
Ответ 3
Я только что заметил, что принятый ответ не работает. Например. этот случай:
a = 1-np.random.random(20)*0.05
<20 uniformly chosen values between 0.95 and 1.0>
np.sort(a)
>>>> array([ 0.9514548 , 0.95172218, 0.95454535, 0.95482343, 0.95599525,
0.95997008, 0.96385762, 0.96679186, 0.96873524, 0.97016127,
0.97377579, 0.98407259, 0.98490461, 0.98964753, 0.9896733 ,
0.99199411, 0.99261766, 0.99317258, 0.99420183, 0.99730928])
TOL = 0.01
Результаты в:
a.flat[i[d>TOL]]
>>>> array([], dtype=float64)
Просто потому, что ни одно из значений сортированного входного массива не находится на достаточном расстоянии, чтобы быть как минимум "TOL" appart, тогда как правильный результат должен быть:
>>>> array([ 0.9514548, 0.96385762, 0.97016127, 0.98407259,
0.99199411])
(хотя это зависит от того, как вы решаете, какое значение взять внутри "TOL" )
Вы должны использовать тот факт, что целые числа не страдают от такого механического прецизионного эффекта:
np.unique(np.floor(a/TOL).astype(int))*TOL
>>>> array([ 0.95, 0.96, 0.97, 0.98, 0.99])
который выполняет в 5 раз быстрее предлагаемого решения (согласно% timeit).
Обратите внимание, что ".astype(int)" является необязательным, хотя его удаление ухудшает производительность в 1,5 раза, учитывая, что извлечение uniques из массива int выполняется намного быстрее.
Возможно, вы захотите добавить половину "TOL" к результатам uniques, чтобы компенсировать эффект покрытия пола:
(np.unique(np.floor(a/TOL).astype(int))+0.5)*TOL
>>>> array([ 0.955, 0.965, 0.975, 0.985, 0.995])
Ответ 4
Как насчет чего-то вроде
np.unique1d(np.floor(1e7*x)/1e7)
где x
- ваш исходный массив.