NumPy
NumPy — фундамент научных вычислений на Python: N-мерные массивы, векторизованные операции и быстрая математика без циклов.
1. Что такое NumPy
NumPy добавляет в Python тип ndarray — многомерный массив фиксированного типа. Операции над массивами выполняются в миллионы раз быстрее циклов на чистом Python, потому что работают на уровне C без интерпретатора.
Над NumPy построены Pandas, Scikit-Learn, TensorFlow и почти вся аналитика.
2. Установка
pip install numpyВ дистрибутиве Miniconda/Anaconda numpy уже есть.
import numpy as np
print(np.__version__)Традиционный псевдоним пакета — np.
3. Создание массивов
a = np.array([1, 2, 3]) # из списка
m = np.array([[1, 2], [3, 4]]) # матрица 2x2
z = np.zeros((3, 2)) # нули
o = np.ones(5) # единицы
e = np.eye(3) # единичная матрица
r = np.arange(0, 10, 2) # [0, 2, 4, 6, 8]
l = np.linspace(0, 1, 5) # 5 точек от 0 до 1Форма массива — свойство .shape, число элементов — .size, тип — .dtype.
4. Индексация и слайсы
a = np.array([10, 20, 30, 40])
a[0] # 10
a[-1] # 40
a[1:3] # [20, 30]
a[::2] # [10, 30]
m = np.array([[1, 2, 3], [4, 5, 6]])
m[1, 2] # 6 (строка 1, столбец 2)
m[:, 0] # [1, 4]
m[0, :] # [1, 2, 3]Логическая индексация возвращает элементы по условию:
a[a > 25] # [30, 40]5. Формы и изменение размера
a = np.arange(12)
a.reshape(3, 4) # 3x4
a.flatten() # в 1D
a.T # транспонирование
np.concatenate([a, a]) # склейка
np.stack([a, a], axis=1) # по новой осиreshape(-1) самостоятельно вычисляет размер: a.reshape(-1) — переход к одному измерению.6. Универсальные функции (ufunc)
Арифметика и тригонометрия применяются поэлементно и быстро:
a = np.array([1, 2, 3])
a + 10 # [11, 12, 13]
np.sqrt(a) # корни
np.exp(a) # экспонента
np.sin(a) # синус
np.sum(a) # сумма
np.mean(a) # среднееСтатистика: min, max, std, var, argmax.
7. Трансляции (Broadcasting)
NumPy умеет совмещать массивы разных форм по правилам трансляции:
m = np.arange(9).reshape(3, 3) # 3x3
v = np.array([1, 2, 3]) # 1D
m + v # v "размазан" по строкам
m * 2 # скаляр по всем элементам8. Линейная алгебра
a = np.array([[2, 1], [1, 3]])
b = np.array([8, 13])
np.linalg.det(a) # определитель
np.linalg.inv(a) # обратная матрица
np.linalg.eig(a) # собственные значения
np.linalg.solve(a, b) # решение системыСкалярное и матричное произведение — np.dot(a, b) или оператор @.
9. Маски и where
data = np.array([3, 15, 7, 20, 2])
is_big = data > 10 # булева маска
data[is_big] # [15, 20]
np.where(data > 10, 1, 0) # замена по условиюФильтрация выбросов: data[(data > 5) & (data < 15)].
10. Случайные числа
rng = np.random.default_rng(42) # генератор с seed
rng.random((3, 3)) # равномерное [0,1)
rng.normal(0, 1, 1000) # нормальное распределение
rng.integers(1, 7, size=10) # целыеdefault_rng вместо старого np.random.seed; так результаты воспроизводимы и безопасны.11. NumPy в реальных задачах
Массивы NumPy — «топливо» соседних библиотек:
- Данные в Pandas хранятся поверх NumPy.
- Вход для моделей Scikit-Learn — матрица признаков ndarray.
- Тензоры в PyTorch превращаются в NumPy одной строкой:
tensor.numpy().
for по спискам на векторные операции NumPy — ускорение в 10–100 раз.12. Векторизация вместо циклов
NumPy выигрывает за счёт выполнения операций сразу над всем массивом в C-коде, минуя Python-интерпретатор. Циклы по элементам в Python — самая частая причина медленных программ.
import numpy as np
data = np.linspace(0, 100, 1_000_000)
# Медленно: цикл по элементам
slow = [x * 2 + 1 for x in data]
# Быстро: векторизация
fast = data * 2 + 113. Типы данных и память
Массив NumPy хранит однотипные значения с фиксированным типом dtype — это компактно и предсказуемо. Тип влияет на точность и занимаемую память.
| dtype | Байт | Диапазон |
|---|---|---|
| int32 | 4 | ±2,1 млрд |
| float32 | 4 | семь значащих цифр |
| float64 | 8 | точность по умолчанию |
| bool | 1 | 0 или 1 |
arr = np.array([1.0, 2.0, 3.0], dtype=np.float32)
print(arr.dtype, arr.nbytes) # 12 байт вместо 24
big = np.arange(10_000, dtype=np.int8)
print(big.dtype) # экономим память на маленьких значениях14. Работа с большими данными: memory-mapped файлы
Файлы в сотни гигабайт не влезают в оперативную память. NumPy умеет читать их по частям через memory-mapping — файл и массив ведут себя одинаково, но данные подгружаются с диска лениво.
# Запись большого массива на диск
big = np.memmap('big.dat', mode='w+', dtype=np.float64,
shape=(100_000, 100_000))
big[:1000] = np.random.rand(1000, 100_000)
del big # flush на диск
# Чтение без загрузки целиком
mm = np.memmap('big.dat', dtype=np.float64,
shape=(100_000, 100_000))
print(mm[5000, 2000]) # доступная строка без загрузки всего файла15. Интеграция NumPy и pandas
pandas основан на NumPy: колонка DataFrame — это массив с dtype. Перевод данных между ними почти бесплатен, а векторные операции NumPy напрямую работают с колонками.
import pandas as pd
import numpy as np
df = pd.DataFrame({'x': np.arange(10), 'y': np.random.rand(10)})
# Массив из колонки
arr = df['y'].to_numpy()
# Векторная операция с сохранением индекса
df['normalized'] = (df['y'] - arr.mean()) / arr.std()
print(np.corrcoef(df['x'].to_numpy(), df['y'].to_numpy()))16. Даты и время: datetime64
Тип datetime64 хранит даты компактно и позволяет арифметику с временными интервалами: вычитание даёт timedelta64, а смещение на неделю выполняется добавлением константы. В отличие от Python-объектов datetime, массивы дат в NumPy работают векторно.
import numpy as np
dates = np.array(['2026-09-19', '2026-09-20'],
dtype='datetime64[D]')
week_later = dates + np.timedelta64(7, 'D')
print(dates, week_later)
# рабочие дни до конца месяца (business days)
biz = np.busday_count(dates[0], '2026-09-30')
print(biz)Единицы задаются кодом: D (дни), M (месяцы), Y — только годов, us/ns для индексирования с микро- и наносекундной точностью. Ключевой нюанс: месяцы и годы не кратны суткам, поэтому их смещение может вызвать ошибку округления при преобразовании.
| Код | Единица | Пример |
|---|---|---|
| D | день | дат индексации без времени |
| M | месяц | агрегации по периодам |
| ns | наносекунда | логи событий и сенсоров |
| us | микросекунда | высокочастотные временные ряды |
17. Fancy-индексация и сортировка массивов
Fancy-индексация обращается к массиву по списку индексов или булевой маске. Первая копирует данные в новом порядке (важно для понимания, это не срез), вторая выбирает элементы по условию — мощный инструмент фильтрации без циклов.
import numpy as np
a = np.array([10, 20, 30, 40])
idx = np.array([3, 0, 2]) # перестановка
print(a[idx]) # [40 10 30]
mask = a > 20
print(a[mask]) # [30 40]
# порядок по двум ключам
people = np.array(['Аня', 'Боря', 'Витя'])
ages = np.array([30, 25, 25])
order = np.lexsort((people, ages))
print(order, people[order])Сортировка по значению — np.sort (новая копия) или a.sort() (на месте). Когда нужно вернуть порядок индексов для повторного применения, используйте np.argsort, а для удаления дубликатов — np.unique.
- Маски можно комбинировать логическими операторами: (a > 10) & (a < 40).
- Для поиска позиции первого элемента с условием — np.argmax(mask).
- Сложные ключи удобнее сортировать через lexsort в порядке от последнего ключа.
18. View vs copy: как не удвоить память
Работа с большими массивами упирается в память. Срезы и многие операции возвращают view — представление, разделяющее буфер данных с исходником. Копия создаётся только при явном copy() или при изменении упаковки/шага. Проверить, делятся ли массивы памятью, помогает np.shares_memory.
import numpy as np
m = np.arange(100_000).reshape(100, 1000)
row = m[10] # view: без копирования
copy_row = m[10].copy() # явная копия
print(np.shares_memory(m, row)) # True
print(np.shares_memory(m, copy_row)) # False
row[0] = -1
print(m[10, 0]) # -1: исходник изменился!Чтобы ускорить тяжёлые расчёты, либо пишите на чистом NumPy (векторизация вместо циклов), либо переключайте критичные функции на Numba/численные библиотеки. Перед передачей данных в pandas или другую библиотеку решайте сознательно: нужна ли независимая копия или разделённое представление достаточно.
19. Численная устойчивость и пропуски (NaN)
На больших данных встречаются пропуски и крайние значения. NumPy зарезервировал NaN и Inf для таких случаев, а агрегаты с префиксом nan (nanmean, nanstd) игнорируют пропуски. Сравнение значений «в лоб» здесь не работает: nan == nan даст False, поэтому проверка делается через np.isnan.
import numpy as np
data = np.array([1.0, np.nan, 3.0, 5.0])
print(np.mean(data)) # nan: пропуск портит среднее
print(np.nanmean(data)) # 3.0, пропуск пропущен
print(np.isnan(data).any()) # True
# устойчивый softmax: вычесть максимум перед exp
x = np.array([1000.0, 1001.0])
z = x - x.max()
print(np.exp(z) / np.exp(z).sum())Переполнение — тихая беда: np.exp(1000) вернёт inf, а не исключение. Правило практика: всякий раз, когда в формуле есть экспонента или деление на близкую к нулю величину, стабилизируйте вычисления — нормируйте, сдвигайте или логарифмируйте.
- Проверяйте пропуски ДО анализа: data[data != data] — скоростной способ найти NaN.
- Деление на ноль контролируйте np.errstate(divide='ignore').
- Для целых массивов NaN недоступен — конвертируйте в float при работе с пропусками.
Что дальше
Дальше — таблицы Pandas и визуализация Matplotlib. Всё это удобно собирать в Jupyter Notebook.