Семестр 3 · Модуль 1 · Неделя 2
Индексация, broadcasting, ufuncs
view и копия · fancy indexing · boolean masking · правила broadcasting ·
универсальные функции · агрегации с осью
Мотивация недели: нормализация признаков перед
обучением ML-модели — это ровно то, что делает каждая ML-библиотека перед
fit. Одна строка NumPy заменит 10 строк циклов.
Индексация и срезы
a = np.arange(12).reshape(3, 4)
a[1, 2] # → 6 (строка 1, столбец 2)
a[-1, -1] # → 11 (последний элемент)
a[:, 1] # весь 2-й столбец → [1 5 9]
a[::-1] # реверс строк
a[0:2, 0:2] # блок 2×2
Ключевой момент: срез возвращает view —
вид на те же данные, а не копию. Изменение view меняет исходный массив.
Срез строк из 1-го семестра работал для строк — здесь то же,
но возвращается view. Как получить настоящую копию?
View vs копия — главная ловушка недели
# срез = view
a = np.arange(12).reshape(3, 4)
view = a[0:2, 0:2]
view[0, 0] = 99
print(a[0, 0]) # 99 — исходный изменён!
# .copy() = копия
a = np.arange(12).reshape(3, 4)
b = a[0:2, 0:2].copy()
b[0, 0] = 99
print(a[0, 0]) # 0 — исходный не тронут
Правило: срезы — view; fancy indexing (списки индексов) —
копия; явная копия — .copy().
Почему изменение «под-массива» меняет исходный массив? Когда это удобно?
Fancy indexing — выборка по спискам индексов
a = np.arange(12).reshape(3, 4)
a[[0, 2], [1, 3]]
array([ 1, 11]) # a[0,1] и a[2,3]
В отличие от срезов, fancy indexing возвращает копию.
Применения: перестановка строк, случайная выборка строк.
a[[2, 0, 1]] # строки в новом порядке
array([[ 8, 9, 10, 11],
[ 0, 1, 2, 3],
[ 4, 5, 6, 7]])
Как выбрать элементы a[0,1] и a[2,3] одной операцией?
Boolean masking — выбор по условию
a = np.arange(12).reshape(3, 4)
a[a > 5]
array([ 6, 7, 8, 9, 10, 11])
Маска — булев массив той же формы; a[mask] возвращает
элементы, где маска истинна.
# комбинирование условий: & | ~ со СКОБКАМИ
(a > 3) & (a < 7)
array([[False, False, False, False],
[ True, True, True, False],
[False, False, False, False]])
Ошибка: a > 3 and a < 7 → ValueError.
Для массивов нет логического and — только поэлементные &.
Что нужно поставить в (a > 3) _ (a < 7), чтобы получить пересечение?
Присваивание по маске и np.where
# замена по маске
a = np.arange(12).reshape(3, 4)
a[a > 5] = -1
array([[ 0, 1, 2, 3],
[ 4, 5, -1, -1],
[-1, -1, -1, -1]])
# np.where(cond, x, y) — поэлементный выбор
np.where(a > 5, a, -1)
Запомните: маска a[a > 5] возвращает значения,
а np.argwhere(cond) — индексы элементов.
Чем a[a > 3] отличается от np.where(a > 3, a, 0)?
Broadcasting — «растягивание» размерностей
Broadcasting — NumPy расширяет меньший массив до формы
большего без копирования данных, чтобы выполнить поэлементную
операцию.
Правило сравнения форм справа налево:
- равные размерности — совместимы;
- одна из размерностей равна 1 — она расширяется;
- не совпали и ни одна не 1 →
ValueError.
Совместимы ли формы (3, 1) и (1, 4)? А (4,) и (3, 4)?
Broadcasting: примеры
# (3,1) + (1,4) → (3,4): «внешнее» суммирование
np.arange(3).reshape(-1, 1) + np.arange(4)
array([[0, 1, 2, 3],
[1, 2, 3, 4],
[2, 3, 4, 5]])
# (5,) + (3,5): вычитание среднего по столбцам
x - x.mean(axis=0) # форма (3,5) - (5,) → (3,5)
Строка-«центр» формы (5,) расширяется до
(3, 5) — это нормализация признаков перед обучением модели.
Какой формы будет результат np.arange(3).reshape(-1,1) + np.arange(4)?
Broadcasting: несовместимые формы
# (4,) + (3,4) — совместимо (последняя 4=4)
np.zeros((3, 4)) + np.arange(4)
array([[0., 1., 2., 3.],
[0., 1., 2., 3.],
[0., 1., 2., 3.]])
# (3,) + (3,4) — НЕсовместимо: 3 vs 4
np.zeros((3, 4)) + np.arange(3)
ValueError: operands could not be broadcast
together with shapes (3,4) (3,)
Проверяйте .shape перед каждой операцией с
матрицами разной формы — это нормальный рабочий процесс, не стыд.
Почему (3,) не расширяется до (3,4)?
Пример: нормализация по столбцам через broadcasting
x = np.array([[0., 2., 10.],
[4., 2., 20.]])
x_min = x.min(axis=0) # → [0. 2. 10.]
span = x.max(axis=0) - x_min # → [4. 0. 10.]
result = (x - x_min) / span # (2,3) - (3,) → (2,3)
result
array([[0., nan, 0.],
[1., nan, 1.]]) # столбец с span=0 → NaN
Константный столбец (span = 0) даёт nan —
edge case, который нужно обработать через np.where (задача семинара).
Почему при вычитании (2,3) - (3,) форма не ломается?
ufuncs — универсальные функции
ufunc — функция, действующая поэлементно над массивами:
np.add, np.multiply, np.exp,
np.log, np.sqrt, np.sin,
np.abs, np.sign.
np.sqrt(np.arange(4))
array([0. , 1. , 1.41421356, 1.73205081])
np.exp([0, 1, 2])
array([1. , 2.71828183, 7.3890561 ])
Методы: reduce (свёртка), accumulate
(накопление), outer (внешнее произведение).
Что вернёт np.add.reduce([1,2,3,4])?
Агрегации и параметр axis
m = np.array([[1, 2, 3], [4, 5, 6]])
m.sum(axis=0) # → [5 7 9] вдоль столбцов
m.sum(axis=1) # → [6 15] вдоль строк
m.mean(axis=0) # → [2.5 3.5 4.5]
Как не путать оси: axis=0 схлопывает
«вертикаль» (результат — по столбцам), axis=1 — «горизонталь»
(результат — по строкам).
Другие: np.std, np.var,
np.min/max, np.argmin/argmax, np.unique,
np.count_nonzero.
Какой shape у результата m.sum(axis=0) для матрицы (3,4)?
Пример: таблица умножения через broadcasting
np.arange(1, 11).reshape(-1, 1) * np.arange(1, 11)
array([[ 1, 2, 3, ...],
[ 2, 4, 6, ...],
[ 3, 6, 9, ...],
...
[ 10, 20, 30, ..., 100]])
# форма (10,1) * (10,) → (10,10)
Одна операция вместо двойного цикла — наглядный пример
силы broadcasting.
Какой формы результат и чему равен элемент [9, 9]?
Типичные ошибки недели 2
a > 3 and a < 7 — and не работает с
массивами; нужно & и скобки.
- Путаница
axis=0 / axis=1 (по какой оси
схлопывается).
- Неожиданная мутация исходного массива из-за view — там, где ожидали
копию.
- Broadcasting: думают, что NumPy «сам разберётся», не проверяя формы —
получают
ValueError.
np.where vs маска: np.where возвращает
значения, np.argwhere — индексы.
Перед какой операцией всегда полезно вывести .shape?
Вопросы для проверки понимания
- Что произойдёт при broadcasting массивов форм
(3,1) и (1,4)?
- Возвращает ли slice копию или view? Как получить копию?
- Как найти индексы элементов, больших порога, и заменить их на порог?
- Чем
a[a > 3] отличается от np.where(a > 3, a, 0)?
Переход: на семинаре — нормализация данных, изображение
как массив, маскирование выбросов и broadcasting-упражнения.
Что дальше
- Семинар недели 2: «Индексация и срезы» (базовый),
«Нормализация и маскирование выбросов» (стандартный),
«Broadcasting + агрегация» (продвинутый).
- ДЗ недели 2:
normalize и
standardize без циклов (autograder, task-02).
- К концу недели вы сможете выбирать и менять элементы
массивов, применять broadcasting и ufuncs с правильной осью.
Готовы? Задача семинара: нормализация данных — edge case
с константным столбцом ждёт вас.