Кладовочка

О разном, чтобы было под рукой онлайн

Шпаргалка по NumPy: массивы, операции, линейная алгебра

NumPy (Numerical Python) — фундамент научного Python. N-мерные массивы (ndarray) в 10–100 раз быстрее списков за счёт векторизованных операций на C. Без NumPy не работают Pandas, Matplotlib, Scikit-learn.

Импорт и создание массивов

import numpy as np

# Из списка
a = np.array([1, 2, 3, 4, 5])          # 1D
b = np.array([[1, 2, 3], [4, 5, 6]])   # 2D

# Нули, единицы, константы
np.zeros((2, 3))           # матрица 2×3 из нулей
np.ones((3, 2))            # матрица 3×2 из единиц
np.full((2, 2), 7)         # матрица 2×2 из семёрок
np.eye(3)                  # единичная матрица 3×3
np.empty((2, 2))           # без инициализации (быстро, но мусор)
np.zeros_like(a)           # как a, но из нулей
np.ones_like(a)            # как a, но из единиц

# Последовательности
np.arange(0, 10, 2)        # [0, 2, 4, 6, 8]
np.linspace(0, 1, 5)       # [0, 0.25, 0.5, 0.75, 1.0]
np.logspace(0, 2, 5)       # [10^0, ..., 10^2] — логарифмическая шкала

# Случайные (новый API, рекомендуется)
rng = np.random.default_rng(seed=42)
rng.random((2, 3))          # равномерное [0, 1)
rng.normal(0, 1, (2, 3))    # нормальное μ=0, σ=1
rng.integers(0, 10, (3, 3)) # целые [0, 10)
rng.choice([1, 2, 3], size=4)  # выборка

# Старый API (до сих пор встречается)
np.random.rand(2, 3)        # равномерное [0, 1)
np.random.randn(2, 3)       # нормальное
np.random.randint(0, 10, (3, 3))  # целые

# Специальные
np.diag([1, 2, 3])          # диагональная матрица
np.fromfunction(lambda i, j: i + j, (3, 3))  # по функции от индексов

Атрибуты и изменение формы

a.shape       # кортеж размеров: (3, 4)
a.ndim        # число измерений: 2
a.size        # общее число элементов: 12
a.dtype       # тип: float64, int32...
a.itemsize    # байт на элемент: 8

# Изменение формы
a.reshape(3, -1)        # -1 = авто-вычисление
a.flatten()             # в 1D (всегда копия)
a.ravel()               # в 1D (view, если возможно)
a.T                     # транспонирование
a.reshape(-1, 1)        # вектор-столбец (n, 1)
a.reshape(1, -1)        # вектор-строка (1, n)
a[:, np.newaxis]        # добавить ось: (3,) → (3, 1)
np.expand_dims(a, axis=0)  # добавить ось
np.squeeze(a)           # убрать оси размера 1

Индексация и срезы

a = np.arange(12).reshape(3, 4)
# [[ 0  1  2  3]
#  [ 4  5  6  7]
#  [ 8  9 10 11]]

# Базовые срезы (view — не копия!)
a[0]          # [0 1 2 3] — первая строка
a[1, 2]       # 6 — элемент (строка 1, столбец 2)
a[:, 0]       # [0 4 8] — первый столбец
a[0:2, 1:3]   # [[1 2] [5 6]] — подматрица

# Fancy indexing (всегда копия)
a[[0, 2]]               # строки 0 и 2
a[:, [1, 3]]            # столбцы 1 и 3
a[[0, 1, 2], [1, 2, 3]]  # элементы (0,1), (1,2), (2,3) → [1, 6, 11]

# Булева индексация
mask = a > 5
a[mask]                  # [6 7 8 9 10 11] — все > 5
a[a > 5] = 0             # присвоение по маске

# Условная замена
np.where(a > 5, a, -1)   # где > 5 — оставить, иначе -1
np.clip(a, 2, 8)         # ограничить диапазоном [2, 8]

Арифметика и операции

# Поэлементные (векторизованные)
a + b, a - b, a * b, a / b    # + - * / — поэлементно
a ** 2                         # квадрат каждого элемента
np.sqrt(a), np.exp(a), np.log(a)
np.sin(a), np.cos(a), np.tan(a)
np.abs(a)

# ⚠️ * — это поэлементное умножение, НЕ матричное!
# Матричное умножение:
A @ B           # оператор @ (Python 3.5+) — рекомендуется
np.dot(A, B)    # классический способ
A.dot(B)        # метод

# Сравнения — возвращают булевы массивы
a > 2          # [False, False, True, ...]
a == b
np.allclose(a, b)  # похожи с учётом погрешности (лучше чем ==)
np.array_equal(a, b)  # точное равенство

# Трансляция (broadcasting) скаляров
a + 5          # прибавить 5 ко всем элементам
a * 2          # умножить все на 2

Broadcasting (трансляция)

Три правила broadcasting:

  1. Если размерности разные — дополнить единицами слева
  2. Размеры совместимы, если равны или один из них равен 1
  3. Размер 1 растягивается до размера другой оси
# (3, 4) + (4,) → OK: (3, 4) + (1, 4) — растягивается по строкам
a = np.ones((3, 4))
b = np.array([1, 2, 3, 4])
a + b
# [[2 3 4 5]
#  [2 3 4 5]
#  [2 3 4 5]]

# (3, 1) + (4,) → (3, 4) — оба растягиваются
col = np.array([[1], [2], [3]])   # (3, 1)
row = np.array([10, 20, 30, 40])  # (4,)
col + row
# [[11 21 31 41]
#  [12 22 32 42]
#  [13 23 33 43]]

# Z-score нормализация через broadcasting
X_norm = (X - X.mean(axis=0, keepdims=True)) / X.std(axis=0, keepdims=True)

# Внешнее произведение через newaxis
outer = a[:, np.newaxis] * b[np.newaxis, :]   # (n, m)
outer = a[:, np.newaxis] * b                   # то же (короче)
outer = np.outer(a, b)                         # ещё короче

# Сетки координат
x = np.linspace(-5, 5, 11)
y = np.linspace(-5, 5, 11)[:, np.newaxis]  # (11, 1)
z = np.sin(np.sqrt(x**2 + y**2))            # (11, 11)

# ogrid — открытая сетка (экономит память)
x, y = np.ogrid[-5:5:11j, -5:5:11j]
# x.shape = (11, 1), y.shape = (1, 11)

Статистика и агрегация

a.sum()                # сумма всех элементов
a.mean()               # среднее
a.std()                # стандартное отклонение
a.var()                # дисперсия
a.min(), a.max()       # минимум, максимум
a.argmin(), a.argmax() # индексы минимума/максимума
np.median(a)           # медиана
np.percentile(a, 50)   # медиана (50-й перцентиль)
np.percentile(a, [25, 50, 75])  # квартили

# По осям (axis)
a.sum(axis=0)          # сумма по строкам → агрегация в столбцы
a.sum(axis=1)          # сумма по столбцам → агрегация в строки
a.max(axis=0)          # максимум каждого столбца

# keepdims — сохранить размерность
a.sum(axis=1, keepdims=True)  # (3,) → (3, 1)

Линейная алгебра (np.linalg)

# Умножение матриц
A @ B                          # рекомендуется

# Транспонирование, след, определитель
A.T                            # транспонирование
np.trace(A)                    # след (сумма диагонали)
np.linalg.det(A)               # определитель (только квадратные)

# Обратная матрица
np.linalg.inv(A)               # A⁻¹ (если det ≠ 0)
np.linalg.pinv(A)              # псевдообратная (Мура-Пенроуза)

# Решение системы Ax = b
x = np.linalg.solve(A, b)      # для квадратной невырожденной
np.allclose(A @ x, b)          # проверка: True

# МНК (метод наименьших квадратов) для переопределённых систем
x, residuals, rank, s = np.linalg.lstsq(A, b, rcond=None)

# Собственные значения и векторы
eigenvals, eigenvecs = np.linalg.eig(A)

# SVD — сингулярное разложение
U, s, Vt = np.linalg.svd(A)
# A = U @ np.diag(s) @ Vt

# QR-разложение
Q, R = np.linalg.qr(A)         # A = Q @ R

# Ранг и норма
np.linalg.matrix_rank(A)       # ранг матрицы
np.linalg.norm(v)              # L2-норма (по умолчанию)
np.linalg.norm(v, ord=1)       # L1-норма
np.linalg.norm(v, ord=np.inf)  # L∞-норма

Объединение и разделение

# Объединение
np.concatenate([a, b], axis=0)   # по строкам (вертикально)
np.concatenate([a, b], axis=1)   # по столбцам (горизонтально)
np.vstack([a, b])                # shortcut: axis=0
np.hstack([a, b])                # shortcut: axis=1
np.column_stack([a, b])          # 1D → столбцы

# Разделение
np.split(a, 3, axis=0)           # на 3 равные части по строкам
np.hsplit(a, 2)                  # горизонтально на 2
np.vsplit(a, 2)                  # вертикально на 2

Полезные функции

# Поиск и фильтрация
np.where(cond, x, y)             # cond → x иначе y
np.unique(a)                     # уникальные значения
np.unique(a, return_counts=True) # + частоты
np.unique(a, return_inverse=True) # + индексы для восстановления
np.isnan(a)                      # булева маска NaN
np.isfinite(a)                   # конечные значения (не NaN, не inf)
np.isinf(a)                      # бесконечности

# NaN-безопасные агрегации
np.nanmean(a), np.nanstd(a), np.nansum(a), np.nanmedian(a)

# Сортировка
np.sort(a)                       # отсортированная копия
a.sort()                         # сортировка на месте
np.argsort(a)                    # индексы для сортировки

# Округление
np.round(a, 2)                   # до 2 знаков
np.ceil(a), np.floor(a)          # вверх/вниз
np.rint(a)                       # до ближайшего целого

# Типы данных
a.astype(np.float32)             # преобразование типа
np.array([1, 2], dtype=np.int64) # задать при создании

Продвинутые техники

einsum — тензорные операции в одной строке

Сокращение от Einstein Summation. Позволяет записать сложные тензорные операции (умножение матриц, свёртки, след, транспонирование) в одной строке без циклов. Быстрее, чем комбинация @ и transpose, и гораздо читаемее для многомерных массивов. Синтаксис: 'входные_индексы->выходные_индексы'.

# Синтаксис: np.einsum('индексы->выход', A, B)

# Умножение матриц
np.einsum('ij,jk->ik', A, B)             # A @ B

# След матрицы
np.einsum('ii->', A)                     # np.trace(A)

# Поэлементное умножение
np.einsum('ij,ij->ij', A, B)             # A * B

# Сумма всех элементов
np.einsum('ij->', A)                     # A.sum()

# Транспонирование
np.einsum('ij->ji', A)                   # A.T

# Внешнее произведение
np.einsum('i,j->ij', u, v)              # np.outer(u, v)

# Batch matrix multiplication
np.einsum('bij,bjk->bik', A3d, B3d)     # batch @

Sliding window view

Скользящее окно по массиву — взгляд на данные через «окошко» фиксированного размера, которое двигается с шагом 1. НЕ копирует данные — возвращает view. Используется для скользящих средних, выделения патчей из изображений, анализа временных рядов.

from numpy.lib.stride_tricks import sliding_window_view

# Скользящее среднее
x = np.arange(10)
w = sliding_window_view(x, window_shape=3)
w.mean(axis=1)                  # [1, 2, 3, 4, 5, 6, 7, 8]

# 2D окна (для изображений)
img = np.random.rand(100, 100)
patches = sliding_window_view(img, (3, 3))   # (98, 98, 3, 3)

C-order vs F-order

Порядок хранения элементов в памяти: C-order (строка за строкой, по умолчанию) — быстрый доступ по строкам. F-order (столбец за столбцом, Fortran-style) — быстрый доступ по столбцам. Для больших матриц разница в скорости может быть 2-5x.

a = np.ones((1000, 1000))
b = np.asfortranarray(a.copy())    # Fortran-order (column-major)

# Доступ по строкам: C-order быстрее
# Доступ по столбцам: F-order быстрее

Структурированные массивы

Массив, где каждый элемент — структура с именованными полями разных типов (как таблица БД или DataFrame без Pandas). Позволяет хранить разнородные данные в одном ndarray и обращаться к полям по имени.

dtype = np.dtype([('city', 'U20'), ('population', 'i4'), ('area', 'f8')])
cities = np.array([
    ('Москва', 12_000_000, 2561.5),
    ('Питер', 5_000_000, 1439.0),
], dtype=dtype)

cities['city']           # ['Москва' 'Питер']
cities['population']     # [12000000 5000000]
cities[cities['population'] > 5_000_000]

np.memmap — работа с файлами на диске как с массивами

Memory-mapped file: массив, который живёт на диске, а в память загружаются только используемые фрагменты. Позволяет работать с массивами больше оперативной памяти (100+ GB) без MemoryError. Чтение и запись как с обычным ndarray.

# Создание
mmap = np.memmap('data.dat', dtype='float32', mode='w+', shape=(1000, 1000))
mmap[:] = np.random.randn(1000, 1000)
mmap.flush()

# Чтение (только нужное в памяти)
mmap = np.memmap('data.dat', dtype='float32', mode='r')
chunk = mmap[0:100]   # загружены только 100 строк

Векторизация vs циклы

Одна и та же операция над всеми элементами массива через NumPy выполняется на C-уровне в десятки раз быстрее, чем Python-цикл. Всегда заменяй for по элементам на векторизованные операции — это главный источник производительности NumPy.

# Медленно (Python loop) — O(n) накладных расходов
result = [x**2 + 2*x + 1 for x in range(1_000_000)]

# Быстро (NumPy) — вычисления на C
x = np.arange(1_000_000)
result = x**2 + 2*x + 1   # ~50x быстрее

Прикладные примеры

# Z-score нормализация
data = np.random.randn(100, 5)
normalized = (data - data.mean(axis=0)) / data.std(axis=0)

# Поиск ближайшего элемента
v = np.array([1, 4, 8, 12, 15])
target = 10
closest = v[np.argmin(np.abs(v - target))]

# N наибольших значений
v = np.array([1, 9, 3, 7, 2, 8])
top_n = v[np.argsort(v)[-3:]]  # последние 3 после сортировки

# Расстояние между векторами
dist_l1 = np.linalg.norm(a - b, ord=1)   # манхэттенское
dist_l2 = np.linalg.norm(a - b, ord=2)   # евклидово

# Косинусное сходство
cos_sim = np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))

# Матрица расстояний
# (n, d) → (n, n) — все попарные расстояния
diff = X[:, np.newaxis, :] - X[np.newaxis, :, :]   # (n, n, d)
dist_matrix = np.linalg.norm(diff, axis=2)           # (n, n)

# Ковариационная и корреляционная матрицы
cov_matrix = np.cov(X, rowvar=False)       # столбцы — переменные
corr_matrix = np.corrcoef(X, rowvar=False)

# One-hot encoding
n_classes = 5
labels = np.array([0, 2, 1, 4])
one_hot = np.eye(n_classes)[labels]  # (4, 5)

Частые ошибки

Проблема Причина Решение
(n,) != (n, 1) Одномерный массив не столбец a.reshape(-1, 1)
a * b вместо A @ B * — поэлементное, не матричное A @ B или np.dot(A, B)
Срез = view, не копия b = a[:5] меняет a b = a[:5].copy()
a == b с float Ошибки округления np.allclose(a, b)
and/or в маске Python-операторы не векторизованы & / `
axis перепутан axis=0 = агрегация по строкам Визуализируй: axis=0 сжимает строки
NaN в данных mean() возвращает NaN np.nanmean(a)
Broadcasting не работает Размеры несовместимы Дополни размерность через None/np.newaxis

Ресурсы

Что дальше


Связанные шпаргалки: Python, Pandas, Matplotlib, Seaborn, SQL для начинающих