Урок курса
Изменение формы: reshape, transpose, добавление и удаление осей
NumPy и pandas: практический тренажёрВ прошлом уроке вы создавали массивы через np.arange, np.zeros, np.ones и другие конструкторы — и каждый раз получали массив с конкретной формой. Теперь задача другая: взять уже существующий массив и поменять его форму, не трогая данные внутри.
reshape: изменение формы и правило сохранения числа элементов
Метод arr.reshape(new_shape) возвращает новый взгляд на те же данные в другой форме. Сами элементы никуда не перемещаются — меняется только интерпретация: сколько строк, столбцов, «слоёв» видит NumPy.
Единственное жёсткое условие: произведение всех размеров в новой форме должно совпадать с исходным size массива. Иначе NumPy выбросит ValueError.
import numpy as np
a = np.arange(12) # shape (12,), size = 12
print(a.shape) # (12,)
b = a.reshape(3, 4) # 3 × 4 = 12 ✓
print(b.shape) # (3, 4)
c = a.reshape(2, 6) # 2 × 6 = 12 ✓
print(c.shape) # (2, 6)
d = a.reshape(6, 2) # 6 × 2 = 12 ✓
print(d.shape) # (6, 2)
Во всех трёх случаях size остаётся 12. Порядок элементов при обходе сохраняется: NumPy раскладывает их построчно (row-major, или C-порядок по умолчанию).
Что происходит, если произведение не сходится:
a.reshape(2, 4) # 2 × 4 = 8 ≠ 12 → ValueError
# ValueError: cannot reshape array of size 12 into shape (2,4)
Ошибка возникает сразу при вызове — NumPy не пытается ничего подгонять. Это хорошо: лучше упасть с понятным сообщением, чем молча создать массив неожиданной формы.
Рабочий паттерн в реальном коде — всегда проверять shape после reshape:
b = a.reshape(3, 4)
print(b.shape) # убеждаемся, что получили (3, 4)
print(b.size) # 12 — size не изменился
Обратите внимание: reshape возвращает новый объект, но часто это представление (view) того же буфера памяти, а не копия. Если изменить элемент в b, изменение может отразиться в a. Для учебных задач это обычно не важно, но знать стоит.
-1 как авторазмер в reshape
Когда делаешь reshape, часто знаешь одно измерение заранее, а второе приходится считать вручную: 12 элементов, хочу 3 строки — значит 4 столбца. NumPy умеет делать это сам, если вместо одного из размеров написать -1.
import numpy as np
a = np.arange(12) # shape (12,), size = 12
b = a.reshape(3, -1) # NumPy считает: 12 // 3 = 4 → shape (3, 4)
print(b.shape) # (3, 4)
c = a.reshape(-1, 4) # NumPy считает: 12 // 4 = 3 → shape (3, 4)
print(c.shape) # (3, 4)
d = a.reshape(-1, 6) # 12 // 6 = 2 → shape (2, 6)
print(d.shape) # (2, 6)
Формула простая: NumPy берёт size массива и делит на произведение всех явно указанных размеров. Результат и есть значение, которое подставляется вместо -1.
То же работает для трёхмерных форм:
e = a.reshape(2, 2, -1) # 12 // (2 × 2) = 3 → shape (2, 2, 3)
print(e.shape) # (2, 2, 3)
f = a.reshape(2, -1, 3) # 12 // (2 × 3) = 2 → shape (2, 2, 3)
print(f.shape) # (2, 2, 3)
Одно жёсткое ограничение: -1 можно указать ровно один раз. Если написать reshape(-1, -1), NumPy не знает, как разложить size на два неизвестных — это сразу ValueError.
a.reshape(-1, -1) # ValueError: can only specify one unknown dimension
Второй подводный камень — когда size не делится нацело на явно указанные размеры:
a.reshape(5, -1) # 12 // 5 = 2.4 — не целое
# ValueError: cannot reshape array of size 12 into shape (5,newaxis)
NumPy не округляет и не обрезает — просто падает с ошибкой.
-1 особенно удобен в реальном коде, когда размер массива заранее неизвестен — например, данные пришли из файла. Вместо того чтобы писать arr.reshape(arr.size // 4, 4), достаточно arr.reshape(-1, 4). Смысл тот же, но читается чище и не требует промежуточного вычисления.
Транспонирование: .T и np.transpose
Транспонирование — это не перераспределение элементов по новой сетке, как в reshape, а перестановка осей. Для двумерного массива это означает одно: строки становятся столбцами, а столбцы — строками.
Есть два эквивалентных способа. Атрибут .T — самый короткий:
import numpy as np
a = np.arange(6).reshape(2, 3)
print(a.shape) # (2, 3)
print(a)
# [[0 1 2]
# [3 4 5]]
b = a.T
print(b.shape) # (3, 2)
print(b)
# [[0 3]
# [1 4]
# [2 5]]
Функция np.transpose(arr) делает то же самое и возвращает идентичный результат:
c = np.transpose(a)
print(c.shape) # (3, 2)
print(np.array_equal(b, c)) # True
Что происходит с shape: исходный (r, c) превращается в (c, r). Число элементов не изменяется — size остаётся прежним, это тот же инвариант, что и у reshape.
Обратите внимание на элементы в результате. В исходном массиве первая строка — [0, 1, 2]. После транспонирования эти значения оказываются в первом столбце: 0 в строке 0, 1 в строке 1, 2 в строке 2. Это и есть перестановка осей: то, что шло по оси 1 (столбцы), теперь идёт по оси 0 (строки), и наоборот.
Поэтому .T не эквивалентен reshape с переставленными размерами. Сравните:
a = np.arange(6).reshape(2, 3)
print(a.T)
# [[0 3]
# [1 4]
# [2 5]]
print(a.reshape(3, 2))
# [[0 1]
# [2 3]
# [4 5]]
Результаты разные. reshape(3, 2) просто переупаковывает элементы в новую сетку, сохраняя их порядок обхода строка за строкой. .T меняет назначение осей — элемент, который стоял в позиции [i, j], теперь стоит в позиции [j, i].
Для одномерного массива .T не даёт никакого эффекта: у вектора shape (n,) нет второй оси, которую можно переставить, поэтому shape остаётся (n,). Если нужно превратить вектор-строку в вектор-столбец, это задача для np.newaxis — об этом в следующем разделе.
Добавление и удаление единичных осей: np.newaxis и squeeze
Иногда нужно не переупаковать данные, а буквально «вставить» новую ось в конкретное место формы — не меняя ни одного элемента. Именно для этого существует np.newaxis.
Технически np.newaxis — это просто псевдоним для None. Его помещают внутрь квадратных скобок при индексировании, и NumPy вставляет единичную ось там, где он стоит.
import numpy as np
v = np.arange(4) # shape (4,)
print(v.shape) # (4,)
col = v[:, np.newaxis] # добавляем ось справа → shape (4, 1)
print(col.shape) # (4, 1)
row = v[np.newaxis, :] # добавляем ось слева → shape (1, 4)
print(row.shape) # (1, 4)
Позиция np.newaxis в индексном выражении определяет, куда именно вставляется новая ось. v[:, np.newaxis] — ось появляется вторым измерением (вектор-столбец). v[np.newaxis, :] — ось появляется первым (вектор-строка).
Важно понимать, что именно это решает задачу, с которой .T не справляется. Транспонирование одномерного массива ничего не меняет, потому что перестановка единственной оси самой с собой — это тождественная операция. А np.newaxis сначала явно добавляет вторую ось, и только тогда форма становится двумерной.
v = np.arange(4)
print(v.T.shape) # (4,) — .T не помог
print(v[:, np.newaxis].shape) # (4, 1) — теперь это вектор-столбец
Обратная операция — np.squeeze. Она удаляет из формы все оси размером 1, независимо от их числа и позиции:
x = np.arange(4).reshape(1, 4, 1) # shape (1, 4, 1)
print(np.squeeze(x).shape) # (4,)
y = np.arange(3).reshape(3, 1) # shape (3, 1)
print(np.squeeze(y).shape) # (3,)
z = np.arange(6).reshape(2, 3) # shape (2, 3) — единичных осей нет
print(np.squeeze(z).shape) # (2, 3) — форма не изменилась
squeeze удобен, когда данные приходят с лишними единичными измерениями — например, после загрузки или после операции, которая «по правилам» возвращает двумерный результат там, где ожидается одномерный.
Один практический момент: squeeze без аргументов удаляет все единичные оси сразу. Если нужно убрать только конкретную ось, есть параметр axis:
x = np.zeros((1, 4, 1)) # shape (1, 4, 1)
print(np.squeeze(x, axis=0).shape) # (4, 1) — убрали только первую ось
print(np.squeeze(x, axis=2).shape) # (1, 4) — убрали только третью ось
Теперь соберём все операции урока в один сквозной пример, чтобы видеть цепочку преобразований и shape на каждом шаге:
import numpy as np
a = np.arange(12) # shape (12,), size 12
print(a.shape) # (12,)
b = a.reshape(3, 4) # shape (3, 4)
print(b.shape) # (3, 4)
c = a.reshape(4, -1) # -1 → 12 // 4 = 3, shape (4, 3)
print(c.shape) # (4, 3)
d = b.T # shape (4, 3)
print(d.shape) # (4, 3)
e = np.transpose(b) # shape (4, 3), идентично b.T
print(np.array_equal(d, e)) # True
f = a.reshape(12, 1) # shape (12, 1)
print(f.shape) # (12, 1)
g = a[:, np.newaxis] # shape (12, 1) — то же через newaxis
print(g.shape) # (12, 1)
h = np.squeeze(g) # shape (12,) — единичная ось убрана
print(h.shape) # (12,)
print(a.size == b.size == g.size == h.size) # True — size не менялся
f и g дают одинаковый результат — shape (12, 1) — разными путями: reshape(12, 1) и [:, np.newaxis]. В реальном коде np.newaxis читается нагляднее именно когда задача — добавить ось к уже существующему массиву, не задумываясь о его полной форме.
Последняя строка явно подтверждает инвариант: все промежуточные формы разные, но size везде одинаковый. Это и есть главное ограничение, которое NumPy проверяет при каждом преобразовании формы.
Закрепите тему на задачах
Перед практикой выпишите исходную и итоговую shape. После reshape число элементов должно сохраниться, а transpose меняет порядок осей. Проверьте, затронет ли изменение результата исходный массив.
NumPy squeeze и newaxis: строка, столбец и форма массива
NumPy reshape и transpose: одинаковая форма, разные значения
Откройте условие и решите задачу перед чтением следующей темы. В задачах на код проверяется программа, в тестовых заданиях — выбранный ответ. Для последовательного обучения вернитесь в программу бесплатного курса.
Задача 1
Массив arr имеет shape (6, 4). Чему будет равен arr.T.shape?
Задача 2
Программист написал arr = np.ones((3, 4)); b = arr.reshape(2, -1). Какой shape будет у b?
Задача 3
Цепочка: reshape с -1, newaxis, transpose, squeeze
Что уже дано
Проверяющая система запускает вашу программу и подаёт данные через stdin. Никаких заранее созданных переменных нет — вы читаете всё сами.
Что нужно сделать
Напишите программу, которая:
- Читает из
stdinодну строку с двумя целыми числами:rowsиcols. - Создаёт одномерный массив
aчерезnp.arange(rows * cols)— shape(rows * cols,). - Преобразует
aв двумерный массивbчерезreshape(rows, -1)— пусть NumPy сам вычислит второй размер. - Транспонирует
bчерезnp.transpose— получает массивcshape(cols, rows). - Добавляет единичную ось слева к
cчерезc[np.newaxis, :]— получает массивdshape(1, cols, rows). - Убирает все единичные оси из
dчерезnp.squeeze— получает массивe. - Выводит в
stdoutровно пять строк — по одной на каждый промежуточный результат — в формате<имя>: shape=<shape> size=<size>:
b: shape=...c: shape=...d: shape=...e: shape=...- И финальную строку:
size_invariant=Trueесли все четыре массива имеют одинаковыйsize, иначеsize_invariant=False.
Ввод и вывод
Программа читает stdin и пишет в stdout.
Формат ввода
Одна строка: два целых числа rows и cols, разделённые пробелом. Гарантируется, что rows >= 2, cols >= 2.
Формат вывода
Пять строк:
b: shape=(rows, cols) size=<rows*cols>
c: shape=(cols, rows) size=<rows*cols>
d: shape=(1, cols, rows) size=<rows*cols>
e: shape=(cols, rows) size=<rows*cols>
size_invariant=True
Формат shape — строго как у Python: (r, c) или (1, c, r) и т. д.
Без регистрации. Гостевая проверка не сохраняет прогресс. Для сохранения перейдите к уроку.
Отправьте решение, чтобы увидеть результаты тестов.
Задача 4
Найди и исправь: неверные вызовы reshape и transpose
Что уже дано
Проверяющая система запускает вашу программу и подаёт данные через stdin. Никаких заранее созданных переменных нет — вы читаете всё сами.
В редакторе уже есть сломанный код. Он пытается выполнить три преобразования формы, но в каждом из трёх вызовов допущена ошибка: либо неверное число элементов в новой форме, либо неправильное использование -1, либо перепутаны операции.
Что нужно сделать
Найдите и исправьте все три ошибки в коде так, чтобы программа:
- Читала из
stdinодну строку с двумя целыми числами:rowsиcols. - Создавала одномерный массив
aчерезnp.arange(rows * cols)— shape(rows * cols,). - Преобразовывала
aчерезreshape(rows, cols + 1)→ ошибка: правильный вызов должен бытьa.reshape(rows, -1), чтобы NumPy сам вычислил второй размер. - Транспонировала результат через
.reshape(cols, rows)→ ошибка: правильная операция —.T(транспонирование, а не reshape). - Применяла
np.squeezeк результату предыдущего шага, добавив перед этим единичную ось черезnp.newaxis→ ошибка:newaxisдобавляется через индексированиеarr[np.newaxis, :], а не присваиванием. - Выводила в
stdoutровно три строки:
reshaped: <shape>
transposed: <shape>
squeezed: <shape>
Формат shape — строго как у Python: (r, c) или (n,) и т. д.
Ввод и вывод
Программа читает stdin и пишет в stdout.
Формат ввода
Одна строка: два целых числа rows и cols, разделённые пробелом. Гарантируется, что rows >= 2, cols >= 2.
Формат вывода
Три строки:
reshaped: (rows, cols)
transposed: (cols, rows)
squeezed: (cols, rows)
Без регистрации. Гостевая проверка не сохраняет прогресс. Для сохранения перейдите к уроку.
Отправьте решение, чтобы увидеть результаты тестов.
Задача 5
Вектор-столбец, вектор-строка и squeeze с axis: управление единичными осями
Что уже дано
Проверяющая система запускает вашу программу и подаёт данные через stdin. Никаких заранее созданных переменных нет — вы читаете всё сами.
Что нужно сделать
Напишите программу, которая:
- Читает из
stdinодну строку с целым числомn. - Создаёт одномерный массив
vчерезnp.arange(n)— shape(n,). - Превращает
vв вектор-столбецcolчерезv[:, np.newaxis]— shape(n, 1). - Превращает
vв вектор-строкуrowчерезv[np.newaxis, :]— shape(1, n). - Применяет
np.squeeze(col, axis=1)— убирает только вторую ось уcol, получает массивs1shape(n,). - Применяет
np.squeeze(row, axis=0)— убирает только первую ось уrow, получает массивs2shape(n,). - Выводит в
stdoutровно шесть строк в формате<имя>: shape=<shape> size=<size>:
v: shape=...col: shape=...row: shape=...s1: shape=...s2: shape=...all_equal=Trueеслиv.size == col.size == row.size == s1.size == s2.size, иначеall_equal=False.
Ввод и вывод
Программа читает stdin и пишет в stdout.
Формат ввода
Одна строка: целое число n (гарантируется n >= 2).
Формат вывода
Шесть строк:
v: shape=(n,) size=<n>
col: shape=(n, 1) size=<n>
row: shape=(1, n) size=<n>
s1: shape=(n,) size=<n>
s2: shape=(n,) size=<n>
all_equal=True
Формат shape — строго как у Python: (n,), (n, 1), (1, n) и т. д.
Без регистрации. Гостевая проверка не сохраняет прогресс. Для сохранения перейдите к уроку.
Отправьте решение, чтобы увидеть результаты тестов.
Задача 6
.T и reshape: одинаковая форма, разное расположение значений
Что уже дано
Проверяющая система запускает вашу программу и подаёт данные через stdin. Никаких заранее созданных переменных нет — вы читаете всё сами.
Что нужно сделать
Напишите программу, которая на одном массиве показывает разницу между транспонированием и изменением формы:
- Читает два целых числа
rowsиcols. Гарантируется, что они различаются и оба не меньше 2. - Создаёт
flat = np.arange(rows * cols). - Создаёт
matформы(rows, cols)черезreshape. - Создаёт
via_t = mat.T. - Независимо создаёт
via_reshape = flat.reshape(cols, rows). - Печатает заголовок
T:, затем сам массивvia_t, заголовокreshape:, затем сам массивvia_reshape. - Последней строкой печатает
same_shape=True, если формы результатов совпадают.
Не переставляйте значения вручную. В этой задаче важно увидеть: .T и reshape(cols, rows) дают одинаковую форму, но располагают исходные значения по-разному.
Ввод и вывод
Программа читает данные из stdin и пишет результат в stdout.
Формат ввода
Одна строка: два различных целых числа rows cols, каждое не меньше 2.
Формат вывода
Сначала T: и напечатанный NumPy-массив via_t, затем reshape: и массив via_reshape, затем строка same_shape=True.
Без регистрации. Гостевая проверка не сохраняет прогресс. Для сохранения перейдите к уроку.
Отправьте решение, чтобы увидеть результаты тестов.
Задачи к уроку
- NumPy reshape и transpose: разница на примере и задача
Почему reshape и transpose дают разные значения при одинаковой форме? Разберите пример и проверьте понимание в задаче по NumPy.
- NumPy newaxis и squeeze: вектор-строка и вектор-столбец
Как получить формы (n, 1) и (1, n) через np.newaxis и убрать единичную ось через squeeze? Пример и задача на формы массивов.
Продолжить с сохранением прогресса
Перейдите к уроку в учебном кабинете.
