Самый быстрый способ обнулить низкие значения в массиве?

Итак, скажем, у меня есть 100 000 массивов с плавающей запятой по 100 элементов в каждом. Мне нужно наибольшее количество значений X, НО только если они больше Y. Любой элемент, не соответствующий этому, должен быть установлен в 0. Каков самый быстрый способ сделать это в Python? Порядок должен поддерживаться. Большинство элементов уже установлено в 0.

примеры переменных:

array = [.06, .25, 0, .15, .5, 0, 0, 0.04, 0, 0]
highCountX = 3
lowValY = .1

ожидаемый результат:

array = [0, .25, 0, .15, .5, 0, 0, 0, 0, 0]

person David    schedule 26.10.2009    source источник
comment
highCountX - это максимальное количество ненулевых элементов, которые я хочу иметь в массиве.   -  person David    schedule 26.10.2009
comment
Если бы это было 2, ожидаемый результат был бы: [0, 0, 0, .15, .5, 0, 0, 0, 0, 0] - highCountX ограничивает количество ненулевых элементов в результате.   -  person Abgan    schedule 26.10.2009
comment
Как выбрать, какой оставить, а какой выбросить, если количество значений превышает highCountX?   -  person James Anderson    schedule 26.10.2009
comment
вы сохраняете самые высокие значения... если есть повторяющиеся значения, не имеет значения, какое из них используется   -  person David    schedule 26.10.2009
comment
@David: Вам следует рассмотреть возможность проверки одного из ответов, чтобы сообщить читателям, что он действительно решил вашу проблему!   -  person Eric O Lebigot    schedule 01.03.2010


Ответы (9)


Это типичная работа для NumPy, которая выполняется очень быстро для этих виды операций:

array_np = numpy.asarray(array)
low_values_flags = array_np < lowValY  # Where values are low
array_np[low_values_flags] = 0  # All low values set to 0

Теперь, если вам нужны только самые большие элементы highCountX, вы можете даже «забыть» маленькие элементы (вместо того, чтобы устанавливать их в 0 и сортировать) и сортировать только список больших элементов:

array_np = numpy.asarray(array)
print numpy.sort(array_np[array_np >= lowValY])[-highCountX:]

Конечно, сортировка всего массива, если вам нужны только несколько элементов, может быть неоптимальной. В зависимости от ваших потребностей вы можете рассмотреть стандартный модуль heapq.

person Eric O Lebigot    schedule 26.10.2009
comment
Хорошо... использование правильных библиотек может завести вас очень далеко :-) - person Abgan; 26.10.2009
comment
Я продолжаю сталкиваться с этим numPy, думаю, мне придется его проверить :) Спасибо за помощь (всем). - person David; 26.10.2009
comment
@David NumPy действительно удовлетворяет потребность. Я бы посоветовал вам начать с учебника, на который я ссылаюсь: это, вероятно, самый быстрый способ освоиться с NumPy и изучить его наиболее важные концепции. - person Eric O Lebigot; 26.10.2009
comment
Что будет быстрее: array_np[low_values_indices] = 0 или array_np *= low_values_indices? - person Radio Controlled; 24.10.2016
comment
предполагая, что вы импортируете numpy как np... тогда вы также можете просто использовать index = np.where(array ‹ lowValY); массив[индекс] = 0; - person user1270710; 10.10.2017
comment
Хотя это работает, это расточительно, и поэтому его, возможно, следует избегать: в контексте вопроса нет необходимости добавлять np.where(), потому что это только добавляет еще один, ненужный уровень вычислений. На самом деле NumPy умеет выбирать элементы массива на основе массива логических значений (как в этом ответе), поэтому нет необходимости преобразовывать его в массив истинных индексов. - person Eric O Lebigot; 11.10.2017

В NumPy есть специальный класс MaskedArray, который делает именно это. Вы можете «маскировать» элементы на основе любого предварительного условия. Это лучше отражает вашу потребность, чем назначение нулей: операции numpy будут игнорировать маскированные значения, когда это необходимо (например, поиск среднего значения).

>>> from numpy import ma
>>> x = ma.array([.06, .25, 0, .15, .5, 0, 0, 0.04, 0, 0])
>>> x1 = ma.masked_inside(0, 0.1) # mask everything in 0..0.1 range
>>> x1
masked_array(data = [-- 0.25 -- 0.15 0.5 -- -- -- -- --],
         mask = [ True False True False False True True True True True],
   fill_value = 1e+20)
>>> print x.filled(0) # Fill with zeroes
[ 0 0.25 0 0.15 0.5 0 0 0 0 0 ]

В качестве дополнительного преимущества маскированные массивы хорошо поддерживаются в библиотеке визуализации matplotlib, если вам это нужно.

Документы по маскированным массивам в numpy

person Alexander Lebedev    schedule 26.10.2009

Использование numpy:

# assign zero to all elements less than or equal to `lowValY`
a[a<=lowValY] = 0 
# find n-th largest element in the array (where n=highCountX)
x = partial_sort(a, highCountX, reverse=True)[:highCountX][-1]
# 
a[a<x] = 0 #NOTE: it might leave more than highCountX non-zero elements
           # . if there are duplicates

Где partial_sort может быть:

def partial_sort(a, n, reverse=False):
    #NOTE: in general it should return full list but in your case this will do
    return sorted(a, reverse=reverse)[:n] 

Выражение a[a<value] = 0 можно записать без numpy следующим образом:

for i, x in enumerate(a):
    if x < value:
       a[i] = 0
person jfs    schedule 26.10.2009

Самый простой способ:

topX = sorted([x for x in array if x > lowValY], reverse=True)[highCountX-1]
print [x if x >= topX else 0 for x in array]

По частям это выбирает все элементы больше lowValY:

[x for x in array if x > lowValY]

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

sorted(..., reverse=True)

Затем индекс списка принимает пороговое значение для верхних highCountX элементов:

sorted(...)[highCountX-1]

Наконец, исходный массив заполняется с использованием другого понимания списка:

[x if x >= topX else 0 for x in array]

Существует граничное условие, при котором есть два или более одинаковых элемента, которые (в вашем примере) являются третьими по величине элементами. Результирующий массив будет содержать этот элемент более одного раза.

Есть и другие граничные условия, например, если len(array) < highCountX. Обработка таких условий остается за разработчиком.

person Greg Hewgill    schedule 26.10.2009
comment
Вы можете использовать x вместо x в массиве, если x > lowValY вместо [x for x в массиве, если x > lowValY], чтобы просто перечислить исходный массив без его копирования (если исходные данные достаточно велики, это может быть полезно) . - person Abgan; 26.10.2009
comment
Это правда. Однако sorted(), вероятно, в любом случае понадобится весь список. - person Greg Hewgill; 26.10.2009
comment
Хех, в 3 раза быстрее, чем мой нуб-код, но мне нужны равные элементы, чтобы поддерживать лимит highCountX. Массивы должны иметь от 20 до 200 элементов... на самом деле они являются сегментами большего массива, который я обрабатываю кусками. Спасибо за помощь. - person David; 26.10.2009
comment
Я не вижу, как вы zeroing элементы в исходном массиве. - person jfs; 26.10.2009
comment
Если highCountX > len([x for x in array if x > lowValY]), вы получите IndexError. - person jfs; 26.10.2009
comment
Это не сработает (IndexError), если количество элементов больше, чем lowValY, меньше, чем highCountX. - person ThisIsMeMoony; 26.10.2009
comment
Да, есть и другие граничные условия. Обработка ошибок остается за разработчиком, я представил схему возможного решения. - person Greg Hewgill; 26.10.2009
comment
+1. Элегантно решен. NB: последнее понимание списка работает только с Python 2.5+ из-за троичной операции. - person e-satis; 26.10.2009

Настроить элементы ниже некоторого порога до нуля легко:

array = [ x if x > threshold else 0.0 for x in array ]

(плюс случайный abs(), если необходимо.)

Однако требование N самых высоких чисел немного расплывчато. Что, если есть, например. N+1 равных чисел выше порога? Какой обрезать?

Вы можете сначала отсортировать массив, а затем установить порог на значение N-го элемента:

threshold = sorted(array, reverse=True)[N]
array = [ x if x >= threshold else 0.0 for x in array ]

Примечание: это решение оптимизировано для удобочитаемости, а не производительности.

person digitalarbeiter    schedule 26.10.2009
comment
в этом случае не имеет значения, какой из них усекается... важнее то, что следует highCountX - person David; 26.10.2009

Вы можете использовать карту и лямбду, это должно быть достаточно быстро.

new_array = map(lambda x: x if x>y else 0, array)
person nnrcschmdt    schedule 26.10.2009

Используйте кучу.

Это работает во времени O(n*lg(HighCountX)).

import heapq

heap = []
array =  [.06, .25, 0, .15, .5, 0, 0, 0.04, 0, 0]
highCountX = 3
lowValY = .1

for i in range(1,highCountX):
    heappush(heap, lowValY)
    heappop(heap)

for i in range( 0, len(array) - 1)
    if array[i] > heap[0]:
        heappush(heap, array[i])

min = heap[0]

array = [x if x >= min else 0 for x in array]

deletemin работает в куче O(lg(k)) и вставке O(lg(k)) или O(1) в зависимости от того, какой тип кучи вы используете.

person Egon    schedule 26.10.2009

Как говорит Эгон, использование кучи - хорошая идея. Но вы можете использовать функцию heapq.nlargest, чтобы сократить некоторые усилия:

import heapq 

array =  [.06, .25, 0, .15, .5, 0, 0, 0.04, 0, 0]
highCountX = 3
lowValY = .1

threshold = max(heapq.nlargest(highCountX, array)[-1], lowValY)
array = [x if x >= threshold else 0 for x in array]
person Matt Anderson    schedule 27.10.2009
comment
Мне нравится это самодельное решение, в котором используются только стандартные модули. Однако его следует обновить, чтобы он действительно возвращал самые большие элементы highCountX (если многие элементы в массиве имеют значение threshold, в конечном массиве слишком много ненулевых элементов). - person Eric O Lebigot; 01.03.2010

person    schedule
comment
Устарело, начиная с версии 0.17.1, см. docs.scipy.org/doc/scipy-0.17.1/reference/generated/ - person weiji14; 02.04.2018