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 и объедините их со значениями. Оба подхода явно выражают преобразование и на практике масштабируются куда лучше.