Чи є функція NumPy для повернення першого індексу чогось у масиві?


Відповіді:


522

Так, ось відповідь, задана масивом NumPy array, та значенням itemдля пошуку:

itemindex = numpy.where(array==item)

Результат - кортеж спочатку всіх індексів рядків, а потім усіх індексів стовпців.

Наприклад, якщо масив має два виміри і він містив ваш елемент у двох місцях тоді

array[itemindex[0][0]][itemindex[1][0]]

було б дорівнює вашому товару, і так би

array[itemindex[0][1]][itemindex[1][1]]

numpy.where


1
Якщо ви шукаєте перший рядок, у якому елемент існує в першому стовпці, це працює (хоча він rows, columns = np.where(array==item); first_idx = sorted([r for r, c in zip(rows, columns) if c == 0])[0]
введе

27
Що робити, якщо ви хочете, щоб він зупинив пошук після пошуку першого значення? Я не думаю, де () можна порівняти пошук ()
Майкл Клеркс

2
Ах! Якщо ви зацікавлені в роботі, перевірте відповідь на це питання: stackoverflow.com/questions/7632963 / ...
Michael Clerx

11
np.argwhereбуде трохи корисніше тут:itemindex = np.argwhere(array==item)[0]; array[tuple(itemindex)]
Ерік

3
Варто зазначити, що ця відповідь передбачає, що масив є 2D. whereпрацює на будь-якому масиві і повертає кортеж довжиною 3 при використанні на 3D-масиві тощо
П. Каміллері

69

Якщо вам потрібен індекс першого появи лише одного значення , ви можете використовувати nonzero(або where, що в цьому випадку становить те саме):

>>> t = array([1, 1, 1, 2, 2, 3, 8, 3, 8, 8])
>>> nonzero(t == 8)
(array([6, 8, 9]),)
>>> nonzero(t == 8)[0][0]
6

Якщо вам потрібен перший індекс кожного з багатьох значень , ви, очевидно, могли б зробити те ж, що було описано вище, але є хитрість, яка може бути швидшою. Далі знаходяться показники першого елемента кожної послідовності :

>>> nonzero(r_[1, diff(t)[:-1]])
(array([0, 3, 5, 6, 7, 8]),)

Зауважте, що він знаходить початок обох підрядів 3s та обох підрядів 8s:

[ 1 , 1, 1, 2 , 2, 3 , 8 , 3 , 8 , 8]

Тож це дещо інакше, ніж пошук першого виникнення кожного значення. У вашій програмі ви можете працювати з відсортованою версією, tщоб отримати те, що вам потрібно:

>>> st = sorted(t)
>>> nonzero(r_[1, diff(st)[:-1]])
(array([0, 3, 5, 7]),)

4
Чи можете ви поясніть, що r_таке?
Джефф

1
@Geoff, r_конкатенати; або, точніше, він переводить об'єкти зрізів для об'єднання вздовж кожної осі. Я міг би використати hstackнатомість; які, можливо, були менш заплутаними. Дивіться документацію для отримання додаткової інформації про r_. Також є c_.
Вебйорн Льоса

+1, приємний! (проти NP. десь) ваше рішення набагато простіше (і, мабуть, швидше) у тому випадку, коли нам потрібне лише перше виникнення заданого значення в 1D масиві
дог

3
Останній випадок (знаходження першого індексу всіх значень) задаєтьсяvals, locs = np.unique(t, return_index=True)
askewchan

50

Ви також можете конвертувати масив NumPy, щоб перерахувати його у повітрі та отримати його індекс. Наприклад,

l = [1,2,3,4,5] # Python list
a = numpy.array(l) # NumPy array
i = a.tolist().index(2) # i will return index of 2
print i

Він надрукує 1.


Можливо, бібліотека змінилася, оскільки це було написано вперше. Але це було перше рішення, яке працювало на мене.
amracel

1
Я добре використав це для пошуку кількох значень у списку, використовуючи розуміння списку:[find_list.index(index_list[i]) for i in range(len(index_list))]
Метт Венхем,

1
@MattWenham Якщо він досить великий, ви можете перетворити свій find_listмасив NumPy object(або щось більш конкретне, що підходить) і просто зробити find_arr[index_list].
Нарфанар

Зовсім поза темою, але це перший раз, коли я бачу фразу "в повітрі" - те, що я бачив найбільше, замість цього, можливо, "на льоту".
flow2k

18

Просто додати дуже ефектного та зручного Альтернатива на основі np.ndenumerateпошуку першого індексу:

from numba import njit
import numpy as np

@njit
def index(array, item):
    for idx, val in np.ndenumerate(array):
        if val == item:
            return idx
    # If no item was found return None, other return types might be a problem due to
    # numbas type inference.

Це досить швидко і, природно, справляється з багатовимірними масивами :

>>> arr1 = np.ones((100, 100, 100))
>>> arr1[2, 2, 2] = 2

>>> index(arr1, 2)
(2, 2, 2)

>>> arr2 = np.ones(20)
>>> arr2[5] = 2

>>> index(arr2, 2)
(5,)

Це може бути набагато швидше (оскільки це коротке замикання на операцію), ніж будь-який підхід, що використовує np.whereабо np.nonzero.


Однак np.argwhereможна також вишукано поводитись з багатовимірними масивами (вам потрібно буде вручну підкинути їх до кортежу, і це не коротке замикання), але це не вдасться, якщо не буде знайдено відповідності:

>>> tuple(np.argwhere(arr1 == 2)[0])
(2, 2, 2)
>>> tuple(np.argwhere(arr2 == 2)[0])
(5,)

2
@njitце скорочення, jit(nopython=True)тобто функція буде повністю скомпільована під час першого запуску, щоб виклики інтерпретатора Python були повністю видалені.
bartolo-otrit

14

Якщо ви збираєтесь використовувати це як індекс в чомусь іншому, ви можете використовувати булові індекси, якщо масиви транслюються; вам не потрібні чіткі індекси. Абсолютний найпростіший спосіб зробити це - просто індексувати на основі значення істини.

other_array[first_array == item]

Булева операція працює:

a = numpy.arange(100)
other_array[first_array > 50]

Ненульовий метод також займає булеви:

index = numpy.nonzero(first_array == item)[0][0]

Дві нулі призначені для кордону індексів (якщо вважати, що first_array є 1D), а потім перший елемент у масиві індексів.


10

l.index(x)повертає найменший i такий, що i - індекс першого появи x у списку.

Можна сміливо припускати, що index()функція в Python реалізована так, що вона зупиняється після пошуку першого збігу, і це призводить до оптимальної середньої продуктивності.

Щоб знайти елемент, що зупиняється після першого збігу в масиві NumPy, використовуйте ітератор ( ndenumerate ).

In [67]: l=range(100)

In [68]: l.index(2)
Out[68]: 2

NumPy масив:

In [69]: a = np.arange(100)

In [70]: next((idx for idx, val in np.ndenumerate(a) if val==2))
Out[70]: (2L,)

Зверніть увагу , що обидва методи index()і nextповертає помилку , якщо елемент не знайдений. З next, можна використовувати другий аргумент для повернення спеціального значення у випадку, якщо елемент не знайдений, наприклад

In [77]: next((idx for idx, val in np.ndenumerate(a) if val==400),None)

У NumPy ( argmax, whereі nonzero) є інші функції , які можна використовувати для пошуку елемента в масиві, але всі вони мають недолік проходження всього масиву в пошуках усіх подій, таким чином, не оптимізовані для пошуку першого елемента. Зверніть увагу також на це whereі nonzeroповерніть масиви, тому вам потрібно вибрати перший елемент, щоб отримати індекс.

In [71]: np.argmax(a==2)
Out[71]: 2

In [72]: np.where(a==2)
Out[72]: (array([2], dtype=int64),)

In [73]: np.nonzero(a==2)
Out[73]: (array([2], dtype=int64),)

Порівняння часу

Просто перевірте, що для великих масивів рішення за допомогою ітератора відбувається швидше, коли шуканий елемент знаходиться на початку масиву (використовуючи %timeitв оболонці IPython):

In [285]: a = np.arange(100000)

In [286]: %timeit next((idx for idx, val in np.ndenumerate(a) if val==0))
100000 loops, best of 3: 17.6 µs per loop

In [287]: %timeit np.argmax(a==0)
1000 loops, best of 3: 254 µs per loop

In [288]: %timeit np.where(a==0)[0][0]
1000 loops, best of 3: 314 µs per loop

Це відкрите питання NumPy GitHub .

Дивіться також: Numpy : швидко знайдіть перший індекс вартості


1
Я думаю, ви також повинні включити термін для найгіршого випадку (останній елемент), щоб читачі могли знати, що з ними відбувається в гіршому випадку, коли вони використовують ваш підхід.
MSeifert

@MSeifert Я не можу отримати розумний термін для найгіршого ітераторського рішення - я збираюся видалити цю відповідь, поки не
з’ясую

1
не %timeit next((idx for idx, val in np.ndenumerate(a) if val==99999))працює? Якщо вам цікаво, чому це в 1000 разів повільніше - це тому, що петлі python над масивними масивами, як відомо, повільно.
MSeifert

@MSeifert немає я не знаю, але я також здивований тим фактом , що argmaxі whereнабагато швидше , в цьому випадку (пошук елемент в кінець масиву)
user2314737

Вони повинні бути такими ж швидкими, як ніби елемент знаходиться на початку. Вони завжди обробляють весь масив, тому вони завжди займають однаковий час (принаймні, слід).
MSeifert

9

Для одновимірних відсортованих масивів було б набагато простіше та ефективніше O (log (n)) використовувати numpy.searchsorted, який повертає ціле число NumPy (позицію). Наприклад,

arr = np.array([1, 1, 1, 2, 3, 3, 4])
i = np.searchsorted(arr, 3)

Просто переконайтеся, що масив вже відсортований

Також перевірте, чи справді повернутий індекс i містить елемент, який шукається, оскільки головна мета searchsorted - знайти індекси, куди слід вводити елементи для підтримки порядку.

if arr[i] == 3:
    print("present")
else:
    print("not present")

2
searchsorted не nlog (n), оскільки він не сортує масив перед пошуком, він передбачає, що масив аргументів вже відсортований. ознайомтеся з документацією на numpy.searchsorted (посилання вище)
Алок Наяк

6

Щоб індексувати будь-які критерії, ви можете зробити щось на зразок наступного:

In [1]: from numpy import *
In [2]: x = arange(125).reshape((5,5,5))
In [3]: y = indices(x.shape)
In [4]: locs = y[:,x >= 120] # put whatever you want in place of x >= 120
In [5]: pts = hsplit(locs, len(locs[0]))
In [6]: for pt in pts:
   .....:         print(', '.join(str(p[0]) for p in pt))
4, 4, 0
4, 4, 1
4, 4, 2
4, 4, 3
4, 4, 4

Ось швидка функція робити те, що робить list.index (), за винятком випадків, коли він не створює виняток, якщо його не знайдено. Остерігайтеся - це, мабуть, дуже повільно на великих масивах. Ви можете, ймовірно, мавпи виправити це на масиви, якщо краще скористатися ним як методом.

def ndindex(ndarray, item):
    if len(ndarray.shape) == 1:
        try:
            return [ndarray.tolist().index(item)]
        except:
            pass
    else:
        for i, subarray in enumerate(ndarray):
            try:
                return [i] + ndindex(subarray, item)
            except:
                pass

In [1]: ndindex(x, 103)
Out[1]: [4, 0, 3]

5

Для 1D масивів, я рекомендував би np.flatnonzero(array == value)[0], що еквівалентно , як np.nonzero(array == value)[0][0]і np.where(array == value)[0][0]але уникає каліцтва розпакування з 1-елементним кортежем.


4

Альтернативою вибору першого елемента з np.where () є використання генераторного вираження разом із перерахуванням, таким як:

>>> import numpy as np
>>> x = np.arange(100)   # x = array([0, 1, 2, 3, ... 99])
>>> next(i for i, x_i in enumerate(x) if x_i == 2)
2

Для двовимірного масиву можна було б зробити:

>>> x = np.arange(100).reshape(10,10)   # x = array([[0, 1, 2,... 9], [10,..19],])
>>> next((i,j) for i, x_i in enumerate(x) 
...            for j, x_ij in enumerate(x_i) if x_ij == 2)
(0, 2)

Перевага такого підходу полягає в тому, що він зупиняє перевірку елементів масиву після того, як знайдено перший збіг, тоді як np.where перевіряє всі елементи на відповідність. Вираз генератора був би швидшим, якщо в масиві є рання відповідність.


Якщо у масиві може бути не збіг, цей метод також дозволяє вам зручно вказати резервне значення. Якби перший приклад повернувся Noneяк запасний варіант, це стане next((i for i, x_i in enumerate(x) if x_i == 2), None).
Ерленд Магнус

4

У NumPy існує безліч операцій, які, можливо, можуть бути складені для цього. Це поверне індекси елементів, рівних позиції:

numpy.nonzero(array - item)

Потім ви можете взяти перші елементи списків, щоб отримати один елемент.


5
Хіба це не дасть індекси всіх елементів, які не дорівнюють позиції?
Autoplectic

3

Пакет numpy_indexed (відмова, я його автор) містить векторизований еквівалент list.index для numpy.ndarray; це є:

sequence_of_arrays = [[0, 1], [1, 2], [-5, 0]]
arrays_to_query = [[-5, 0], [1, 0]]

import numpy_indexed as npi
idx = npi.indices(sequence_of_arrays, arrays_to_query, missing=-1)
print(idx)   # [2, -1]

Це рішення має векторну ефективність, узагальнює ndarrays і має різні способи боротьби з відсутніми значеннями.


-1

Примітка: це для версії python 2.7

Ви можете використовувати лямбда-функцію для вирішення проблеми, і вона працює як у масиві NumPy, так і в списку.

your_list = [11, 22, 23, 44, 55]
result = filter(lambda x:your_list[x]>30, range(len(your_list)))
#result: [3, 4]

import numpy as np
your_numpy_array = np.array([11, 22, 23, 44, 55])
result = filter(lambda x:your_numpy_array [x]>30, range(len(your_list)))
#result: [3, 4]

І можна використовувати

result[0]

щоб отримати перший показник відфільтрованих елементів.

Для python 3.6 використовуйте

list(result)

замість

result

Це призводить до роботи <filter object at 0x0000027535294D30>на Python 3 (протестовано на Python 3.6.3). Можливо, оновлення для Python 3?
Пітер Мортенсен
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.