2025, Nov 19 12:02

Добавляем столбец единиц в двумерный массив NumPy

Как корректно добавить константный столбец (столбец единиц) в двумерный массив NumPy: почему ломается list comprehension и решение через broadcast_to и hstack.

Добавить константный столбец к двумерному массиву NumPy кажется пустяком, но на практике это часто вызывает сложности, когда смешивают логику списков Python с семантикой NumPy. Ниже — краткое объяснение, почему наивный подход ломается и как аккуратно решить задачу штатными средствами NumPy.

Обзор задачи

У нас есть двумерный массив с одним столбцом, и нужно добавить справа столбец из единиц. Проще говоря, превратить столбец признаков в матрицу с дополнительным столбцом смещения (bias).

Неподходящий пример

Данные выглядят так:

import numpy as np
vals2d = np.array([[6.575],
                   [6.421],
                   [7.185],
                   [6.998],
                   [6.43 ],
                   [6.012],
                   [6.172],
                   [5.631],
                   [6.004],
                   [6.377],
                   [6.03 ]])

Естественная первая попытка — собрать новый массив через list comprehension:

augmented = np.array([[item, 1] for item in vals2d])

Это приводит к ошибке вида:

ValueError: попытка присвоить элементу массива последовательность. Запрошенный массив имеет неоднородную форму после 2 измерений. Обнаруженная форма: (506, 2) + неоднородная часть.

Почему это не работает

Итерация по двумерному массиву NumPy возвращает одномерные массивы, а не скаляры. Каждая итерация здесь даёт что-то вроде array([6.575]). В итоге list comprehension строит элементы вида [array([6.575]), 1] вместо [6.575, 1]. Когда NumPy пытается собрать из этого единый числовой массив, он замечает неоднородное содержимое и отказывается его приводить. Схожая ловушка: массивы не предназначены для хранения списков Python внутри; это ведёт к типу object, который почти наверняка не подходит для этой задачи.

Правильное решение на NumPy

Оставайтесь в мире NumPy. Расширьте скаляр с помощью broadcast_to и склейте столбцы через hstack:

result = np.hstack([vals2d, np.broadcast_to(1, vals2d.shape)])

Получится:

array([[6.575, 1.   ],
       [6.421, 1.   ],
       [7.185, 1.   ],
       [6.998, 1.   ],
       [6.43 , 1.   ],
       [6.012, 1.   ],
       [6.172, 1.   ],
       [5.631, 1.   ],
       [6.004, 1.   ],
       [6.377, 1.   ],
       [6.03 , 1.   ]])

Если всё же хотите поправить list comprehension

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

np.array([[val, 1] for val in vals2d[:, 0]])
# or
np.array([[*row, 1] for row in vals2d])

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

Операции, родные для NumPy, вроде broadcast_to и hstack, предотвращают несоответствие форм и не дают скатиться к типу object. Это сохраняет корректную структуру массива и обеспечивает предсказуемое поведение последующих вычислений.

Выводы

Работая с массивами NumPy, предпочитайте векторизованные операции вместо циклов Python. Если нужно добавить константный столбец, разверните константу до целевой формы и склейте по последней оси. Если видите ошибки о «неоднородной форме», проверьте, что именно возвращает ваша итерация, и убедитесь, что вы комбинируете однородные числовые структуры, а не списки или вложенные массивы.