Найти диапазон индекса начала / остановки для значений в NumPy Array Greater Th N - PullRequest
4 голосов
/ 07 марта 2020

Допустим, у меня есть массив NumPy:

x = np.array([2, 3, 4, 0, 0, 1, 1, 4, 6, 5, 8, 9, 9, 4, 2, 0, 3])

Для всех значений в x >= 2 мне нужно найти индексы запуска / остановки, где последовательные значения x >=2 (т.е. прогон одного значения, большего или равного 2, не учитывается). Затем я повторяю это для x >= 3, x >=4, ..., x >= x.max(). Выходные данные должны быть NumPy массивом тремя столбцами (первый столбец - минимальное значение, второй столбец - включающий начальный индекс, а третий столбец - конечный индекс) и будет выглядеть следующим образом:

[[2,  0,  2],
 [2,  7, 14],
 [3,  1,  2],
 [3,  7, 13],
 [4,  7, 13],
 [5,  8, 12],
 [6, 10, 12],
 [8, 10, 12],
 [9, 11, 12]
]

Наивно, я мог просматривать каждое уникальное значение и затем искать индексы запуска / остановки. Однако для этого необходимо выполнить несколько проходов через x. Какой лучший NumPy векторизованный способ выполнения sh этой задачи? Есть ли решение, которое не требует многократного прохождения данных?

Обновление

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

[[2,  0,  2],
 [2,  7, 14],
 [2, 16, 16],  # New line needed
 [3,  1,  2],
 [3,  7, 13],
 [3, 16, 16],  # New line needed
 [4,  2,  2],  # New line needed
 [4,  7, 13],
 [5,  8, 12],
 [6,  8,  8],  # New line needed
 [6, 10, 12],
 [8, 10, 12],
 [9, 11, 12]
]

Ответы [ 2 ]

3 голосов
/ 07 марта 2020

Вот еще одно решение (которое, я считаю, можно улучшить):

import numpy as np
from numpy.lib.stride_tricks import as_strided

x = np.array([2, 3, 4, 0, 0, 1, 1, 4, 6, 5, 8, 9, 9, 4, 2, 0, 3])

# array of unique values of x bigger than 1
a = np.unique(x[x>=2])

step = len(a)  # if you encounter memory problems, try a smaller step
result = []
for i in range(0, len(a), step):
    ai = a[i:i + step]
    c = np.argwhere(x >= ai[:, None])
    c[:,0] = ai[c[:,0]]
    c =  np.pad(c, ((1,1), (0,0)), 'symmetric')

    d = np.where(np.diff(c[:,1]) !=1)[0]

    e = as_strided(d, shape=(len(d)-1, 2), strides=d.strides*2).copy()
    # e = e[(np.diff(e, axis=1) > 1).flatten()]
    e[:,0] = e[:,0] + 1 

    result.append(np.hstack([c[:,0][e[:,0, None]], c[:,1][e]]))

result = np.concatenate(result)

# array([[ 2,  0,  2],
#        [ 2,  7, 14],
#        [ 2, 16, 16],
#        [ 3,  1,  2],
#        [ 3,  7, 13],
#        [ 3, 16, 16],
#        [ 4,  2,  2],
#        [ 4,  7, 13],
#        [ 5,  8, 12],
#        [ 6,  8,  8],
#        [ 6, 10, 12],
#        [ 8, 10, 12],
#        [ 9, 11, 12]])

Извините, что не комментируем каждый шаг - если позже я найду время, я его исправлю.

0 голосов
/ 07 марта 2020

Это довольно интересная проблема, действительно. Я попытался решить его, разделив его на три части.

Группировка:

import numpy as np
import pandas as pd
x = np.array([2, 3, 4, 0, 0, 1, 1, 4, 6, 5, 8, 9, 9, 4, 2, 0, 3])
groups = pd.DataFrame(x).groupby([0]).indices

Таким образом, группы - это словарь {0: [3, 4, 15], 1: [5, 6], 2: [0, 14], 3: [1, 16], 4: [2, 7, 13], 5: [9], 6: [8], 8: [10], 9: [11, 12]}, а его значения - numpy массивы dtype=int64.

Маскировка:

В этой части я перебираю несколько массивов масок x>=i для каждого уникального значения i в порядке убывания:

mask_array = np.zeros(x.size).astype(int)
for group in list(groups)[::-1]:
    mask = mask_array[groups[group]] = 1
    # print(group, ':', mask_array)
    # output = find_slices(mask)

И эти маски выглядят так:

9 : [0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0]
8 : [0 0 0 0 0 0 0 0 0 0 1 1 1 0 0 0 0]
6 : [0 0 0 0 0 0 0 0 1 0 1 1 1 0 0 0 0]
5 : [0 0 0 0 0 0 0 0 1 1 1 1 1 0 0 0 0]
4 : [0 0 1 0 0 0 0 1 1 1 1 1 1 1 0 0 0]
3 : [0 1 1 0 0 0 0 1 1 1 1 1 1 1 0 0 1]
2 : [1 1 1 0 0 0 0 1 1 1 1 1 1 1 1 0 1]
1 : [1 1 1 0 0 1 1 1 1 1 1 1 1 1 1 0 1]
0 : [1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1]

Извлечение срезов из масок :

Я предполагаю построить некоторую функцию под названием find_slices, которая извлекает позиции срезов из маскировать массивы (если вы раскомментируете это). Вот что я сделал:

def find_slices(m):
    m1 = np.r_[0, m]
    m2 = np.r_[m, 0]
    starts, = np.where(~m1 & m2)
    ends, = np.where(m1 & ~m2)
    return np.c_[starts, ends - 1]

Например, позиции среза массива [0 1 1 0 0 0 0 1 1 1 1 1 1 1 0 0 1] будут [[1, 2], [7, 13], [16, 16]]. Обратите внимание, что это не стандартный способ возврата срезов, конечная позиция обычно увеличивается на 1.

Окончательный сценарий

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

import numpy as np
import pandas as pd
x = np.array([2, 3, 4, 0, 0, 1, 1, 4, 6, 5, 8, 9, 9, 4, 2, 0, 3])
groups = pd.DataFrame(x).groupby([0]).indices
mask_array = np.zeros(x.size).astype(bool)

m = []
for group in list(groups)[::-1]:
    mask_array[groups[group]] = True
    s = find_slices(mask_array)
    group_output = np.c_[np.repeat(group, s.shape[0]), s] #insert first column
    m.append(group_output) 
output = np.concatenate(m[::-1])
output = output[output[:,1]!= output[:,2]] #elimate slices with unit length

Выход:

 [[ 0  0 16]
 [ 1  0  2]
 [ 1  5 14]
 [ 2  0  2]
 [ 2  7 14]
 [ 3  1  2]
 [ 3  7 13]
 [ 4  7 13]
 [ 5  8 12]
 [ 6 10 12]
 [ 8 10 12]
 [ 9 11 12]]
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...