Найти уникальные элементы массива с плавающей точкой в ​​numpy (при сравнении с использованием дельта-значения) - PullRequest
17 голосов
/ 25 марта 2011

У меня есть ndarray значений с плавающей точкой в ​​numpy, и я хочу найти уникальные значения этого массива. Конечно, у этого есть проблемы из-за точности с плавающей запятой ... поэтому я хочу иметь возможность установить дельта-значение, которое будет использоваться для сравнения при определении того, какие элементы являются уникальными.

Есть ли способ сделать это? На данный момент я просто делаю:

unique(array)

Что дает мне что-то вроде:

array([       -Inf,  0.62962963,  0.62962963,  0.62962963,  0.62962963,
    0.62962963])

где значения, которые выглядят одинаково (для количества отображаемых десятичных разрядов), очевидно, немного отличаются.

Ответы [ 4 ]

12 голосов
/ 25 марта 2011

Другая возможность заключается в округлении до ближайшего желаемого допуска:

np.unique(a.round(decimals=4))

, где a - ваш исходный массив.

Редактировать: Просто отметитьчто мое решение и @ unutbu почти одинаковы по скорости (мое, может быть, на 5% быстрее) в соответствии с моими таймингами, так что либо это хорошее решение.

Правка # 2: Этопредназначен для решения проблемы Пола.Это определенно медленнее и могут быть некоторые оптимизации, которые можно сделать, но я выкладываю это как есть, чтобы продемонстрировать стратегию:

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, но это подпадает под «дальнейшую оптимизацию».

5 голосов
/ 25 марта 2011

Разве floor и round оба не соответствуют требованиям ОП в некоторых случаях?

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]

ОП может также захотеть посмотреть scipy.cluster (для причудливой версии этого метода) или numpy.digitize (для причудливой версии двух других методов)

2 голосов
/ 27 июня 2016

Я только что заметил, что принятый ответ не работает.Например, в этом случае:

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», в то время какправильный результат должен быть:

>>>> 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 раза, учитывая, что извлечение уникальных значений из массива int выполняется намного быстрее.

Возможно, вы захотите добавить половину «TOL» к результатам уникальных операций, чтобы компенсировать настилэффект:

(np.unique(np.floor(a/TOL).astype(int))+0.5)*TOL
>>>> array([ 0.955,  0.965,  0.975,  0.985,  0.995])
1 голос
/ 25 марта 2011

Как насчет чего-то вроде

np.unique1d(np.floor(1e7*x)/1e7)

, где x - ваш исходный массив.

...