Урок курса
Базовая индексация: элементы, строки, столбцы и срезы
NumPy и pandas: практический тренажёрВ прошлом уроке мы меняли форму массивов через reshape и .T, получая двумерные структуры с нужным числом строк и столбцов. Теперь, когда массив уже существует, нужно уметь обращаться к конкретным его элементам — и именно с этого начинается индексация.
Целочисленный индекс: выбор скаляра в 1D и 2D
Самый простой способ получить что-то из массива — обратиться к нему по целочисленному индексу.
Для одномерного массива синтаксис ничем не отличается от обычного Python-списка:
import numpy as np
arr = np.array([10, 20, 30, 40, 50])
print(arr[0]) # 10
print(arr[2]) # 30
print(arr[-1]) # 50
Отрицательные индексы отсчитываются с конца: arr[-1] — последний элемент, arr[-2] — предпоследний. Это удобно, когда длина массива заранее неизвестна.
Для двумерного массива нужно указать сразу оба индекса через запятую: arr[i, j] — строка i, столбец j.
arr2d = np.arange(12).reshape(3, 4)
# [[ 0, 1, 2, 3],
# [ 4, 5, 6, 7],
# [ 8, 9, 10, 11]]
print(arr2d[1, 2]) # 6
print(arr2d[0, 0]) # 0
print(arr2d[-1, -1]) # 11
Здесь arr2d[1, 2] — второй ряд (индекс 1), третий столбец (индекс 2).
Что важно понять про результат. Когда вы обращаетесь к элементу по целочисленному индексу, вы получаете не одноэлементный массив, а NumPy-скаляр. У него нет осей вообще — его shape равен пустому кортежу ():
elem = arr2d[1, 2]
print(type(elem)) # <class 'numpy.int64'> (или другой numpy int)
print(elem.shape) # ()
Это принципиально отличает скаляр от одномерного массива с одним элементом shape (1,). Разница даёт о себе знать позже, когда NumPy применяет правила broadcasting — там форма результата имеет значение. Пока достаточно знать: целочисленный индекс по каждой оси убирает эту ось из результата, и если индексов столько же, сколько осей, от массива не остаётся ни одной оси.
Выбор целой строки и столбца в 2D-массиве
Когда нужен не один элемент, а целая строка или столбец, в игру вступает двоеточие : — символ, который означает «все индексы по этой оси».
Возьмём массив shape (3, 4):
import numpy as np
arr = np.arange(12).reshape(3, 4)
# [[ 0, 1, 2, 3],
# [ 4, 5, 6, 7],
# [ 8, 9, 10, 11]]
Чтобы взять вторую строку (индекс 1), можно написать двумя эквивалентными способами:
print(arr[1]) # [4, 5, 6, 7] — shape (4,)
print(arr[1, :]) # [4, 5, 6, 7] — shape (4,)
arr[1] — сокращённая запись; NumPy автоматически дополняет недостающие оси полными срезами. arr[1, :] делает то же самое явно: берём строку 1, а по столбцам — всё подряд.
Чтобы взять третий столбец (индекс 2), двоеточие ставится на место строкового индекса:
print(arr[:, 2]) # [ 2, 6, 10] — shape (3,)
Здесь : по первой оси означает «все строки», а 2 по второй фиксирует конкретный столбец.
Что происходит с shape. Результат в обоих случаях — одномерный массив, а не двумерный. Механизм простой: целый индекс убирает ту ось, по которой он задан.
arr[1, :]— ось строк схлопнулась из-за1, ось столбцов сохранилась полностью → shape(4,).arr[:, 2]— ось строк сохранилась полностью, ось столбцов схлопнулась из-за2→ shape(3,).
Проверить это можно прямо в коде:
row = arr[1]
col = arr[:, 2]
print(row.shape) # (4,)
print(col.shape) # (3,)
Одномерный массив — это не скаляр и не строка-матрица. Его можно складывать, умножать, передавать в функции — и при этом shape чётко говорит, сколько в нём элементов и вдоль какой логической оси они лежат.
Отрицательные индексы работают так же, как при точечном доступе: arr[-1] даёт последнюю строку, arr[:, -1] — последний столбец.
print(arr[-1]) # [ 8, 9, 10, 11] — shape (4,)
print(arr[:, -1]) # [ 3, 7, 11] — shape (3,)
Это часто используют, когда нужно быстро взять крайние строки или столбцы, не зная заранее размеров массива.
Срез start:stop:step и прямоугольный блок по двум осям
Если целый индекс выбирает одну конкретную позицию, то срез выбирает диапазон позиций вдоль оси. Полная форма записи — start:stop:step, где:
start— с какой позиции начинать (включительно);stop— до какой позиции идти (не включая её);step— шаг между выбираемыми позициями.
Любой из трёх параметров можно опустить. При положительном шаге: пропущенный start означает начало оси, пропущенный stop — конец оси, пропущенный step — шаг 1. При отрицательном шаге пропущенные границы меняются на противоположные: движение начинается с последнего элемента и идёт к позиции перед первым.
Посмотрим на одномерном примере:
import numpy as np
v = np.array([10, 20, 30, 40, 50, 60])
print(v[1:4]) # [20, 30, 40] — позиции 1, 2, 3
print(v[::2]) # [10, 30, 50] — каждый второй с начала
print(v[1::2]) # [20, 40, 60] — каждый второй, начиная с позиции 1
print(v[::-1]) # [60, 50, 40, 30, 20, 10] — обратный порядок
v[1:4] берёт позиции 1, 2, 3 — три элемента. v[::2] идёт от начала до конца с шагом 2. v[::-1] — отрицательный шаг: пропущенные start и stop по умолчанию задают движение от последнего элемента к первому включительно, что даёт разворот массива.
Переход в два измерения. Для двумерного массива срезы по строкам и по столбцам записываются через запятую: arr[r1:r2, c1:c2]. Это выбирает прямоугольный блок: строки от r1 до r2-1 и столбцы от c1 до c2-1.
arr = np.arange(12).reshape(3, 4)
# [[ 0, 1, 2, 3],
# [ 4, 5, 6, 7],
# [ 8, 9, 10, 11]]
print(arr[0:2, 1:3])
# [[1, 2],
# [5, 6]]
arr[0:2, 1:3] — строки 0 и 1, столбцы 1 и 2. Получаем прямоугольный блок 2×2.
Можно применять шаг сразу по обеим осям:
print(arr[::2, ::2])
# [[ 0, 2],
# [ 8, 10]]
По строкам шаг 2 — берём строки 0 и
- По столбцам шаг 2 — берём столбцы 0 и
- Результат снова прямоугольный.
Комбинация среза с целым индексом. Ничто не мешает задать по одной оси срез, а по другой — конкретный индекс:
print(arr[1:3, 0])
# [4, 8]
Здесь по строкам — срез 1:3 (строки 1 и 2), по столбцам — целый индекс 0. Ось столбцов схлопывается, остаётся одномерный массив из двух элементов. Это принципиально отличает такой вызов от arr[1:3, 0:1]: последний сохранит ось столбцов и вернёт shape (2, 1), а не (2,). Правило простое: срез сохраняет ось, целый индекс убирает её — но о том, как из этого вычислять shape результата, поговорим в следующем разделе.
Предсказание shape результата и сводный пример
Теперь можно сформулировать единственное правило, которое объясняет shape любого результата базовой индексации:
- Целый индекс по оси убирает эту ось из результата.
- Срез по оси сохраняет ось, длина которой определяется диапазоном среза.
Из этого немедленно следует: если вы задали целый индекс по всем осям — получаете скаляр shape (). Если по одной оси срез, по другой целый индекс — одномерный массив. Если по обеим осям срезы — двумерный массив.
Как вычислить длину среза с шагом. Самый надёжный способ — использовать встроенный метод Python slice(...).indices(n), который применяет те же правила нормализации границ, что и срез NumPy. Метод возвращает нормализованную тройку (start, stop, step) с учётом знака шага, отрицательных границ и выхода за пределы оси длиной n; длина среза — это len(range(*slice(s, e, k).indices(n))).
import numpy as np
n = 5
# Положительный шаг, обычный случай
print(len(range(*slice(1, 4, 1).indices(n)))) # 3 (позиции 1, 2, 3)
print(len(range(*slice(0, 6, 2).indices(n)))) # 3 (позиции 0, 2, 4 — stop обрезан до 5)
# Отрицательный шаг
print(len(range(*slice(None, None, -1).indices(n)))) # 5 ([::-1] по оси длиной 5)
print(len(range(*slice(3, 0, -1).indices(n)))) # 3 (позиции 3, 2, 1)
Для частного случая положительного шага и границ 0 <= start < stop <= n длину можно посчитать вручную по формуле ceil((stop - start) / step). Например, 1:4:1 → ceil(3/1) = 3, 0:4:2 → ceil(4/2) = 2. Шаблон ::k по оси длиной n при положительном k даёт ceil(n / k). Но как только шаг становится отрицательным или границы выходят за пределы оси — пользуйтесь slice(...).indices(n), а не формулой.
Проверим правило на одном массиве:
arr = np.arange(12).reshape(3, 4)
# [[ 0, 1, 2, 3],
# [ 4, 5, 6, 7],
# [ 8, 9, 10, 11]]
print(arr[1, 2].shape) # () — два целых индекса, обе оси убраны
print(arr[0].shape) # (4,) — целый индекс по строкам, срез : по столбцам
print(arr[:, 2].shape) # (3,) — срез : по строкам, целый индекс по столбцам
print(arr[0:2, 1:3].shape) # (2, 2) — срезы по обеим осям: 2 строки, 2 столбца
print(arr[::2, ::2].shape) # (2, 2) — ceil(3/2)=2 строки, ceil(4/2)=2 столбца
print(arr[-1].shape) # (4,) — отрицательный индекс, та же логика
Для arr[::2, ::2] считаем вручную (шаг положительный, границы в пределах оси): ось строк длиной 3, шаг 2 → ceil(3/2) = 2 строки (индексы 0 и 2); ось столбцов длиной 4, шаг 2 → ceil(4/2) = 2 столбца (индексы 0 и 2). Итого shape (2, 2).
Один нюанс, который легко пропустить: срез 0:1 и целый индекс 0 — разные вещи.
print(arr[0, :].shape) # (4,) — ось строк убрана
print(arr[0:1, :].shape) # (1, 4) — ось строк сохранена, но длина 1
Оба варианта дают «первую строку», но второй сохраняет двумерную структуру. Это важно, когда результат передаётся в функцию или участвует в операции, чувствительной к числу осей.
Кратко: shape результата определяется по каждой оси независимо. Идёте слева направо по индексному выражению — если видите целое число, эту ось вычёркиваете; если видите срез, записываете его длину. Для нетривиальных случаев длину срезов надёжнее считать через slice(...).indices(n), а не вручную.
Закрепите тему на задачах
Проверьте исключённую правую границу среза и шаг. Для двумерного массива различайте a[rows, cols] и последовательный выбор строк и столбцов.
NumPy: прямоугольный срез вместо цепочечной индексации
Срезы NumPy с шагом: задача на двумерный массив
Откройте условие и решите задачу перед чтением следующей темы. В задачах на код проверяется программа, в тестовых заданиях — выбранный ответ. Для последовательного обучения вернитесь в программу бесплатного курса.
Задача 1
Массив arr имеет shape (5, 8). Чему равен shape результата выражения arr[2, :]?
Задача 2
Массив arr создан как np.arange(24).reshape(4, 6). Какое выражение возвращает строки 0, 2 и столбцы 0, 2, 4?
Задача 3
Срезы двумерного массива: shape на каждом шаге
Что уже дано
Проверяющая система запускает вашу программу и подаёт данные через stdin. Никаких заранее созданных переменных нет — вы читаете всё сами.
Что нужно сделать
Напишите программу, которая:
- Читает из
stdinодну строку с четырьмя целыми числами:R,C,p,q. - Создаёт двумерный массив
arrчерезnp.arange(R * C).reshape(R, C). - Выполняет пять операций и выводит shape каждого результата в отдельной строке:
arr[0]— первая строка целиком;arr[:, -1]— последний столбец целиком;arr[0:p, 0:q]— прямоугольный блок из первыхpстрок иqстолбцов;arr[::2, ::2]— каждая вторая строка и каждый второй столбец;arr[1:p, q]— срез строк 1..p-1, фиксированный столбецq(целый индекс).
Garантируется: 1 <= p <= R, 0 <= q < C.
Shape выводите строго в формате Python: (n,) для одномерных, (r, c) для двумерных.
Ввод и вывод
Программа читает stdin и пишет в stdout.
Формат ввода
Одна строка: четыре целых числа R C p q, разделённые пробелами.
Формат вывода
Пять строк — shape каждого из пяти результатов в порядке, описанном выше.
Без регистрации. Гостевая проверка не сохраняет прогресс. Для сохранения перейдите к уроку.
Отправьте решение, чтобы увидеть результаты тестов.
Задача 4
Найди и исправь: цепочечная индексация вместо прямоугольного среза
Что уже дано
Проверяющая система запускает вашу программу и подаёт данные через stdin. Никаких заранее созданных переменных нет.
В редакторе уже есть сломанный код. Он пытается выполнить три операции над двумерным массивом, но в каждой допущена ошибка: либо применена цепочечная индексация вместо одного выражения, либо перепутан целый индекс со срезом, либо неверно задан шаг.
Что нужно сделать
Найдите и исправьте все три ошибки так, чтобы программа:
- Читала из
stdinодну строку с четырьмя целыми числами:R,C,r,c. - Создавала двумерный массив
arrчерезnp.arange(R * C).reshape(R, C). - Вычисляла три результата и выводила их shape в три отдельные строки:
res1: прямоугольный блок строк0..r-1и столбцов0..c-1— shape должен быть(r, c);res2: каждая вторая строка (начиная с 0) и каждый второй столбец (начиная с 0) — shape должен быть(ceil(R/2), ceil(C/2));res3: столбецc-1целиком как одномерный массив — shape должен быть(R,).
Shape выводите строго в формате Python: (n,) для одномерных, (r, c) для двумерных.
Ввод и вывод
Программа читает stdin и пишет в stdout.
Формат ввода
Одна строка: четыре целых числа R C r c, разделённые пробелами. Гарантируется: 1 <= r <= R, 1 <= c <= C.
Формат вывода
Три строки — shape результатов res1, res2, res3 в указанном порядке.
Без регистрации. Гостевая проверка не сохраняет прогресс. Для сохранения перейдите к уроку.
Отправьте решение, чтобы увидеть результаты тестов.
Задача 5
Скалярные элементы: значения и shape по целочисленным индексам
Что уже дано
Проверяющая система запускает вашу программу и подаёт данные через stdin. Никаких заранее созданных переменных нет — вы читаете всё сами.
Что нужно сделать
Напишите программу, которая:
- Читает из
stdinодну строку с четырьмя целыми числами:R,C,i,j. - Создаёт двумерный массив
arrчерезnp.arange(R * C).reshape(R, C). - Вычисляет три выражения:
e1 = arr[i, j]— элемент строкиi, столбцаj;e2 = arr[-1, -1]— элемент последней строки, последнего столбца;e3 = arr[0, -1]— элемент первой строки, последнего столбца.
- Выводит в
stdoutтри строки в формате:
<значение> <shape>
где shape — строковое представление атрибута .shape каждого результата (пустой кортеж () для скаляров).
Целочисленный индекс по каждой оси убирает эту ось. Если индексов столько же, сколько осей, результат — NumPy-скаляр с shape = ().
Ввод и вывод
Программа читает stdin и пишет в stdout.
Формат ввода
Одна строка: четыре целых числа R C i j, разделённые пробелами. Гарантируется: 2 <= R, C <= 20, 0 <= i < R, 0 <= j < C.
Формат вывода
Три строки — по одной для каждого из e1, e2, e3 — в формате <значение> <shape>. Пример строки: 6 ().
Без регистрации. Гостевая проверка не сохраняет прогресс. Для сохранения перейдите к уроку.
Отправьте решение, чтобы увидеть результаты тестов.
Задача 6
Значения через срезы с шагом и угловые элементы двумерного массива
Что уже дано
Проверяющая система запускает вашу программу и подаёт данные через stdin. Никаких заранее созданных переменных нет — вы читаете всё сами.
Что нужно сделать
Напишите программу, которая:
- Читает из
stdinодну строку с четырьмя целыми числами:R,C,sr,sc— число строк, столбцов и шаги среза по строкам и столбцам. - Создаёт двумерный массив
arrчерезnp.arange(R * C).reshape(R, C). - Вычисляет и выводит в
stdoutпять строк в указанном порядке:
- Строка 1: значение элемента
arr[0, 0](верхний левый угол). - Строка 2: значение элемента
arr[-1, -1](нижний правый угол) — через отрицательные индексы. - Строка 3: shape среза
arr[::sr, ::sc]— каждаяsr-я строка и каждыйsc-й столбец. - Строка 4: shape среза
arr[1::sr, 1::sc]— то же, но начиная с позиции 1 по каждой оси. - Строка 5: shape среза
arr[0:R:sr, -1:-(C+1):-sc]— строки с шагомsr, столбцы в обратном порядке с шагомsc.
Shape выводите строго в формате Python: (r, c) для двумерных.
Ввод и вывод
Программа читает stdin и пишет в stdout.
Формат ввода
Одна строка: четыре целых числа R C sr sc, разделённые пробелами. Гарантируется: 2 <= R, C <= 20, 1 <= sr <= R, 1 <= sc <= C.
Формат вывода
Пять строк: первые две — целые числа (значения элементов), последние три — shape в формате Python.
Без регистрации. Гостевая проверка не сохраняет прогресс. Для сохранения перейдите к уроку.
Отправьте решение, чтобы увидеть результаты тестов.
Задачи к уроку
- Срезы двумерного массива NumPy: строки, столбцы и шаг
Как работают a[::2, 1::2] и отрицательные индексы NumPy? Разберите двумерные срезы на примере и решите задачу с проверкой.
- NumPy: прямоугольный срез вместо цепочечной индексации
Исправьте цепочечную индексацию двумерного массива: выберите прямоугольный срез одной операцией NumPy.
Продолжить с сохранением прогресса
Перейдите к уроку в учебном кабинете.
