2016-12-25 3 views
12

рассмотрит массив со aКаков наиболее эффективный способ найти позицию первого значения np.nan?

a = np.array([3, 3, np.nan, 3, 3, np.nan]) 

я мог бы сделать

np.isnan(a).argmax() 

Но это требует найти все np.nan просто найти первый.
Есть ли более эффективный способ?


Я пытался выяснить, смогу ли я передать параметр в np.argpartition таким образом, что np.nan прибудет отсортирован первым в отличии от последнего.


EDIT относительно [dup].
Существует несколько причин, по которым этот вопрос отличается.

  1. Этот вопрос и ответы касаются равенства ценностей. Это относится к isnan.
  2. Эти ответы все страдают от той же проблемы, с которой я сталкиваюсь. Заметьте, я дал совершенно правильный ответ, но подчеркнул, что это неэффективность. Я ищу, чтобы исправить неэффективность.

EDIT относительно второго [DUP].

Все еще обращаясь к вопросу равенства и вопросов/ответов, старые и, возможно, устаревшие.

+2

Возможный дубликат [Есть функция Numpy, чтобы вернуть первый индекс чего-либо в массиве?] (Http://stackoverflow.com/questions/432112/is-there-a-numpy-function-to-return -the-first-index-of-something-in-a-array) – Delgan

+1

Возможный дубликат [Numpy: найти первый индекс значения быстро] (http://stackoverflow.com/questions/7632963/numpy-find-first- index-of-value-fast) – fuglede

+1

Второй дублирует альтернативы «короткого замыкания». Часть 'isnan' эта проблема не делает ее уникальной. Но дурак датирован. – hpaulj

ответ

7

Я выдвину

a.argmax() 

С @fuglede's тестового массива:

In [1]: a = np.array([np.nan if i % 10000 == 9999 else 3 for i in range(100000)]) 
In [2]: np.isnan(a).argmax() 
Out[2]: 9999 
In [3]: np.argmax(a) 
Out[3]: 9999 
In [4]: a.argmax() 
Out[4]: 9999 

In [5]: timeit a.argmax() 
The slowest run took 29.94 .... 
10000 loops, best of 3: 20.3 µs per loop 

In [6]: timeit np.isnan(a).argmax() 
The slowest run took 7.82 ... 
1000 loops, best of 3: 462 µs per loop 

Я не numba установлена, поэтому можно сравнить это. Но мое ускорение относительно short больше, чем @fuglede's 6x.

Я тестирую в Py3, который принимает <np.nan, в то время как Py2 вызывает предупреждение о времени выполнения. Но поиск кода предполагает, что это не зависит от этого сравнения.

/numpy/core/src/multiarray/calculation.cPyArray_ArgMax играет с осями (перемещение на один из интересующих конца), и делегатов к действию arg_func = PyArray_DESCR(ap)->f->argmax, функция, которая зависит от DTYPE.

В numpy/core/src/multiarray/arraytypes.c.src выглядит как BOOL_argmax короткое замыкание, возвращаясь, как только он встречает True.

for (; i < n; i++) { 
    if (ip[i]) { 
     *max_ind = i; 
     return 0; 
    } 
} 

И @[email protected]_argmax также короткие замыкания на максимальной nan. np.nan является «максимальным» в argmin.

#if @[email protected] 
    if (@[email protected](mp)) { 
     /* nan encountered; it's maximal */ 
     return 0; 
    } 
#endif 

Комментарии от опытных c кодеров приветствуются, но мне кажется, что по крайней мере np.nan, простой argmax будет так быстро вам, что мы можем получить.

Играя с 9999 в генерации a, показывает, что время зависит от этого значения, соответствующего короткому замыканию.

+0

Теперь, когда я могу думать прямо. Это замечательно! И все это сводится к максимальному максимальному количеству np.nan. – piRSquared

+0

Интересно! В моей тестовой настройке выполняется простой «argmax», а также поиск JIT. Я думаю, это имеет смысл, поскольку, видимо, они делают то же самое! Я добавлю тайминги к другому ответу. – fuglede

3

При поиске первого совпадения в различных сценариях мы можем выполнить итерацию и искать первое совпадение и выйти из первого совпадения, а не переходить/обрабатывать весь массив. Таким образом, мы имели бы подход с использованием Python's next function, как так -

next((i for i, val in enumerate(a) if np.isnan(val))) 

Sample пробегов -

In [192]: a = np.array([3, 3, np.nan, 3, 3, np.nan]) 

In [193]: next((i for i, val in enumerate(a) if np.isnan(val))) 
Out[193]: 2 

In [194]: a[2] = 10 

In [195]: next((i for i, val in enumerate(a) if np.isnan(val))) 
Out[195]: 5 
+0

почему downvote! Это было умно и полезно. – piRSquared

+0

Я собирался предложить цикл с перерывом, но это использование генератора плюс один «следующий» эквивалентен. Интересно о скорости. – hpaulj

+3

Для очень большого массива и наном рядом с началом, итерация до первого имеет шанс быть быстрее. Но в целом скомпилированные функции numpy будут быстрее, даже если они пересекают весь массив. Каков необходимый тестовый костюм. – hpaulj

6

Вот это вещий подход с использованием itertools.takewhile():

from itertools import takewhile 
sum(1 for _ in takewhile(np.isfinite, a)) 

Benchmark с generator_expression_within_ next подход :

In [118]: a = np.repeat(a, 10000) 

In [120]: %timeit next(i for i, j in enumerate(a) if np.isnan(j)) 
100 loops, best of 3: 12.4 ms per loop 

In [121]: %timeit sum(1 for _ in takewhile(np.isfinite, a)) 
100 loops, best of 3: 11.5 ms per loop 

Но до сих пор (на сегодняшний день) медленнее, чем Numpy подход:

In [119]: %timeit np.isnan(a).argmax() 
100000 loops, best of 3: 16.8 µs per loop 

1. Проблема с этим подходом является использование enumerate функции. Которая возвращает объект enumerate из массива numpy first (который является объектом итератора) и вызывает функцию генератора, а атрибут итератора занимает время.

+1

pls добавляет 'np.isnan (a) .argmax()' к эталонам. – piRSquared

+3

Этот массив слишком мал для этих подходов, чтобы бить функции numpy. Попробуй это в большом массиве, может быть? – ayhan

+2

@ayhan На самом деле это был повторный массив. Я просто забыл добавить соответствующую команду. Вот новый. – Kasramvd

9

Возможно, стоит посмотреть на numba.jit; без него, Векторизованная версии, скорее всего, бить прямо вперед поиск чисто-Python в большинстве сценариев, но после компиляции коды, обычный поиск будет взять на себя инициативу, по крайней мере, в моем тестировании:

In [63]: a = np.array([np.nan if i % 10000 == 9999 else 3 for i in range(100000)]) 

In [70]: %paste 
import numba 

def naive(a): 
     for i in range(len(a)): 
       if np.isnan(a[i]): 
         return i 

def short(a): 
     return np.isnan(a).argmax() 

@numba.jit 
def naive_jit(a): 
     for i in range(len(a)): 
       if np.isnan(a[i]): 
         return i 

@numba.jit 
def short_jit(a): 
     return np.isnan(a).argmax() 
## -- End pasted text -- 

In [71]: %timeit naive(a) 
100 loops, best of 3: 7.22 ms per loop 

In [72]: %timeit short(a) 
The slowest run took 4.59 times longer than the fastest. This could mean that an intermediate result is being cached. 
10000 loops, best of 3: 37.7 µs per loop 

In [73]: %timeit naive_jit(a) 
The slowest run took 6821.16 times longer than the fastest. This could mean that an intermediate result is being cached. 
100000 loops, best of 3: 6.79 µs per loop 

In [74]: %timeit short_jit(a) 
The slowest run took 395.51 times longer than the fastest. This could mean that an intermediate result is being cached. 
10000 loops, best of 3: 144 µs per loop 

Edit: Как отметил @hpaulj в своем ответе, numpy фактически поставляется с оптимизированным короткозамкнутым поиском, производительность сравнимо с поиском JITted выше:

In [26]: %paste 
def plain(a): 
     return a.argmax() 

@numba.jit 
def plain_jit(a): 
     return a.argmax() 
## -- End pasted text -- 

In [35]: %timeit naive(a) 
100 loops, best of 3: 7.13 ms per loop 

In [36]: %timeit plain(a) 
The slowest run took 4.37 times longer than the fastest. This could mean that an intermediate result is being cached. 
100000 loops, best of 3: 7.04 µs per loop 

In [37]: %timeit naive_jit(a) 
100000 loops, best of 3: 6.91 µs per loop 

In [38]: %timeit plain_jit(a) 
10000 loops, best of 3: 125 µs per loop 
Смежные вопросы