Урок курса
Объединение массивов: concatenate и stack
NumPy и pandas: практический тренажёрВ предыдущих уроках мы работали с отдельными массивами: считали агрегаты, обнаруживали NaN, управляли формой. Теперь встаёт другой вопрос — как склеить несколько массивов в один. Именно здесь начинается работа с np.concatenate и np.stack.
np.concatenate: объединение по существующей оси
Когда нужно склеить несколько массивов в один, первый инструмент — np.concatenate. Его сигнатура выглядит так:
np.concatenate([arr1, arr2, ...], axis=k)
Параметр axis=k указывает, вдоль какой из уже существующих осей происходит «склейка». Ключевое слово здесь — существующей: concatenate не добавляет новых измерений, он только удлиняет массив по одной из тех осей, которые уже есть.
Чтобы операция прошла без ошибки, входные массивы должны удовлетворять двум условиям:
- одинаковое число осей (одинаковый
ndim); - совпадающие размеры по всем осям, кроме той, вдоль которой идёт склейка.
Правило для shape результата простое. Пусть все входные массивы имеют ndim=2, и мы склеиваем по оси 0. Тогда по оси 0 берём сумму размеров всех входных массивов; по оси 1 — размер остаётся тем же, что у каждого входного (они обязаны совпадать).
В общем виде: если склеиваем по оси k, то размер результата по оси k равен сумме соответствующих размеров входных массивов, а по всем остальным осям размер совпадает с размером входных.
Конкретно: два массива shape (2, 3) при axis=0 дают (4, 3), при axis=1 дают (2, 6). Это можно проверить ещё до запуска кода — сложить нужные цифры вручную.
Нарушение условий совместимости NumPy сразу сообщит ошибкой ValueError: all the input array dimensions except for the concatenation axis must match exactly. Это сигнал: посмотри на shape всех входных массивов и найди несовпадение.
Практика np.concatenate по осям 0 и 1
Посмотрим, как работает concatenate на конкретном коде. Возьмём два двумерных массива одинаковой формы:
import numpy as np
a = np.array([[1, 2, 3], [4, 5, 6]]) # shape (2, 3)
b = np.array([[7, 8, 9], [10, 11, 12]]) # shape (2, 3)
# Склеиваем по оси 0 — добавляем строки
r0 = np.concatenate([a, b], axis=0)
print(r0.shape) # (4, 3)
print(r0)
# [[ 1 2 3]
# [ 4 5 6]
# [ 7 8 9]
# [10 11 12]]
# Склеиваем по оси 1 — добавляем столбцы
r1 = np.concatenate([a, b], axis=1)
print(r1.shape) # (2, 6)
print(r1)
# [[ 1 2 3 7 8 9]
# [ 4 5 6 10 11 12]]
При axis=0 массивы встают друг под другом: строк стало 2+2=4, а три столбца остались. При axis=1 — встают рядом: строки те же две, а столбцов стало 3+3=6.
Одномерный случай немного отличается — у вектора только одна ось, поэтому axis=0 единственный вариант:
x = np.array([1, 2, 3]) # shape (3,)
y = np.array([4, 5]) # shape (2,)
result = np.concatenate([x, y], axis=0)
print(result.shape) # (5,)
print(result) # [1 2 3 4 5]
Обратите внимание: здесь x и y имеют разные длины, и это нормально — по единственной оси размеры суммируются, никаких дополнительных ограничений нет.
Главное, что стоит закрепить из этих примеров: shape результата всегда можно посчитать вручную ещё до запуска. Берёте shape входных массивов, в позиции оси k складываете числа, по остальным позициям оставляете как есть. Если цифры не сходятся — NumPy выдаст ValueError ещё до того, как что-то пойдёт не так в логике программы.
np.stack: создание новой оси и практика по разным позициям
np.stack решает другую задачу: он не удлиняет массив вдоль существующей оси, а создаёт новую ось и складывает массивы вдоль неё. Это единственное, но принципиальное отличие от concatenate.
Сигнатура:
np.stack([arr1, arr2, ...], axis=k)
Параметр axis=k теперь означает: на какую позицию в кортеже shape вставить новое измерение. После вставки размер по этой позиции будет равен числу сложенных массивов.
Жёсткое требование: все входные массивы должны иметь строго одинаковую shape. Не просто совпадать по одной оси — а быть идентичными по всем осям. Если хотя бы один массив отличается, NumPy выбросит ValueError.
Одномерный случай
Возьмём два вектора shape (3,):
import numpy as np
a = np.array([1, 2, 3]) # shape (3,)
b = np.array([4, 5, 6]) # shape (3,)
# Новая ось в позиции 0 — массивы становятся строками
s0 = np.stack([a, b], axis=0)
print(s0.shape) # (2, 3)
print(s0)
# [[1, 2, 3],
# [4, 5, 6]]
# Новая ось в позиции 1 — массивы становятся столбцами
s1 = np.stack([a, b], axis=1)
print(s1.shape) # (3, 2)
print(s1)
# [[1, 4],
# [2, 5],
# [3, 6]]
При axis=0 два вектора длиной 3 превращаются в матрицу (2, 3): два — это число массивов, 3 — исходная длина. При axis=1 новая ось вставляется между существующей длиной и ничем: получаем (3, 2). Можно представить это как «каждый элемент вектора теперь имеет два значения — из a и из b».
Двумерный случай
Когда входные массивы двумерные, результат np.stack будет трёхмерным — новое измерение добавляется к уже существующим двум:
A = np.ones((3, 4)) # shape (3, 4)
B = np.ones((3, 4)) * 2 # shape (3, 4)
print(np.stack([A, B], axis=0).shape) # (2, 3, 4)
print(np.stack([A, B], axis=1).shape) # (3, 2, 4)
print(np.stack([A, B], axis=2).shape) # (3, 4, 2)
Логика прямая: берёте исходный кортеж (3, 4) и вставляете 2 (число массивов) на позицию axis:
axis=0→ вставляем2перед3, 4→(2, 3, 4)axis=1→ вставляем2между3и4→(3, 2, 4)axis=2→ вставляем2после3, 4→(3, 4, 2)
Эта операция вставки — ключ к тому, как читать shape результата np.stack без запуска кода. Нужно знать: сколько массивов складываете и куда вставляете новое измерение.
Разница между concatenate и stack: алгоритм проверки shape
Оба инструмента объединяют массивы, но делают это принципиально по-разному. np.concatenate работает с уже существующими осями: число измерений результата совпадает с числом измерений входных массивов, а один из размеров увеличивается. np.stack создаёт новую ось: результат всегда имеет на одно измерение больше, чем входные.
Это различие хорошо видно на одномерных векторах:
np.concatenate([a, b], axis=0)для двух векторов shape(n,)даёт shape(2n,)— один длинный вектор.np.stack([a, b], axis=0)для тех же векторов даёт shape(2, n)— матрицу из двух строк.
Результаты морфологически разные, хотя данные «те же самые».
Алгоритм предсказания shape для concatenate
Чтобы узнать shape результата до запуска кода, достаточно трёх шагов:
- Выпишите shape каждого входного массива.
- Убедитесь, что по всем осям, кроме
k, размеры совпадают — иначе будетValueError. - По оси
kсложите соответствующие размеры всех входных массивов; по остальным осям оставьте размер как есть.
Пример: массивы (2, 3) и (2, 3) при axis=0 → складываем первые числа: 2+2=4, второе остаётся 3 → результат (4, 3).
Алгоритм предсказания shape для stack
- Убедитесь, что все входные массивы имеют строго одинаковую shape.
- Возьмите исходный кортеж shape.
- Вставьте число массивов
nв позициюkэтого кортежа.
Пример: два массива (3, 4) при axis=1 → берём (3, 4), вставляем 2 на позицию 1 → (3, 2, 4).
Этот алгоритм работает для любого числа осей и любого числа складываемых массивов. Если складываете пять массивов shape (6, 7) через np.stack(axis=0), результат — (5, 6, 7): число массивов на нулевую позицию, исходная форма следом.
Главный практический вопрос при выборе между двумя функциями: нужно ли сохранить число осей или добавить новое измерение? Если нужно просто удлинить — concatenate. Если нужно «сложить в стопку» с сохранением структуры каждого массива — stack.
Попробуйте решить
Даны два массива shape (3, 4). Какой shape будет у результата np.concatenate([a, b], axis=1)?
Продолжить с проверкой и прогрессом
Откройте интерактивный раннер с заданиями урока.
