2009-12-26 6 views
23

Я хочу выбрать только определенные строки из массива NumPy на основе значения во втором столбце. Например, этот тестовый массив имеет целые числа от 1 до 10 во втором столбце.Выбор строк из NumPy ndarray

>>> test = numpy.array([numpy.arange(100), numpy.random.randint(1, 11, 100)]).transpose() 
>>> test[:10, :] 
array([[ 0, 6], 
     [ 1, 7], 
     [ 2, 10], 
     [ 3, 4], 
     [ 4, 1], 
     [ 5, 10], 
     [ 6, 6], 
     [ 7, 4], 
     [ 8, 6], 
     [ 9, 7]]) 

Если бы я хотел только те строки, где второе значение равно 4, то легко:

>>> test[test[:, 1] == 4] 
array([[ 3, 4], 
     [ 7, 4], 
     [16, 4], 
     ... 
     [81, 4], 
     [83, 4], 
     [88, 4]]) 

Но как я могу добиться того же результата, когда есть больше чем один хотел значение?

Желаемый список может быть произвольной длины. Например, я могу хотеть все строки, где второй столбец либо 2, 4 или 6:

>>> wanted = [2, 4, 6] 

Единственный способ, который я придумал, чтобы использовать список понимание, а затем преобразовать это обратно в массив и, кажется, слишком запутанным, хотя он работает:

>>> test[numpy.array([test[x, 1] in wanted for x in range(len(test))])] 
array([[ 0, 6], 
     [ 3, 4], 
     [ 6, 6], 
     ... 
     [90, 2], 
     [91, 6], 
     [92, 2]]) 

есть ли лучший способ сделать это в самом NumPy, что мне не хватает?

ответ

16
test[numpy.logical_or.reduce([test[:,1] == x for x in wanted])] 

Результат должен быть быстрее, чем исходная версия, поскольку NumPy выполняет внутренние циклы вместо Python.

+0

Это решение проходит через массив len (разыскивается) раз. Как правило, быстрее пройти через массив за один проход. – EOL

+0

Спасибо Amnon. Это решение, которое я решил принять.Я думаю, что это понятно для понимания и примерно в 20 раз быстрее моего первоначального решения. –

+0

Что делать, если я хочу выбрать определенные строки с условными значениями в столбце? Например, я хочу выбрать все строки, чей столбец 1 имеет значения в диапазоне от 5 до 10. Например. 'test [test [:, 1]> 4 & test [:, 1] <8]'. – Shyamkkhadka

28

Следующее решение должно быть быстрее, чем решение Амнона как wanted становится больше:

wanted_set = set(wanted) # Much faster look up than with lists, for larger lists 

@numpy.vectorize 
def selected(elmt): return elmt in wanted_set # Or: selected = numpy.vectorize(wanted_set.__contains__) 

print test[selected(test[:, 1])] 

На самом деле, он имеет преимущество поиска через test массив только раз (вместо len(wanted) раза). Он также использует встроенный быстрый элемент Python для поиска в наборах, которые для этого намного быстрее, чем списки. Это также быстро, потому что он использует быстрые циклы Numpy. Вы также получаете оптимизацию оператора in: как только элемент wanted соответствует, остальные элементы не должны быть протестированы (в отличие от «логического» или «подхода Amnon», были проверены все элементы в wanted независимо от того, что) ,

В качестве альтернативы, вы можете использовать следующий Однострочник, который также идет через массив только один раз:

test[numpy.apply_along_axis(lambda x: x[1] in wanted, 1, test)] 

Это гораздо медленнее, хотя, как это извлекает элемент во втором столбце в каждом итерации (вместо того, чтобы делать это за один проход, как в первом решении этого ответа).

+0

Эти решения называют python для каждого элемента, а не для сравнения numpy. Согласно моим тестам, ваше первое решение быстрее, чем мое для len (требуется) = 50, но медленнее для len (требуется) = 5. – Amnon

+0

EOL, большое спасибо за ваше время и усилия. Ваши объяснения были ясны. Я решил использовать решение Amnon, потому что для моего обычного сценария (len (test) около 1000 и len (требуется) около 3-5), это было быстрее, чем ваше первое решение. Разница в скорости не огромна, но я также нашел ее более ясной. Но было хорошо напомнить о векторе numpy, и я уверен, что скоро найду его для использования. –

+0

@Amnon: Хорошая точка и интересные результаты. Благодаря! – EOL

0

Это в два раза быстрее, чем вариант Амнона для Len (тест) = 1000:

wanted = (2,4,6) 
wanted2 = numpy.expand_dims(wanted, 1) 
print test[numpy.any(test[:, 1] == wanted2, 0), :] 
+1

@ahatchkins: Некоторые опечатки в вашей версии. Что вы предлагаете, так это: # конвертировать разыскиваемый список в один массив столбцов хотел = numpy.array (хотел) .reshape ((len (хотел), 1)) # # print test [numpy.any (test [:, 1] == wanted, 0)] В моих тестах это примерно в 2 раза быстрее, чем решение Амнона. –

+0

Да, была опечатка: s/wanted2/wanted /. Исправлено: –

+0

Хм, да. Ты прав. На самом деле два опечатка. И только в 2 раза быстрее.) –

9

numpy.in1d является то, что вы ищете:

print test[numpy.in1d(test[:,1], wanted)] 

должна легко быть самым быстрым решением, если хотел большой; плюс, это самый читаемый, скажем.