Урок курса

Базовая индексация: элементы, строки, столбцы и срезы

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 и

  1. По столбцам шаг 2 — берём столбцы 0 и
  2. Результат снова прямоугольный.

Комбинация среза с целым индексом. Ничто не мешает задать по одной оси срез, а по другой — конкретный индекс:

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. Никаких заранее созданных переменных нет — вы читаете всё сами.

Что нужно сделать

Напишите программу, которая:

  1. Читает из stdin одну строку с четырьмя целыми числами: R, C, p, q.
  2. Создаёт двумерный массив arr через np.arange(R * C).reshape(R, C).
  3. Выполняет пять операций и выводит 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 каждого из пяти результатов в порядке, описанном выше.

РешениеPython

Без регистрации. Гостевая проверка не сохраняет прогресс. Для сохранения перейдите к уроку.

Отправьте решение, чтобы увидеть результаты тестов.

Задача 4

Найди и исправь: цепочечная индексация вместо прямоугольного среза

Что уже дано

Проверяющая система запускает вашу программу и подаёт данные через stdin. Никаких заранее созданных переменных нет.

В редакторе уже есть сломанный код. Он пытается выполнить три операции над двумерным массивом, но в каждой допущена ошибка: либо применена цепочечная индексация вместо одного выражения, либо перепутан целый индекс со срезом, либо неверно задан шаг.

Что нужно сделать

Найдите и исправьте все три ошибки так, чтобы программа:

  1. Читала из stdin одну строку с четырьмя целыми числами: R, C, r, c.
  2. Создавала двумерный массив arr через np.arange(R * C).reshape(R, C).
  3. Вычисляла три результата и выводила их 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 в указанном порядке.

РешениеPython

Без регистрации. Гостевая проверка не сохраняет прогресс. Для сохранения перейдите к уроку.

Отправьте решение, чтобы увидеть результаты тестов.

Задача 5

Скалярные элементы: значения и shape по целочисленным индексам

Что уже дано

Проверяющая система запускает вашу программу и подаёт данные через stdin. Никаких заранее созданных переменных нет — вы читаете всё сами.

Что нужно сделать

Напишите программу, которая:

  1. Читает из stdin одну строку с четырьмя целыми числами: R, C, i, j.
  2. Создаёт двумерный массив arr через np.arange(R * C).reshape(R, C).
  3. Вычисляет три выражения:
  • e1 = arr[i, j] — элемент строки i, столбца j;
  • e2 = arr[-1, -1] — элемент последней строки, последнего столбца;
  • e3 = arr[0, -1] — элемент первой строки, последнего столбца.
  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 ().

РешениеPython

Без регистрации. Гостевая проверка не сохраняет прогресс. Для сохранения перейдите к уроку.

Отправьте решение, чтобы увидеть результаты тестов.

Задача 6

Значения через срезы с шагом и угловые элементы двумерного массива

Что уже дано

Проверяющая система запускает вашу программу и подаёт данные через stdin. Никаких заранее созданных переменных нет — вы читаете всё сами.

Что нужно сделать

Напишите программу, которая:

  1. Читает из stdin одну строку с четырьмя целыми числами: R, C, sr, sc — число строк, столбцов и шаги среза по строкам и столбцам.
  2. Создаёт двумерный массив arr через np.arange(R * C).reshape(R, C).
  3. Вычисляет и выводит в 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.

РешениеPython

Без регистрации. Гостевая проверка не сохраняет прогресс. Для сохранения перейдите к уроку.

Отправьте решение, чтобы увидеть результаты тестов.

Задачи к уроку

Все задачи курса →

Продолжить с сохранением прогресса

Перейдите к уроку в учебном кабинете.

Перейти к интерактивному уроку