2025, Dec 21 03:01

Эффективное преобразование 2D‑массива в [x, y, значение] без циклов: NumPy и Pandas

Показываем, как превратить 2D‑массив в триплеты [x, y, значение] без медленных циклов. Два способа: NumPy (meshgrid) и Pandas (stack, reset_index). С примерами.

Преобразование 2D‑массива изображения в компактный список триплетов [x, y, значение] — типичная задача в обработке данных, визуализации и извлечении признаков. Прямолинейный подход с вложенными циклами на больших массивах работает мучительно медленно, даже если логика верная. Ниже — краткое объяснение, почему так происходит, и как выполнить ту же работу эффективно с помощью NumPy или Pandas.

Постановка задачи

Предположим, у нас есть массив 1000×1000, похожий на изображение, и нужно получить матрицу из трёх столбцов, где первые два — координаты x и y, а третий — значение пикселя из исходного массива.

Наивная реализация, упирающаяся в пределы производительности

import numpy as np

arr2d = np.random.rand(1000, 1000)
pts = np.array([(cx, cy) for cx in range(arr2d.shape[1]) for cy in range(arr2d.shape[0])])
pts = np.c_[pts, np.zeros(pts.shape[0])]
for r in range(arr2d.shape[0]):
    for c in range(arr2d.shape[1]):
        pts[np.logical_and(pts[:, 1] == r, pts[:, 0] == c), 2] = arr2d[r, c]

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

Почему так происходит

Подход снова и снова сканирует и сопоставляет координаты для каждого элемента, из-за чего возникает огромный объём работы. В теории результат корректен, но время выполнения становится непрактичным для массивов с сотнями тысяч или миллионами элементов. Цель — получить ту же структуру [x, y, значение] без итераций в Python.

Эффективные решения

Есть два лаконичных способа прийти к тому же результату. Один использует Pandas и меняет форму данных в несколько шагов. Второй остаётся целиком в NumPy и напрямую строит координатные сетки.

Вариант 1: Pandas stack + reset_index

import numpy as np
import pandas as pd

arr2d = np.random.rand(1000, 1000)
result = pd.DataFrame(arr2d).stack().reset_index().to_numpy()

Здесь строки DataFrame — это y‑координаты, а столбцы — x‑координаты. Операция stack сворачивает все столбцы в один, превращая x в часть иерархического индекса вместе с y. Шаг reset_index расплющивает этот мультииндекс в обычные столбцы, формируя структуру со столбцами [y, x, val]. Наконец, to_numpy преобразует её в массив NumPy.

Вариант 2: Чистый NumPy с meshgrid + hstack

import numpy as np

arr2d = np.random.rand(1000, 1000)
rows, cols = arr2d.shape

yy, xx = np.meshgrid(np.arange(cols), np.arange(rows))
output = np.hstack([
    xx.reshape(-1, 1),
    yy.reshape(-1, 1),
    arr2d.reshape(-1, 1)
])

Так за один раз строятся матрицы координат. Вызовы reshape превращают их в столбцовые векторы, а hstack склеивает их по горизонтали, получая трёхколоночный массив [x, y, значение].

Почему это важно

По мере роста данных поэлементные циклы на Python перестают быть жизнеспособными. Векторизованные преобразования формы со stack или meshgrid сохраняют логику лаконичной и понятной и избегают затрат на обработку каждого элемента, которые губят наивный подход.

Выводы

Если нужно превратить 2D‑массив в триплеты [x, y, значение], избегайте вложенных циклов и повторного сопоставления координат. Либо воспользуйтесь в Pandas связкой stack и reset_index, чтобы получить [y, x, val] в виде массива NumPy, либо постройте сетки x и y в NumPy через meshgrid и объедините их со значениями. Оба подхода явно выражают преобразование и на практике масштабируются куда лучше.