numpy получает блоки, образующие массив в матрице

#python #arrays #numpy #matrix

#python #массивы #numpy #матрица

Вопрос:

У меня есть матрица numpy, такая как приведенная ниже. Что я хотел бы сделать, так это получить массивы, содержащие блоки, составляющие каждую строку / столбец здесь. Как это можно эффективно сделать в numpy?

Примеры

Итак, если, например, у нас есть массив [1 1 1 1 0 1 1 1 1 0] (первая строка), то мы получим [4 4] , поскольку у нас есть 2 блока по 4.

Для первого столбца мы получим, [3 1] поскольку в начале у нас есть три 1 -s, за которыми следует ноль, затем один 1 , а затем больше нулей.

Упомянутая матрица

 [[1 1 1 1 0 1 1 1 1 0]
 [1 0 0 1 0 1 1 1 1 1]
 [1 0 1 0 1 0 0 1 0 1]
 [0 1 0 0 1 0 1 0 0 0]
 [1 1 1 1 1 1 0 1 0 1]
 [0 0 1 0 0 1 1 1 0 0]
 [0 0 0 0 1 1 0 1 1 0]
 [0 0 0 0 0 0 0 0 1 1]
 [0 1 0 1 0 1 0 0 0 0]
 [0 0 1 0 0 0 1 1 1 0]]
  

ПРИМЕЧАНИЕ: строки упорядочены слева направо, а столбцы — сверху вниз.

Комментарии:

1. Какой будет строка 2? [1, 1, 5] ? И строка 3: [1, 1, 1, 1, 1] ?

2. @S3DEV да, точно

3. @S3DEV и та же операция должна быть также применена к столбцам (строки упорядочены слева направо, столбцы сверху вниз)

4. ОК. И какова форма / структура данных для вывода, учитывая, что вы ищете результаты строк и столбцов. Возможно, вывод для каждого?

5. @S3DEV Один массив, содержащий несколько массивов, каждый массив имеет форму, описанную здесь (массивы упорядочены по их появлению в матрице, что означает, что массив, описывающий первую строку, находится в нулевой позиции (первой)). То же самое относится и к столбцам.

Ответ №1:

Вот некоторая магия numpy:

 a = np.array([[1, 1, 1, 1, 0, 1, 1, 1, 1, 0],
              [1, 0, 0, 1, 0, 1, 1, 1, 1, 1],
              [1, 0, 1, 0, 1, 0, 0, 1, 0, 1],
              [0, 1, 0, 0, 1, 0, 1, 0, 0, 0],
              [1, 1, 1, 1, 1, 1, 0, 1, 0, 1],
              [0, 0, 1, 0, 0, 1, 1, 1, 0, 0],
              [0, 0, 0, 0, 1, 1, 0, 1, 1, 0],
              [0, 0, 0, 0, 0, 0, 0, 0, 1, 1],
              [0, 1, 0, 1, 0, 1, 0, 0, 0, 0],
              [0, 0, 1, 0, 0, 0, 1, 1, 1, 0]])


a_pad = np.zeros((a.shape[0] 2, a.shape[1] 2))
a_pad[1:-1, 1:-1] = a

cols = [np.diff(np.nonzero(c)[0].reshape(-1, 2), axis=1)[:, 0]
        for c in np.diff(a_pad, axis=0).T[1:-1]]
# [array([3, 1]),  array([1, 2, 1]),  array([1, 1, 2, 1]), ...

rows = [np.diff(np.nonzero(r)[0].reshape(-1, 2), axis=1)[:, 0]
        for r in np.diff(a_pad, axis=1)[1:-1]]
# [array([4, 4]),  array([1, 1, 5]),  array([1, 1, 1, 1, 1]), ...
  

Теперь давайте рассмотрим, что происходит на примере array ( a[5, :] ):

 # a            [0, 0, 1,  0, 0, 0, 1, 1,  1, 0,]
# pad       [0, 0, 0, 1,  0, 0, 0, 1, 1,  1, 0, 0]
# diff()       [0, 0, 1, -1, 0, 0, 1, 0, 0, -1, 0]
#                     ^   ^        ^         ^
# nonzero()          [2,  3,       6,        9]
# reshape() [[2, 3],
#            [6, 9]]
# diff()     [1, 3]
  

Идея заключается в том, что при заполнении двоичного массива нулями на обоих концах можно легко найти начало и конец каждой последовательности единиц, применив np.diff() ( 1 где 0->1 и -1 где 1->0 ). Поэтому np.nonzero(np.diff()) дает индексы начальной и конечной точек каждой последовательности. Кроме того, мы знаем, что starts ( 1 ) и ends ( -1 ) всегда должны чередоваться. So np.reshape(-1, 2) дает нам начальные точки в первом столбце и конечные точки во втором. np.diff() Повторное применение к этому массиву дает длину каждой последовательности.

Комментарии:

1. Похоже, это отлично работает! Не могли бы вы объяснить процесс здесь?

2. Тот же метод в моем решении, но другое расположение аналогичных методов 😉

Ответ №2:

Это способ сделать это по строкам:

 def get_blocks(a):
    x = np.diff(a,prepend=0,append=0)
    groups, starts = np.nonzero(x*x x)
    groups, ends = np.nonzero(x*x-x)
    values = ends - starts
    markers = np.diff(groups, prepend=0)
    marker_idx = np.nonzero(marker_idx)[0]
    return np.split(values, marker_idx)
  

Пример запуска:

 >>> a = np.array([[1, 1, 1, 1, 0, 1, 1, 1, 1, 0],
   [1, 0, 0, 1, 0, 1, 1, 1, 1, 1],
   [1, 0, 1, 0, 1, 0, 0, 1, 0, 1],
   [0, 1, 0, 0, 1, 0, 1, 0, 0, 0],
   [1, 1, 1, 1, 1, 1, 0, 1, 0, 1],
   [0, 0, 1, 0, 0, 1, 1, 1, 0, 0],
   [0, 0, 0, 0, 1, 1, 0, 1, 1, 0],
   [0, 0, 0, 0, 0, 0, 0, 0, 1, 1],
   [0, 1, 0, 1, 0, 1, 0, 0, 0, 0],
   [0, 0, 1, 0, 0, 0, 1, 1, 1, 0]])
>>> get_blocks(a)
[array([4, 4], dtype=int32), 
array([1, 1, 5], dtype=int32), 
array([1, 1, 1, 1, 1], dtype=int32), 
array([1, 1, 1], dtype=int32), 
array([6, 1, 1], dtype=int32), 
array([1, 3], dtype=int32), 
array([2, 2], dtype=int32), 
array([2], dtype=int32), 
array([1, 1, 1], dtype=int32), 
array([1, 3], dtype=int32)]
  

Это работает и для столбцов, если вы вызываете его A.T

Комментарии:

1. np.diff() принимает аргумент perpend и аргумент append ?! Чувак, я должен чаще просматривать документацию.

2. @sclaronomic Да, похоже, это новая функция с января 2019 года, когда был выпущен numpy 1.16.0.