NumPy, pandas и SciPy
Массивы, таблицы и численные методы для данных эксперимента: устройство ndarray, индексация, типы, группировка, временные ряды, интегралы, уравнения и подгонка кривых
Вячеслав Федоров
Лекция 10
Разработка и применение ПО
в физических исследованиях
Изложение. Десятая лекция посвящена трём библиотекам, на которых основана обработка данных в Python. NumPy предоставляет многомерный массив чисел одного типа и операции над ним целиком; pandas предоставляет таблицу с именованными столбцами разных типов и индексом, размечающим строки; SciPy предоставляет численные методы — интегрирование, решение дифференциальных уравнений, оптимизацию, подгонку кривых и интерполяцию. Почти весь научный стек Python — SciPy, Matplotlib, scikit-learn — принимает и возвращает массивы NumPy, поэтому от понимания устройства массива зависят и скорость, и правильность расчёта. В рамках лекции рассматриваются устройство массива и способы его создания, индексация и векторные операции, типы элементов и чтение файлов, затем таблицы pandas: отбор, группировка, объединение, пропуски и временные ряды, и в завершение — численные методы SciPy на физических задачах: закон излучения Планка, затухающий осциллятор и измерение эмиттанса пучка. Надпись на титуле — np, общепринятое сокращение имени numpy при импорте; на последнем слайде — pd, такое же сокращение для pandas.
Демонстрации. Слайды с розовым выделением слова «Демонстрация» отмечают места, где изложение прерывается для работы в терминале виртуальной машины lab. На каждом таком слайде проигрывается запись тех же команд; числа в записях получены на этой виртуальной машине с двумя ядрами, Python 3.12, NumPy 2.5, pandas 3.0 и SciPy 1.18, далее называемой стендом. На другой машине иными будут времена, а последние знаки вещественных результатов могут отличаться в пределах округления.
Содержание
От массива чисел до таблиц, временных рядов и численных методов
Изложение. Лекция состоит из шести частей. Первые три посвящены NumPy: устройство массива и его отличие от списка; индексация, срезы и маски, векторные операции по осям, broadcasting и линейная алгебра; типы элементов, переполнение, точность и чтение массивов из файлов. Четвёртая и пятая посвящены pandas: таблица и её первичный осмотр, отбор строк и столбцов и копирование при записи; группировка, объединение таблиц, пропущенные значения и временные ряды. Шестая часть посвящена SciPy: физические константы, интегралы и задача Коши, минимум и корень, подгонка кривых с погрешностями параметров и интерполяция.
Цель лекции
Обрабатывать данные эксперимента операциями над целыми массивами и таблицами и готовыми численными методами, без циклов Python
Сигналы, поля и ансамбли частиц хранятся в массивах NumPy, журналы приборов, каталоги и результаты серий измерений — в таблицах pandas, а интегралы, уравнения движения и подгонку моделей вычисляет SciPy. Цикл по элементам выполняет скомпилированный код. Задача разработчика — записать расчёт как операцию над массивом или вызов готового метода и проверить результат прямым вычислением или точным решением.
Изложение. Физик обращается к языку программирования ради данных: осциллограммы, показания датчиков, координаты и импульсы макрочастиц, результаты серий измерений. Однородные числовые данные удобно хранить в массивах NumPy, где числа одного типа хранятся в памяти подряд, а разнородные записи с именованными полями — в таблицах pandas, где у каждого столбца свой тип. Обе библиотеки построены на одной идее: цикл по элементам выполняется не интерпретатором, а скомпилированным кодом, поэтому расчёт записывается как операция над массивом или столбцом целиком. Такая запись короче и быстрее, но не освобождает от проверки: векторную формулу необходимо сверить с прямым вычислением на небольших данных. Третья библиотека, SciPy, предоставляет поверх тех же массивов готовые численные методы — интегрирование, решение уравнений, оптимизацию и подгонку моделей, — и результат такого метода проверяется сравнением с точным решением или с известной константой.
Научный
стек Python
Обработка и анализ данных эксперимента
scikit-learn
машинное обучение
NumPy: ndarray — числа одного типа подряд в памяти и операции над ними на C
Пояснение
Массив ndarray — общий формат данных: SciPy, Matplotlib и scikit-learn принимают и возвращают массивы NumPy, столбец pandas хранит данные в массиве.
Для физика
Курс: NumPy, pandas и SciPy — эта лекция, графики — следующая, машинное обучение — глава книги «Машинное обучение».
Изложение. Научный стек Python устроен слоями. Основанием стека является NumPy с многомерным массивом ndarray: числа одного типа хранятся подряд, а операции над ними выполняются циклами на C. Над массивом надстроены остальные библиотеки: pandas хранит столбцы таблицы в массивах и добавляет к ним метки строк и столбцов; SciPy реализует численные методы — интегрирование, решение уравнений, оптимизацию, подгонку кривых, обработку сигналов; Matplotlib строит графики по массивам; scikit-learn обучает модели на матрицах признаков. Таким образом, массив служит общим форматом данных, что позволяет передавать результат одной библиотеки в другую без преобразований. В курсе NumPy, pandas и основные численные методы SciPy рассматриваются в этой лекции, построение графиков — в следующей, машинное обучение — в главе книги «Машинное обучение».
Массив NumPy
Список и массив, устройство ndarray, создание массивов
01
Массив NumPy
Индексы и векторы
Типы и файлы
Таблицы pandas
Группы и время
Численные методы
Изложение. В первой части рассмотрим, чем массив NumPy отличается от списка Python и почему эта разница даёт выигрыш и в памяти, и во времени. Далее описываются устройство ndarray — буфер данных, тип элементов, форма и шаги — и способы создания массивов.
Список
и массив
list: ссылки на объекты в куче
объекты float, по 24 байта каждый
ndarray float64: числа подряд в одном буфере
8 байт на число, без заголовков объектов
list ndarray
хранение ссылки на объекты числа подряд
миллион float 32,4 МБ 8 МБ
тип элементов любой у каждого один на массив
цикл интерпретатор скомпилированный
Пояснение
Размер массива задаётся при создании, тип элементов один на весь массив, операции записываются над массивом целиком.
Изложение. Список Python хранит не числа, а ссылки на объекты. Каждое вещественное число списка является отдельным объектом float в куче: 24 байта на заголовок со счётчиком ссылок, указателем на тип и само значение, плюс 8 байт ссылки в массиве самого списка. Оценим объём памяти на стенде. Список из миллиона вещественных чисел занимает 32,4 МБ: 8,4 МБ — массив ссылок с запасом под рост, 24 МБ — объекты. Массив NumPy хранит те же числа подряд в одном буфере, по 8 байт на float64, и занимает 8 МБ — вчетверо меньше. Важнее экономии памяти другое: тип элементов один на весь массив, что позволяет выполнять цикл по элементам в скомпилированном коде без проверки типа каждого значения и без создания объектов-результатов. Отсюда ограничения массива: размер задаётся при создании, а элементы относятся к одному типу. Отсюда же главный приём: операция записывается над массивом целиком, без цикла Python.
Устройство
ndarray
m: форма (3, 4)
строка → +32 байта
столбец → +8 байт
m.T: шаги (8, 32)
буфер: 12 чисел float64, 96 байт подряд
адрес m[i, j] = начало + 32·i + 8·j
Атрибут Значение у m
dtype float64
shape (3, 4)
ndim 2
strides (32, 8)
itemsize 8
nbytes 96
Пояснение
Транспонирование и срезы создают представление: новый заголовок над тем же буфером; reshape — тоже, если позволяют шаги, иначе копирует данные.
Изложение. Массив ndarray представляет собой буфер данных и заголовок, который описывает, как этот буфер читать. Заголовок хранит тип элементов dtype, форму shape — длину по каждой оси — и шаги strides: на сколько байт сдвинуться в буфере при увеличении индекса по каждой оси на единицу. Для матрицы 3×4 из float64, записанной по строкам, переход к следующему столбцу сдвигает адрес на 8 байт, к следующей строке — на 32 байта, поэтому strides равны (32, 8), а адрес элемента m[i, j] вычисляется как начало буфера плюс 32·i плюс 8·j. Транспонирование не переставляет числа в памяти, а меняет шаги местами: у m.T они равны (8, 32). Так же устроены срезы: они создают новое представление, то есть новый заголовок над тем же буфером, и выполняются мгновенно при любом размере массива. Метод reshape тоже возвращает представление, если новую форму можно описать шагами над тем же буфером, а иначе копирует данные: m.T.reshape(12) уже не делит память с m, поскольку столбцы m не расположены в буфере подряд. Атрибуты itemsize и nbytes дают размер одного элемента и всех данных в байтах.
Создание
массивов
np.array([1, 2, 3], dtype=np.float32) np.zeros((3, 4)), np.ones(5), np.full(3, 7.0) np.empty(1000) # память не заполняется np.eye(3), np.diag([1, 2, 3]) np.arange(0, 1, 0.25) # задан шаг np.linspace(0, 1, 5) # задано число точек np.logspace(0, 3, 4) # 1, 10, 100, 1000 rng = np.random.default_rng(42) rng.normal(0.0, 1.0, size=(1000, 3)) m.reshape(2, 6), m.ravel(), m.copy()
Пояснение
Для дробного шага число точек arange зависит от округления; когда известно число точек, применяется linspace.
Пояснение
Случайные числа — генератор default_rng с явным зерном: расчёт воспроизводится. np.random.rand и np.random.seed — устаревший интерфейс.
Важно
np.empty не обнуляет память: в массиве остаются прежние значения.
Изложение. Массив создаётся тремя способами: преобразованием структуры Python, генерацией встроенной функцией и чтением файла. Функция np.array превращает список или вложенные списки в массив; тип элементов выводится из данных или задаётся аргументом dtype. Функции zeros, ones и full создают массив заданной формы, заполненный нулями, единицами или указанным значением; empty только выделяет память и не заполняет её, поэтому в массиве остаются прежние значения, и перед чтением в него необходимо записать все элементы. Функции eye и diag строят единичную и диагональную матрицы. Для равномерных сеток предназначены arange с заданным шагом и linspace с заданным числом точек; с дробным шагом число точек arange зависит от округления, поэтому сетку на отрезке строят через linspace. Случайные числа даёт генератор np.random.default_rng: зерно, переданное явно, обеспечивает воспроизводимость расчёта, а методы генератора normal, uniform, integers задают распределение. Функции np.random.rand и np.random.seed относятся к старому интерфейсу с глобальным состоянием и сохранены для совместимости. Метод reshape меняет форму, ravel вытягивает массив в вектор, по возможности без копирования, а copy создаёт независимую копию данных.
Демонстрация:
устройство массива
01
Миллион чисел списком: размер списка и объектов
02
Тот же миллион массивом: nbytes, dtype, itemsize
03
Матрица 3×4: форма и шаги
04
Транспонирование: шаги и общая память
05
Непрерывность транспонированного массива
Что наблюдается
Список занимает 32,4 МБ, массив — 8 МБ. После транспонирования шаги (32, 8) становятся (8, 32), память общая, данные не скопированы.
root@lab — запись со стенда
Демонстрация. В интерактивном интерпретаторе создаётся список из миллиона вещественных чисел. Размер самого списка по sys.getsizeof вместе с суммой размеров всех объектов-чисел составляет 32 448 728 байт, около 32,4 МБ. Массив из тех же чисел, полученный np.array, занимает по атрибуту nbytes ровно 8 000 000 байт: тип float64, по 8 байт на элемент. Затем создаётся матрица 3×4 из чисел от 0 до 11: форма (3, 4), шаги (32, 8). Транспонированная матрица m.T имеет форму (4, 3) и шаги (8, 32); np.shares_memory подтверждает, что она использует тот же буфер, что и m. Флаг c_contiguous у транспонированной матрицы равен False: её строки не расположены в памяти подряд. Некоторые функции, которым необходим непрерывный буфер, в таком случае сначала копируют данные.
Индексы и векторы
Срезы и представления, маски, оси, broadcasting, линейная алгебра
02
Массив NumPy
Индексы и векторы
Типы и файлы
Таблицы pandas
Группы и время
Численные методы
Изложение. Вторая часть посвящена тому, как выбирать части массива и как записывать вычисления над массивами без циклов: срезы и маски, операции по осям, правила broadcasting и линейная алгебра.
Индексация
и срезы
a = np.arange(24).reshape(4, 6)
выделено a[1:3, ::2]
Важно
a[i][j] создаёт промежуточную строку; в NumPy пишут a[i, j].
a[1, 2] # элемент: 8 a[1] # строка, форма (6,) a[:, 0] # столбец, форма (4,) a[1:3, ::2] # форма (2, 3) a[::-1] # строки в обратном порядке a[..., 0] # то же, что a[:, 0] a[:, None] # новая ось: (4, 1, 6)
Пояснение
Срез — представление: запись в срез меняет исходный массив.
Изложение. Индексы многомерного массива перечисляются через запятую в одних квадратных скобках: a[1, 2] — элемент второй строки и третьего столбца. Запись a[1][2] тоже работает, но сначала создаёт промежуточный массив-строку и затем индексирует его, поэтому в NumPy её избегают. Для каждой оси допускается срез с началом, концом и шагом, как у списка: a[1:3, ::2] выбирает вторую и третью строки и каждый второй столбец. Пропущенные оси подразумеваются целиком, многоточие заменяет все оставшиеся оси, а None, или np.newaxis, вставляет новую ось длины 1. Срез не копирует данные: результат — представление над тем же буфером с другими началом и шагами, поэтому запись в срез меняет исходный массив, а взятие среза у массива любого размера выполняется мгновенно.
Маски
и списки индексов
x = np.arange(10) mask = x > 5 # массив bool x[mask] # [6 7 8 9] x[(x % 2 == 0) & (x > 5)] # [6 8] x[[0, 2, 4]] # [0 2 4] np.where(x > 5, x, 0) # замена без цикла x[x > 5] = 0 # запись в x
Операция Результат
срез x[2:5] представление
маска x[x > 5] копия
список x[[0, 2]] копия
x[маска] = … запись в x
Важно
and, or и not к массивам не применяются: условия соединяют операторами &, |, ~ и заключают в скобки.
Изложение. Кроме срезов, индексом может служить массив. Результатом сравнения x > 5 является массив значений bool той же формы — маска; x[mask] выбирает элементы, для которых маска истинна. Условия соединяются поэлементными операторами &, | и ~, а не словами and, or и not: последние требуют одного логического значения, и для массива из нескольких элементов NumPy сообщает об ошибке ValueError. Заключать сравнения в скобки необходимо, поскольку & связывает сильнее, чем >. Список номеров x[[0, 2, 4]] выбирает элементы в указанном порядке. В отличие от среза, выборка по маске или списку всегда создаёт копию, поскольку выбранные элементы не образуют регулярной решётки в буфере. Присваивание x[mask] = значение, напротив, записывает прямо в исходный массив. Функция np.where выбирает значение из одного или другого массива по условию, что позволяет обойтись без условного оператора в цикле.
Демонстрация:
срезы и копии
02
Выборка по маске и изменение выбранного
04
Общая память: срез с шагом и список индексов
Что наблюдается
Запись в срез обнулила элементы x; изменение выборки по маске x не затронуло; x[x > 5] *= 2 изменило x. Срез с шагом делит память с x, список индексов — нет.
root@lab — запись со стенда
Демонстрация. Массив x содержит числа от 0 до 9. Срез s = x[2:5] является представлением, поэтому присваивание s[:] = 0 обнуляет третий, четвёртый и пятый элементы самого x. Далее x создаётся заново, и y = x[x > 5] выбирает элементы больше пяти; y *= 2 удваивает их в копии, а x остаётся прежним: интерпретатор печатает y со значениями 12, 14, 16, 18 и неизменённый x. Составное присваивание x[x > 5] *= 2 работает иначе: Python выполняет его как чтение выборки, умножение и запись x[x > 5] = результат, и запись идёт в исходный массив. Последняя строка проверяет общую память функцией np.shares_memory: срез с шагом x[::2] делит буфер с x, а выборка по списку индексов x[[0, 2]] — нет.
Векторные
операции и оси
Выражение для data формы (1000, 3) Форма результата
data.mean() () — одно число
data.mean(axis=0) (3,) — по каналам
data.mean(axis=1) (1000,) — по измерениям
data.mean(axis=0, keepdims=True) (1, 3)
data.cumsum(axis=0) (1000, 3)
Для физика
Строки — измерения, столбцы — каналы: axis=0 сворачивает измерения и даёт по числу на канал.
np.sqrt(x), np.exp(-x**2) x * y + 1 np.maximum(x, 0) np.sin(2 * np.pi * f * t) x.sum(), x.min(), x.argmax() x.std(ddof=1) # выборочное
Пояснение
Универсальная функция (ufunc) применяется к каждому элементу в скомпилированном цикле.
Изложение. Арифметические операторы и математические функции NumPy применяются к массивам поэлементно: np.sqrt(x), np.exp(-x**2), x * y + 1 вычисляются в скомпилированном цикле по всем элементам. Такие функции называются универсальными, ufunc. Сводные операции — sum, mean, std, min, max, argmax — без аргумента обрабатывают весь массив и возвращают одно число; накопленная сумма cumsum возвращает массив, а без аргумента предварительно вытягивает данные в вектор. Аргумент axis задаёт ось, вдоль которой выполняется свёртка: для данных формы (1000, 3), где строки — измерения, а столбцы — каналы, mean(axis=0) сворачивает измерения и возвращает три средних, по одному на канал, а mean(axis=1) — тысячу средних по строкам. Аргумент keepdims=True сохраняет свёрнутую ось длины 1, что удобно для последующего broadcasting. Метод std по умолчанию делит на N; для выборочного стандартного отклонения достаточно указать ddof=1.
Демонстрация:
оси и агрегирование
01
Генератор случайных чисел с зерном 42
02
Тысяча измерений трёх каналов: средние 1, 5 и −2, шум 0,1
03
Средние и отклонения по каналам
04
Среднее по строкам: форма результата
05
Хвост распределения и гистограмма первого канала
Что наблюдается
Средние 0,999, 4,997 и −2,004 и отклонения около 0,1 воспроизводят параметры генератора; 25 значений первого канала больше 1,2.
root@lab — запись со стенда
Демонстрация. Генератор default_rng с зерном 42 создаёт тысячу измерений трёх каналов с нормальным шумом: средние каналов 1, 5 и −2, стандартное отклонение 0,1. Массив data имеет форму (1000, 3). Среднее по оси 0 даёт три числа — 0,999, 4,997 и −2,004, — а выборочное отклонение с ddof=1 — около 0,1 для каждого канала; таким образом, параметры генератора воспроизведены. Среднее по оси 1 возвращает массив формы (1000,), по одному числу на измерение. Сумма маски data[:, 0] > 1.2 подсчитывает значения первого канала, превышающие среднее больше чем на два стандартных отклонения: их 25 из тысячи, что согласуется с ожидаемыми 2,3 % для нормального распределения. Функция np.histogram с пятью интервалами возвращает счётчики 45, 263, 453, 212 и 27 и границы интервалов.
Broadcasting
01
С последней оси
формы операндов сравниваются справа налево
02
Равны или 1
ось длины 1 растягивается до длины другого операнда
03
Недостающие оси
слева дописываются оси длины 1
d = r[:, None, :] - r[None, :, :] # (N, N, 3) dist = np.sqrt((d**2).sum(axis=-1)) # (N, N)
Операнды Результат
(4, 3) и (3,) (4, 3)
(4, 1) и (3,) (4, 3)
(N, 1, 3) и (1, N, 3) (N, N, 3)
(4, 3) и (4,) ошибка
Важно
Операнды не копируются, но результат занимает полную форму: (N, N, 3) из float64 при N = 10 000 — 2,2 ГиБ.
Изложение. Broadcasting позволяет выполнять арифметику над массивами разной, но согласованной формы. Формы сравниваются начиная с последней оси: необходимо, чтобы длины по оси совпадали либо одна из них была равна единице, и тогда ось длины 1 растягивается до длины другого операнда без копирования данных. Если у одного операнда осей меньше, к его форме слева дописываются оси длины 1. Поэтому к матрице формы (4, 3) можно прибавить вектор формы (3,) — он прибавится к каждой строке, — а вектор формы (4,) нельзя: последние оси 3 и 4 не согласуются, и NumPy сообщает об ошибке. Чтобы прибавить вектор к каждому столбцу, ему добавляют ось: v[:, None] имеет форму (4, 1). Тот же приём даёт попарные разности координат N частиц: r[:, None, :] - r[None, :, :] имеет форму (N, N, 3). Сами операнды не копируются, но результат и промежуточные массивы занимают полную форму, и при N = 10 000 получим массив разностей из float64 объёмом 2,2 ГиБ. Такие расчёты разбивают на блоки или переписывают через матричное произведение.
Демонстрация:
попарные расстояния
01
Тысяча частиц со случайными координатами
02
Попарные расстояния двумя циклами Python по спискам
03
Те же расстояния через broadcasting
04
Через матричное произведение r @ r.T
Что наблюдается
Циклы — 107,6 мс, broadcasting — 13,1 мс, в 8 раз быстрее; r @ r.T — 2,5 мс, в 43 раза, но с расхождением 3e-8. Массив d занимает 22,9 МиБ.
root@lab — запись со стенда
Демонстрация. Скрипт pairs.py задаёт координаты тысячи частиц в единичном кубе и вычисляет матрицу попарных расстояний тремя способами. Два вложенных цикла Python по спискам координат выполняются за 107,6 мс. Broadcasting — разность r[:, None, :] - r[None, :, :], квадрат, сумма по последней оси и корень — занимает 13,1 мс, в 8 раз быстрее: выигрыш ограничен тем, что промежуточный массив разностей формы (1000, 1000, 3) занимает 22,9 МиБ, и время уходит на запись и чтение памяти. Для третьего способа запишем квадрат разности в виде |ri − rj|² = |ri|² + |rj|² − 2 ri·rj, где последнее слагаемое представляет собой матричное произведение r @ r.T, которое выполняет библиотека BLAS. Он занимает 2,5 мс, в 43 раза быстрее циклов, но теряет точность на вычитании близких чисел: наибольшее расхождение с прямым расчётом, 3·10⁻⁸, приходится на нулевые расстояния частиц до самих себя, где вместо нуля остаётся погрешность округления порядка машинного эпсилона, а корень из неё даёт величину порядка 10⁻⁸. Скрипт подтверждает функцией np.allclose, что циклы и broadcasting совпадают в пределах допуска; вещественные результаты необходимо сравнивать именно так, а не оператором ==.
Линейная
алгебра
Задача Функция
матричное произведение A @ B, np.matmul
система A x = b np.linalg.solve(A, b)
собственные значения np.linalg.eigh, eig
SVD, ранг, норма np.linalg.svd, matrix_rank, norm
спектр сигнала np.fft.rfft, rfftfreq
свёртка по индексам np.einsum('ij,jk->ik', A, B)
Важно
Систему решают solve, а не inv(A) @ b: быстрее и точнее.
Для физика
Матрица перехода через магнитную структуру — произведение матриц элементов: M = Mn @ … @ M1.
Пояснение
Интегрирование, оптимизация и подгонка кривых — в SciPy, шестая часть лекции.
Изложение. Модуль np.linalg предоставляет основные операции линейной алгебры поверх библиотек BLAS и LAPACK. Оператор @ вызывает матричное произведение np.matmul. Систему линейных уравнений A x = b решает np.linalg.solve; вычисление обратной матрицы и умножение на неё медленнее и теряет точность на плохо обусловленных матрицах. Собственные значения и векторы симметричной матрицы находит eigh, произвольной — eig; svd, matrix_rank и norm дают сингулярное разложение, ранг и нормы. Модуль np.fft вычисляет дискретное преобразование Фурье: rfft — для вещественного сигнала, rfftfreq — соответствующие частоты. Функция einsum позволяет записывать свёртки по индексам в нотации Эйнштейна. Для физика типичным примером является линейная оптика пучка: матрица перехода через магнитную структуру равна произведению матриц отдельных элементов. Численные методы — интегрирование, оптимизация, подгонка кривых, специальные функции — собраны в SciPy, которому посвящена шестая часть лекции; модуль scipy.linalg повторяет и расширяет np.linalg.
Типы и файлы
Типы элементов, переполнение и точность, NaN, чтение и запись массивов
03
Массив NumPy
Индексы и векторы
Типы и файлы
Таблицы pandas
Группы и время
Численные методы
Изложение. Третья часть посвящена типам элементов массива: сколько памяти занимает элемент, какой диапазон и точность он обеспечивает и как ошибки переполнения и округления проявляются без сообщений. Завершается часть чтением и записью массивов.
Типы
элементов
dtype Байт Диапазон или точность
bool 1 True, False
int8, uint8 1 −128…127, 0…255
int32 4 ±2,1 млрд
int64 8 ±9,2e18
float32 4 около 7 значащих цифр
float64 8 около 16 значащих цифр
complex128 16 пара float64
<U10 40 строка до 10 символов
object 8 ссылка на объект Python
Пояснение
По умолчанию целые — int64, вещественные — float64; тип задаётся аргументом dtype или методом astype.
Пояснение
NumPy 2: тип результата не зависит от значения числа Python — uint8 + 100 остаётся uint8, uint8 + 300 — ошибка.
Важно
Смешение строк и чисел даёт массив строк: np.array([1, 2.5, '3']) имеет тип <U32.
Изложение. Тип элементов dtype определяет и занимаемую память, и диапазон представимых значений. Целые типы бывают со знаком и без знака и занимают от 1 до 8 байт: uint8 хранит числа от 0 до 255, int32 — до 2,1 миллиарда по модулю, int64 — до 9,2·10^18. Вещественный float32 занимает 4 байта и даёт около семи значащих десятичных цифр, float64 — 8 байт и около шестнадцати. Строки фиксированной длины хранятся по 4 байта на символ, а тип object хранит ссылки на произвольные объекты Python и лишает массив преимуществ NumPy. По умолчанию целые числа получают тип int64, вещественные — float64. В NumPy 2 изменились правила приведения типов: тип результата больше не зависит от значения числа Python, число подстраивается под тип массива. Сумма массива uint8 и числа 100 остаётся uint8, а число 300, не помещающееся в uint8, вызывает ошибку OverflowError; в NumPy 1 тип выбирался по значению, и та же сумма давала uint16. При смешении строк и чисел в одном списке np.array выбирает строковый тип, и все элементы становятся строками.
Переполнение
и точность
>>> np.array([1, 2, 70000], dtype=np.uint16) OverflowError: Python integer 70000 out of bounds for uint16 >>> np.array([1, 2, 70000]).astype(np.uint16) array([ 1, 2, 4464], dtype=uint16) >>> np.array([200, 250], dtype=np.uint8) + 100 array([44, 94], dtype=uint8) >>> np.float32(16777216) + np.float32(1) np.float32(1.6777216e+07)
01
Конструктор
с NumPy 2.0 отвергает число вне диапазона типа
02
astype и арифметика
переполняются без сообщений: остаток по модулю 2^16 и 2^8
03
float32
24 двоичных разряда мантиссы: 2^24 + 1 не представимо
Для физика
Счётчик событий в uint16 или время t += dt во float32 дают ошибку без единого сообщения.
Изложение. Целые типы фиксированной ширины переполняются. С версии 2.0 NumPy отвергает число Python, не помещающееся в тип, уже в конструкторе: np.array([1, 2, 70000], dtype=np.uint16) завершается ошибкой OverflowError. Однако явное приведение astype и арифметика над массивами переполняются без сообщений: 70000 в uint16 превращается в остаток по модулю 2^16, равный 4464, а 200 + 100 в uint8 — в 44. Над отдельными скалярами NumPy выдаёт предупреждение RuntimeWarning, над массивами переполнение не проверяется. Вещественное переполнение даёт inf с предупреждением RuntimeWarning, но и в пределах диапазона вещественные числа ограничены точностью: мантисса float32 содержит 24 двоичных разряда, поэтому 16 777 216 + 1 во float32 остаётся равным 16 777 216. Для физика типичными источниками незаметных ошибок являются счётчик событий в узком целом типе и накопление времени t += dt во float32. Отметим, что каждое такое сложение округляется: миллион шагов по 1 мс дают 991,1 с вместо 1000 с, а начиная с t = 32 768 с шаг в 1 мс перестаёт изменять сумму вовсе.
Демонстрация:
типы и переполнение
01
Число 70000 в uint16: конструктор и astype
03
Предел точности float32
04
arange с дробным шагом и linspace
05
NaN: сравнение и среднее
Что наблюдается
70000 → ошибка или 4464; 200 + 100 в uint8 = 44. 2^24 + 1 во float32 = 2^24. arange(0.5, 0.8, 0.1) даёт 4 точки вместо 3. nan не равен себе, nanmean пропускает его.
root@lab — запись со стенда
Демонстрация. Конструктор np.array с dtype=np.uint16 отвергает число 70000: интерпретатор печатает OverflowError с сообщением о выходе за границы типа. То же число, приведённое методом astype, без сообщения превращается в 4464. Массив uint8 из чисел 200 и 250 после прибавления 100 даёт 44 и 94 — остатки от деления на 256 — без предупреждения. Сумма np.float32(16777216) и np.float32(1) печатается как 1.6777216e+07: единица потеряна. Вызов np.arange(0.5, 0.8, 0.1) возвращает четыре точки, а не три: число точек равно округлённому вверх частному (0,8 − 0,5) / 0,1, которое из-за двоичного округления чуть больше трёх. Последняя точка чуть меньше 0,8 и печатается как 0.8, поэтому правая граница, которую arange по определению исключает, фактически попадает в сетку. linspace(0.5, 0.8, 4) строит четыре точки по построению и включает правую границу точно. Наконец, nan не равен самому себе, mean от массива с nan возвращает nan, а nanmean пропускает пропуск и возвращает 1,0.
Чтение
и запись
Формат Функции Особенности
.npy np.save, np.load двоичный, тип и форма в заголовке
.npz np.savez, savez_compressed несколько массивов в архиве
текст savetxt, loadtxt, genfromtxt читается в редакторе, втрое больше
HDF5, Parquet h5py, pandas большие наборы, частичное чтение
Пояснение
np.load(..., mmap_mode='r') отображает файл в память: читаются только нужные части.
Важно
loadtxt требует полных строк; genfromtxt заменяет пропуски на nan.
Изложение. Основным форматом для собственных массивов является .npy: np.save записывает двоичное содержимое буфера и заголовок с типом и формой, np.load читает его обратно без потери точности. Аргумент mmap_mode='r' не загружает файл целиком, а отображает его в память, и с диска читаются только те части, к которым обращается расчёт; это позволяет обрабатывать массивы больше оперативной памяти. Несколько массивов сохраняются в архив .npz функциями savez и savez_compressed. Текстовые файлы пишут savetxt и читают loadtxt и genfromtxt: такой файл открывается любым редактором, но занимает в несколько раз больше места, а чтение требует разбора каждой строки. loadtxt ожидает полные строки и на пропущенном значении останавливается с ошибкой, genfromtxt подставляет на его место nan. Большие наборы данных хранят в HDF5, к которому обращается пакет h5py, или в столбцовом формате Parquet, с которым работает pandas.
Демонстрация:
текст и двоичный формат
01
Миллион чисел в текст и в .npy: размеры
02
Время чтения: loadtxt и load
03
Текстовый журнал с пропусками
04
loadtxt и genfromtxt на пропусках
Что наблюдается
Текст — 24,3 МиБ, .npy — 7,6 МиБ; loadtxt — 139 мс, load — 0,7 мс. На пропуске loadtxt останавливается с ValueError, genfromtxt подставляет nan.
root@lab — запись со стенда
Демонстрация. Скрипт files.py записывает миллион нормально распределённых чисел в текстовый файл функцией savetxt и в двоичный функцией save. Текстовый файл занимает 24,3 МиБ — около 25 байт на строку в формате с восемнадцатью знаками после запятой, — двоичный 7,6 МиБ, то есть 8 байт на число и заголовок. loadtxt читает текст за 139 мс, load читает .npy за 0,7 мс, в двести раз быстрее, и результаты совпадают. Затем показывается короткий журнал log.csv со столбцами t, U и I, в котором два значения пропущены. Скрипт readlog.py читает его дважды: loadtxt останавливается с ошибкой ValueError «could not convert string '' to float64 at row 1, column 2», а genfromtxt возвращает матрицу, где пропуски заменены на nan. Для таких журналов удобнее pandas, рассматриваемый в следующей части.
Таблицы pandas
Series и DataFrame, чтение и осмотр, отбор строк и столбцов, копирование при записи
04
Массив NumPy
Индексы и векторы
Типы и файлы
Таблицы pandas
Группы и время
Численные методы
Изложение. В четвёртой части перейдём от однородных массивов к таблицам. Рассматриваются устройство Series и DataFrame, чтение таблицы из файла и её первичный осмотр, отбор строк и столбцов по меткам, номерам и условиям и правило копирования при записи, действующее с pandas 3.0.
Series
и DataFrame
df.head(4): индекс слева, у каждого столбца свой тип
df['Age'] — Series: значения столбца и тот же индекс
Пояснение
Series — одномерный массив с индексом; DataFrame — столбцы-Series с общим индексом.
Пояснение
Столбец хранится массивом NumPy или Arrow, поэтому операции над столбцом векторные.
Важно
pandas 3.0: строки по умолчанию имеют тип str, копирование при записи включено всегда.
Изложение. Если NumPy предоставляет массив чисел одного типа, то pandas предоставляет таблицу, у каждого столбца которой свой тип. Одномерный объект Series представляет собой массив значений вместе с индексом, то есть набором меток строк. DataFrame является набором столбцов-Series с общим индексом и именами столбцов. На слайде показаны первые четыре строки таблицы пассажиров «Титаника»: индекс 0, 1, 2, 3, целые столбцы Survived и Pclass, строковый Sex и вещественные Age и Fare; возраст хранится как float64, поскольку он бывает дробным — у младенцев указан в долях года, — а пропуски обозначены NaN. Каждый столбец хранится отдельным массивом NumPy или, для строк и некоторых других типов, массивом Arrow, поэтому операции над столбцом выполняются векторно, как над массивом NumPy. Массив NumPy для функций NumPy и SciPy возвращает метод to_numpy(); атрибут values у строкового столбца в pandas 3.0 даёт не массив NumPy, а массив Arrow. В версии 3.0, вышедшей в январе 2026 года, pandas изменил два умолчания: строковые столбцы получают отдельный тип str вместо object, а копирование при записи действует всегда.
Чтение
и осмотр таблицы
import pandas as pd df = pd.read_csv("titanic.csv") df.shape # (891, 12) df.head(3) # первые строки df.info() # типы и число непустых df.describe() # сводка по числовым df["Pclass"].value_counts() df.isna().sum() # пропуски
Источник Функция
CSV, TSV read_csv(sep=…)
Excel read_excel
Parquet read_parquet
HDF5 read_hdf
SQL read_sql
JSON read_json
Пояснение
Аргументы read_csv usecols, dtype, parse_dates, na_values и nrows позволяют читать только нужное и сразу с верными типами.
Изложение. Таблица читается функцией семейства read_*: read_csv для текстовых таблиц с разделителем, read_excel, read_parquet, read_hdf, read_sql и read_json для других форматов. У read_csv много аргументов, и наиболее важные из них позволяют сократить дальнейшую работу: sep задаёт разделитель, decimal — десятичный знак (файлы, сохранённые в русской локали, обычно читаются с sep=";" и decimal=","), usecols — нужные столбцы, dtype — их типы, parse_dates — столбцы с датами, na_values — обозначения пропусков в файле, nrows — число строк для пробного чтения большого файла. Для первичного осмотра таблицы достаточно нескольких команд: shape даёт число строк и столбцов, head — первые строки, info — тип и число непустых значений каждого столбца, describe — сводку по числовым столбцам, value_counts — частоты значений, isna().sum() — число пропусков по столбцам. Таблица пассажиров «Титаника» из учебника pandas для начинающих (файл doc/data/titanic.csv в репозитории pandas) содержит 891 строку и 12 столбцов; возраст неизвестен у 177 пассажиров, номер каюты — у 687, порт посадки — у двух.
Демонстрация:
осмотр таблицы
01
Чтение titanic.csv и размер таблицы
02
Первые строки пяти столбцов
03
info: типы и число непустых
Что наблюдается
891 строка и 12 столбцов, строки — тип str. Возраст известен у 714 пассажиров, каюта — у 204, порт — у 889; средний возраст 29,7 года.
root@lab — запись со стенда
Демонстрация. В интерактивном интерпретаторе файл titanic.csv читается функцией read_csv; shape показывает 891 строку и 12 столбцов. head(3) по пяти столбцам печатает первые три строки: индекс 0, 1, 2, целые Survived и Pclass, строковый Sex, вещественные Age и Fare. Метод info перечисляет столбцы с числом непустых значений и типом: пять целых столбцов, два вещественных и пять строковых, которые pandas 3.0 хранит в отдельном типе str; таблица занимает в памяти 118,7 КБ. Из числа непустых значений сразу видны пропуски: возраст известен у 714 пассажиров из 891, номер каюты — у 204, порт посадки — у 889. describe по столбцу Age даёт число значений 714, среднее 29,7 года, стандартное отклонение 14,5, минимум 0,42 — ребёнок пяти месяцев — и максимум 80 лет, а также квартили 20,1, 28 и 38 лет.
Отбор
строк и столбцов
df["Age"] # Series df[["Age", "Fare"]] # DataFrame df.loc[78, "Name"] # по метке df.iloc[0, 3] # по номеру df[(df["Sex"] == "female") & (df["Age"] > 30)] df.query("Pclass == 1 and Age < 18") df.sort_values("Age").loc[78] # метка
Индексирует Правая граница среза
loc метки индекса и имена включается
iloc номера позиций с нуля не включается
Важно
После сортировки или фильтрации метки строк не совпадают с номерами: loc[78] и iloc[78] — разные строки.
Изложение. Квадратные скобки с именем столбца возвращают Series, со списком имён — DataFrame. Для отбора строк предназначены два индексатора. loc работает с метками индекса и именами столбцов, и срез по меткам включает правую границу. iloc работает с номерами позиций, как индексация NumPy, и правая граница среза не включается. Условия записываются масками, как в NumPy: сравнение столбца даёт Series из bool, условия соединяются операторами & и | и заключаются в скобки. Метод query принимает условие строкой, где допускаются and и or. Важное следствие меток: сортировка и фильтрация переставляют и удаляют строки, но не меняют их метки. После сортировки по возрасту первой оказывается строка с меткой 803 — самый младший пассажир, — и loc[78] и iloc[78] обращаются к разным строкам. Метод reset_index перенумеровывает строки заново.
Копирование
при записи
# цепочка присваиваний: df не меняется df[df["Age"] > 60]["Fare"] = 0 # одна операция над df: df меняется df.loc[df["Age"] > 60, "Fare"] = 0 sub = df[df["Pclass"] == 1] # копия sub["Fare"] = sub["Fare"] * 2 # df прежний
01
Результат индексации
в pandas 3.0 любой ведёт себя как копия
02
Изменение таблицы
только операцией над ней самой: df.loc[маска, столбец] = …
03
Копия по требованию
столбец и срез делят данные с таблицей до первой записи
Пояснение
Цепочку присваиваний pandas 3.0 отмечает предупреждением ChainedAssignmentError; SettingWithCopyWarning удалён.
Изложение. До версии 3.0 результат индексации в pandas был то представлением, то копией в зависимости от операции, и запись в него то меняла исходную таблицу, то нет; pandas предупреждал об этом SettingWithCopyWarning. С версии 3.0 действует единое правило копирования при записи: любой объект, полученный индексацией или методом, ведёт себя как копия. Таким образом, цепочка присваиваний df[маска]["Fare"] = 0 никогда не меняет df: первый шаг создаёт новый объект, и запись идёт в него. Обнаружив такую цепочку, pandas выдаёт предупреждение ChainedAssignmentError. Для старого кода существен обратный порядок шагов: запись df["Fare"][маска] = 0 до версии 3.0 меняла таблицу, а теперь, как и любая цепочка, её не меняет. Чтобы изменить таблицу, необходимо выполнить операцию над ней самой — df.loc[маска, "Fare"] = 0. Новая таблица sub, отобранная по условию, тоже независима от df: выборка по маске, как и в NumPy, сразу копирует строки. Результаты операций, которые прежде возвращали представление, — столбец df["Fare"], срез строк df[:10], reset_index, rename — данные заранее не копируют: новый объект использует память исходного, пока в один из них не выполнена запись, и только тогда копируются затронутые данные. Отсюда название механизма, Copy-on-Write. Предупреждение SettingWithCopyWarning удалено, и защитные вызовы .copy() после отбора больше не требуются.
Демонстрация:
отбор и запись
02
Сортировка: метки и номера строк
03
Отбор по условию и query
Что наблюдается
После сортировки loc[78] — возраст 0,83, iloc[78] — 15. Цепочка присваиваний вызвала ChainedAssignmentError и не изменила df; присваивание через loc обнулило 22 значения.
root@lab — запись со стенда
Демонстрация. Метка 78 принадлежит пассажиру Caldwell, Master Alden Gates. После сортировки по возрасту первые пять меток — 803, 755, 644, 469 и 78: метки сохранились, а порядок строк изменился, поэтому s.loc[78] даёт возраст этого пассажира, 0,83 года, а s.iloc[78] — семьдесят девятую по возрасту строку с возрастом 15 лет. Отбор по маске находит 103 женщины старше 30 лет, query — 12 пассажиров первого класса младше 18 лет. Затем выполняется цепочка присваиваний df[df["Age"] > 60]["Fare"] = 0: pandas 3.0 печатает предупреждение ChainedAssignmentError и объясняет, что промежуточный объект ведёт себя как копия, а проверка показывает, что у 22 пассажиров старше 60 лет ни одно значение Fare не стало нулём. Та же запись одной операцией df.loc[маска, "Fare"] = 0 изменяет таблицу: нулями стали все 22 значения.
Группы и время
Группировка, объединение и пропуски, временные ряды
05
Массив NumPy
Индексы и векторы
Типы и файлы
Таблицы pandas
Группы и время
Численные методы
Изложение. Пятая часть посвящена операциям, которые позволяют заменить большую часть циклов по строкам: группировке со сводными вычислениями и объединению таблиц по ключу. Затем рассматриваются пропущенные значения, неизбежные в реальных данных, и журналы приборов — показания с меткой времени.
Группировка:
split–apply–combine
разбиение
свёртка
объединение: Series с индексом Pclass
df.groupby("Pclass")["Survived"].mean() g = df.groupby(["Sex", "Pclass"]) g["Survived"].agg(["mean", "size"]) df.pivot_table(values="Survived", index="Sex", columns="Pclass") pd.cut(df["Age"], [0, 12, 18, 40, 80]) df.groupby("Pclass")["Age"].transform("median")
Пояснение
size считает строки группы, count — непустые значения столбца.
Важно
apply с axis=1 — цикл Python по строкам: сначала ищется векторная операция.
Изложение. Группировка устроена по схеме «разбиение — применение — объединение». Метод groupby разбивает таблицу на группы по значениям одного или нескольких столбцов, но сам ничего не вычисляет: он возвращает объект, ожидающий сводной операции. Сводная операция — mean, sum, size, agg со списком функций или с именованными результатами — применяется к каждой группе, а результаты объединяются в Series или DataFrame, индексом которого служат значения ключа. На слайде таблица пассажиров разбита по классу каюты: 216, 184 и 491 пассажир, и среднее по столбцу Survived даёт долю выживших — 0,630, 0,473 и 0,242. Сводная таблица pivot_table раскладывает ту же свёртку по двум ключам — по строкам и по столбцам. Функция pd.cut разбивает непрерывную величину, например возраст, на интервалы, которые затем служат ключом группировки. Метод transform возвращает результат свёртки для каждой строки исходной таблицы, что позволяет заполнить пропущенный возраст медианой его группы: для трёх классов она равна 37, 29 и 24 годам. Методы size и count различаются: size считает строки группы, count — только непустые значения выбранного столбца. Сводные операции, как и арифметика над столбцами, выполняются в скомпилированном коде. Метод apply с axis=1, напротив, вызывает функцию Python для каждой строки и создаёт для неё объект Series, то есть представляет собой цикл Python; к нему прибегают лишь тогда, когда векторной записи не нашлось.
Демонстрация:
группировка
01
Доля выживших по классам
02
Сводная таблица: пол и класс
03
Возрастные группы через pd.cut
04
Заполнение возраста медианой класса
Что наблюдается
Выжили 63, 47 и 24 % пассажиров трёх классов; женщины первого класса — 97 %, мужчины третьего — 14 %. transform заполнил все 177 пропусков возраста.
root@lab — запись со стенда
Демонстрация. Среднее по столбцу Survived внутри групп по классу каюты даёт долю выживших: 0,630 в первом классе, 0,473 во втором и 0,242 в третьем. Сводная таблица pivot_table раскладывает ту же долю по полу и классу: среди женщин выжили 97, 92 и 50 % по классам, среди мужчин — 37, 16 и 14 %. Функция pd.cut делит возраст на интервалы до 12, от 12 до 18, от 18 до 40 и от 40 до 80 лет, и группировка по ним с agg(["mean", "size"]) показывает долю выживших и число пассажиров в каждой группе: 0,58 среди 69 детей, 0,43 среди 70 подростков, 0,39 и 0,37 среди 425 и 150 взрослых; пассажиры с неизвестным возрастом в группы не попадают. Наконец, transform("median") возвращает для каждой строки медианный возраст её класса, и fillna заполняет им все 177 пропусков.
Объединение
и пропуски
m = df.merge(ports, on="Embarked", how="left", validate="m:1", indicator=True) pd.concat([run1, run2], ignore_index=True) df.isna().sum() # пропуски по столбцам df.dropna(subset=["Age"]) # строки без пропуска df["Embarked"].fillna("S") # подстановка значения s.ffill(), s.interpolate() # по соседним точкам
how Результат
inner строки с ключом в обеих таблицах
left все строки левой, NaN без пары
right все строки правой
outer все строки обеих
Важно
Сводки пропускают NaN: средний возраст считается по 714 пассажирам из 891.
Пояснение
Пропуск: NaN в числах и строках, pd.NA в Int64 и boolean, NaT в датах.
Изложение. Метод merge объединяет две таблицы по значениям ключевых столбцов, как соединение в SQL. Аргумент how задаёт, какие строки попадут в результат: inner — строки с ключом в обеих таблицах, left — все строки левой таблицы с NaN в столбцах правой там, где пары нет, right и outer — все строки правой и обеих таблиц. Аргумент validate проверяет кратность ключей: при значении "m:1" дубликат ключа в правой таблице вызывает ошибку MergeError вместо незаметного размножения строк; indicator=True добавляет столбец _merge с источником строки. В примере к пассажирам присоединяется справочник портов посадки ports — таблица из столбцов Embarked и Port с названиями портов для кодов C, Q и S, — и у двух пассажиров без порта столбец Port получает NaN. Функция pd.concat склеивает таблицы одинаковой структуры, например две серии измерений. Пропуск обозначается значением NaN в вещественных и строковых столбцах, pd.NA — в столбцах Int64 и boolean, NaT — в датах. Сумма маски isna по столбцам показывает число пропусков; dropna удаляет строки с пропуском, fillna подставляет значение, ffill переносит предыдущее значение вперёд, interpolate заполняет пропуск по соседним точкам. Сводные операции по умолчанию пропускают NaN: средний возраст вычисляется по 714 значениям из 891, поэтому число непустых значений необходимо проверять до вычисления сводок. Выбор стратегии является решением о данных, а не о коде: заполнение медианой сужает распределение, а удаление строк смещает выборку, если пропуски связаны с другими признаками, — возраст неизвестен у 28 % пассажиров третьего класса и лишь у 14 и 6 % пассажиров первого и второго.
Временные
ряды
log = pd.read_csv("vacuum.csv", parse_dates=["time"], index_col="time") log.resample("5min").agg(["mean", "max"]) log["p"].rolling("1min").mean() log.interpolate(method="time") log.loc["2026-10-02 12:00":"2026-10-02 12:30"]
Метод Что делает
resample равномерная сетка, свёртка по интервалам
rolling окно до текущей точки
shift, diff сдвиг и разность соседних
interpolate заполнение пропусков; по времени — с method='time'
Для физика
Журнал вакуума, тока пучка или температуры: показания с меткой времени, неравномерные и с пропусками.
Изложение. Показания приборов почти всегда сопровождаются меткой времени, проставленной системой сбора. Для работы с метками времени достаточно при чтении указать столбец времени в parse_dates и сделать его индексом: таблица получает индекс DatetimeIndex и операции, недоступные обычным числам. Срез по строкам дат выбирает интервал времени и, как всякий срез по меткам, включает правую границу целиком: строка "2026-10-02 12:30" обозначает всю минуту, и в срез попадают показания до 12:30:59. Метод resample пересчитывает ряд на равномерную сетку с заданным шагом и сворачивает значения внутри каждого интервала: среднее, максимум, число показаний. Метод rolling вычисляет свёртку по скользящему окну, которое по умолчанию оканчивается в текущей точке и охватывает предшествующие показания; окно задаётся числом точек или длительностью, например "1min", и тогда неравномерный шаг показаний учитывается автоматически. Поэтому скользящее среднее запаздывает относительно исходного ряда на половину окна; аргумент center=True располагает окно симметрично вокруг точки. shift и diff сдвигают ряд и вычисляют разности соседних значений, interpolate с method="time" заполняет пропуски линейно по времени. В pandas 3.0 даты, прочитанные из строк, по умолчанию хранятся с микросекундным разрешением, а с наносекундным — лишь когда этого требует запись; псевдонимы интервалов месяца, квартала и года записываются как ME, QE и YE, а прежние M, Q и Y вызывают ошибку.
Демонстрация:
журнал установки
01
Начало файла vacuum.csv
02
Чтение с индексом времени
03
Пауза в записи и пропуски
05
Всплеск давления и интерполяция тока
Что наблюдается
1708 показаний, разрешение — микросекунды; самая длинная пауза — 3 мин 4 с, 17 пропусков тока. Максимум давления 434 нПа приходится на интервал с 12:40.
root@lab — запись со стенда
Демонстрация. Синтетический журнал установки vacuum.csv за один час создан заранее скриптом make_log.py с генератором случайных чисел: время опроса с шагом от 1,5 до 2,5 с, давление p около 40 нПа с всплеском до 440 нПа в 12:40, ток пучка I около 150 мА, медленно убывающий со временем жизни 10 ч, около процента пропущенных показаний тока и трёхминутная пауза записи с 12:20. После чтения с parse_dates и index_col таблица содержит 1708 показаний двух величин, а индекс имеет тип datetime64 с микросекундным разрешением — умолчание pandas 3.0. Разность соседних меток времени показывает самую длинную паузу — 3 мин 4 с, а isna — 17 пропусков тока. resample по 10 минутам с разными свёртками для столбцов даёт максимум давления и средний ток в каждом интервале: всплеск виден в интервале с 12:40 как максимум 434,2 нПа, а ток убывает от 148,8 до 136,9 мА. Скользящее среднее давления по минутному окну достигает максимума в 12:41:01, почти через минуту после начала всплеска: окно оканчивается в текущей точке, и среднее максимально, когда в окне оказывается весь всплеск. Интерполяция по времени заполняет все пропуски тока.
Численные методы
SciPy: константы, интегралы и уравнения, минимум и корень, подгонка кривых, интерполяция
06
Массив NumPy
Индексы и векторы
Типы и файлы
Таблицы pandas
Группы и время
Численные методы
Изложение. Последняя часть посвящена SciPy — библиотеке численных методов, построенной поверх NumPy. Рассмотрим физические константы, вычисление интегралов и решение задачи Коши, поиск минимума и корня, подгонку модели к измерениям с оценкой погрешностей параметров на примере измерения эмиттанса пучка и интерполяцию табличных данных.
Подмодули
и константы
Подмодуль Задачи
constants константы CODATA, единицы
integrate интегралы, задача Коши
optimize минимум, корни, подгонка
interpolate интерполяция таблиц
linalg системы, разложения, спектр
signal, fft фильтры, спектры сигналов
stats, special распределения, спецфункции
Пояснение
Многие методы реализуют алгоритмы QUADPACK, ODEPACK, MINPACK и LAPACK; аргументы и результаты — массивы NumPy.
from scipy import constants as C C.c, C.h, C.k, C.e # точные в СИ C.physical_constants["electron mass"] # (9.1093837139e-31, 'kg', 2.8e-40) E = 13.6 * C.eV # эВ в джоули C.h * C.c / E / C.nano # 91.2 нм C.find("Bohr") # поиск по названию
Для физика
Формулы записываются в СИ, а данные вводятся в привычных единицах через множители eV, nano, milli.
Изложение. SciPy является библиотекой численных методов, построенной поверх NumPy: её функции принимают и возвращают массивы NumPy. Библиотека разделена на подмодули по областям, перечисленные в таблице; linalg повторяет и расширяет np.linalg, например LU-разложением и матричной экспонентой expm. Принято импортировать нужный подмодуль: from scipy import optimize. Многие методы реализуют проверенные десятилетиями алгоритмы QUADPACK, ODEPACK, MINPACK и LAPACK, поэтому, прежде чем программировать численный метод самостоятельно, его ищут в SciPy. Подмодуль constants содержит константы CODATA 2022 в СИ; скорость света, а с 2019 года и постоянные Планка и Больцмана и элементарный заряд точны по определению единиц. Словарь physical_constants возвращает по названию кортеж из значения, единицы и погрешности, функция find ищет названия по подстроке, а множители eV, nano, milli переводят привычные единицы в СИ: энергия 13,6 эВ, умноженная на C.eV, даёт джоули, и длина волны фотона hc/E равна 91,2 нм — граница серии Лаймана.
Закон Планка:
интеграл и корень
from scipy import integrate, optimize # интеграл x^3/(e^x - 1) от 0 до inf равен pi^4/15 f = lambda x: x**3 * np.exp(-x) / (1 - np.exp(-x)) I, err = integrate.quad(f, 0, np.inf) sigma = 2 * np.pi * C.k**4 / (C.h**3 * C.c**2) * I # максимум спектра: (x - 5) e^x + 5 = 0 g = lambda x: (x - 5) * np.exp(x) + 5 x = optimize.brentq(g, 1, 10) # g(1) < 0 < g(10) b = C.h * C.c / (x * C.k) # lambda_max * T = b
Важно
e^x переполняет float64 при x > 709: множитель e^-x в числителе и знаменателе устраняет переполнение.
Пояснение
quad возвращает интеграл и оценку абсолютной погрешности; пределы могут быть бесконечными.
Пояснение
brentq ищет корень на отрезке, на концах которого функция имеет разные знаки; root решает системы.
Изложение. Рассмотрим две задачи, связанные с законом излучения абсолютно чёрного тела. Интегрирование формулы Планка по частотам после замены x = hν/kT приводит к интегралу I = ∫x³/(eˣ − 1)dx от нуля до бесконечности, равному π⁴/15, и постоянная Стефана—Больцмана выражается через него как σ = 2πk⁴I/(h³c²). Функция quad реализует адаптивную квадратуру QUADPACK: она принимает функцию и пределы, в том числе бесконечные, и возвращает значение интеграла и оценку абсолютной погрешности; допуски по умолчанию epsabs и epsrel равны 1,49·10⁻⁸. Подынтегральное выражение записано с множителем e⁻ˣ в числителе и знаменателе: eˣ при x > 709 превышает наибольшее число float64, и NumPy возвращает бесконечность с предупреждением. Для x³/(eˣ − 1) деление на бесконечность даёт верный ноль, однако в близком интеграле ∫x⁴eˣ/(eˣ − 1)²dx возникает отношение двух бесконечностей, и quad возвращает nan. Вторая задача — поиск максимума спектра по длинам волн — приводит к трансцендентному уравнению (x − 5)eˣ + 5 = 0. Функция brentq находит корень методом Брента на отрезке, на концах которого функция имеет разные знаки, и при этом условии сходится гарантированно; по корню x ≈ 4,965 постоянная Вина вычисляется как b = hc/(xk). Системы нелинейных уравнений решает функция root.
Задача Коши:
solve_ivp
from scipy.integrate import solve_ivp def rhs(t, y, g, w0): # y = [x, v] x, v = y return [v, -2 * g * v - w0**2 * x] sol = solve_ivp(rhs, (0, 10), [1.0, 0.0], args=(0.3, 2 * np.pi), t_eval=np.linspace(0, 10, 501), rtol=1e-8, atol=1e-10) x, v = sol.y # форма (2, 501)
Метод Применение
RK45 по умолчанию, нежёсткие задачи
DOP853 высокая точность
Radau, BDF жёсткие задачи
LSODA автоматический выбор, ODEPACK
Важно
По умолчанию rtol = 1e-3 и atol = 1e-6: около трёх верных знаков. Допуски задаются явно.
Изложение. Обыкновенные дифференциальные уравнения решает функция solve_ivp; её название означает задачу с начальными условиями, то есть задачу Коши. Она интегрирует систему первого порядка dy/dt = f(t, y), поэтому уравнение затухающего осциллятора x'' + 2γx' + ω₀²x = 0 сводится к системе: введём вектор состояния y = [x, v] и получим x' = v, v' = −2γv − ω₀²x. Параметры модели передаются через аргумент args, а не через глобальные переменные, что позволяет решать ту же систему с другими параметрами без изменения кода. Аргумент t_eval задаёт моменты, в которые сохраняется решение, а шаг интегрирования метод выбирает сам. Результат содержит моменты t, решение y формы «число компонент × число моментов», число вычислений правой части nfev и признак success. По умолчанию применяется явный метод Рунге—Кутты RK45 с допусками rtol = 10⁻³ и atol = 10⁻⁶: локальная ошибка шага поддерживается меньше atol + rtol·|y|, что соответствует примерно трём верным знакам, поэтому для расчёта, сравниваемого с измерением, допуски необходимо задавать явно. Для высокой точности документация рекомендует DOP853, для жёстких систем, где масштабы времени различаются на порядки, как в цепочках распадов, — неявные методы Radau и BDF; LSODA из библиотеки ODEPACK сам определяет жёсткость и переключается между методами Адамса и BDF.
Демонстрация:
Планк и осциллятор
01
Константа: значение, единица, погрешность
02
Интеграл Планка через quad
03
Переполнение e^x в исходной записи
04
Постоянные Стефана—Больцмана и Вина
05
Осциллятор: допуски solve_ivp
Что наблюдается
Интеграл совпал с точным значением до 3e-15, σ и b — с CODATA. С допусками по умолчанию ошибка решения 2e-3, с rtol = 1e-8 — 1e-8 при 4796 вычислениях правой части вместо 428.
root@lab — запись со стенда
Демонстрация. В интерактивном интерпретаторе импортируются NumPy и подмодули constants, integrate и optimize. Словарь physical_constants по названию electron mass возвращает кортеж из значения 9,1093837139·10⁻³¹, единицы kg и погрешности 2,8·10⁻⁴⁰ — данные CODATA 2022. Функция quad по подынтегральному выражению в устойчивой записи даёт 6,493939402266831 с оценкой погрешности 2,6·10⁻⁹; точное значение π⁴/15 = 6,493939402266828 отличается на 3·10⁻¹⁵, то есть фактическая ошибка почти на шесть порядков меньше оценки. Тот же интеграл в исходной записи x³/(eˣ − 1) вычисляется верно, но интерпретатор печатает предупреждение RuntimeWarning: overflow encountered in exp — при больших x экспонента переполнилась, и верный результат получен лишь потому, что деление на бесконечность даёт ноль. Умножив интеграл на 2πk⁴/(h³c²), получим постоянную Стефана—Больцмана 5,670374419184433·10⁻⁸ Вт/(м²·К⁴); константа C.sigma отличается от неё лишь в шестнадцатом знаке, на уровне округления float64. Функция brentq находит корень x = 4,965114231744276, и постоянная Вина hc/(xk) = 2,8977719551851727·10⁻³ м·К совпадает с C.Wien во всех знаках. Затем интерпретатор закрывается, и скрипт osc.py решает уравнение затухающего осциллятора с γ = 0,3 с⁻¹ и ω₀ = 2π рад/с на интервале 10 с, сравнивая решение с точным в 501 точке. С допусками по умолчанию метод RK45 вычисляет правую часть 428 раз, и наибольшая ошибка координаты составляет 2,2·10⁻³ при начальном отклонении 1. С rtol = 10⁻⁸ и atol = 10⁻¹⁰ ошибка уменьшается до 1,0·10⁻⁸ ценой 4796 вычислений. Метод восьмого порядка DOP853 с rtol = 10⁻¹⁰ достигает ошибки 1,1·10⁻¹⁰ за 2954 вычисления: при высоких требованиях к точности метод высокого порядка оказывается выгоднее.
Минимум
и подгонка
lj = lambda r: 4 * (r**-12 - r**-6) res = optimize.minimize(lj, x0=1.5) res.x, res.fun # [1.1225], -1.0 def model(t, n0, tau): # t, затем параметры return n0 * np.exp(-t / tau) popt, pcov = optimize.curve_fit(model, t, y, p0=(5, 1), sigma=dy, absolute_sigma=True) perr = np.sqrt(np.diag(pcov)) # погрешности
Аргументы Погрешности из pcov
без sigma точки равноценны; масштаб по разбросу остатков
sigma=dy dy — относительные веса; масштаб по χ²
sigma=dy, absolute_sigma=True dy — ошибки измерений; без масштаба
Важно
Без p0 все параметры начинаются с 1: для нелинейной модели p0 задаётся явно.
Изложение. Функция minimize ищет минимум функции одной или многих переменных, начиная с приближения x0; без ограничений по умолчанию применяется квазиньютоновский метод BFGS. Для потенциала Леннард-Джонса V(r) = 4[(1/r)¹² − (1/r)⁶] в безразмерных единицах получим минимум при r = 2^(1/6) ≈ 1,122 глубиной −1. Найденный минимум является локальным: метод сходится к одному из минимумов, обычно ближайшему к x0, и глобального минимума не гарантирует. Функция curve_fit подгоняет параметры модели к данным методом наименьших квадратов, для задачи без ограничений — методом Левенберга—Марквардта из MINPACK. Модель принимает независимую переменную первым аргументом, параметры — следующими; без начального приближения p0 все параметры начинаются с единицы, и нелинейная подгонка может не сойтись. Результатом являются параметры popt и ковариационная матрица pcov, корни диагональных элементов которой дают стандартные погрешности. Смысл погрешностей определяют аргументы sigma и absolute_sigma. Без sigma все точки равноценны, и pcov масштабируется по разбросу остатков. С sigma минимизируется χ² = Σ(rᵢ/σᵢ)², но по умолчанию, при absolute_sigma=False, учитываются лишь относительные значения sigma: pcov масштабируется так, чтобы приведённый χ² был равен единице. При absolute_sigma=True sigma считается стандартными ошибками измерений, и pcov отражает именно их; приведённый χ² около единицы тогда подтверждает согласие модели и оценки ошибок с данными. Таким образом, без absolute_sigma погрешности параметров описывают разброс точек вокруг модели, а не точность прибора. Сами погрешности из pcov основаны на линейном приближении модели вблизи оптимума.
Измерение
эмиттанса
σ²(k) = ε·(R11²·β0 − 2·R11·R12·α0 + R12²·γ0) R11 = 1 − k·l, R12 = l, γ0 = (1 + α0²)/β0
Для физика
Скан: 25 значений k от −1,5 до 1,5 1/м, ошибка размера 20 мкм; минимум модели 0,158 мм при k = 1,08 1/м.
01
Парабола по k
три коэффициента дают ε, β0 и α0
02
Веса после возведения в квадрат
ошибка σ² равна 2σ·dσ и мала у перетяжки
03
Подгонка с весами
sigma = 2σ·20 мкм, absolute_sigma=True
Важно
Преобразование данных, выпрямляющее модель, меняет и веса точек; тот же эффект даёт логарифмирование экспоненты.
Изложение. Эмиттанс — площадь, занимаемая пучком в фазовой плоскости «координата — угол», делённая на π, — является основной мерой качества пучка: он определяет, до какого размера пучок может быть сфокусирован. Углы частиц непосредственно не измеряются, поэтому применяется метод скана квадрупольной линзой: изменяя оптическую силу линзы k = 1/f, снимают размер пучка σ на люминофорном экране. В тонколинзовом приближении линза и промежуток l до экрана дают матрицу перехода с элементами R11 = 1 − kl и R12 = l, и квадрат размера выражается через эмиттанс ε и параметры Твисса β0, α0, γ0 в месте линзы формулой на слайде. Зависимость σ² от k представляет собой параболу, три коэффициента которой однозначно определяют три искомые величины, и задача сводится к подгонке функцией curve_fit. Возведение в квадрат, однако, меняет веса точек: погрешность квадрата равна 2σ·dσ и мала у перетяжки, где пучок узкий и где сосредоточена основная информация об эмиттансе. Подгонка без весов приписывает всем точкам одинаковую погрешность и занижает вклад перетяжки, поэтому пересчитанные погрешности передаются в sigma. Отсюда общее правило: преобразование данных, выпрямляющее модель, меняет и веса точек; тот же эффект даёт логарифмирование экспоненты. Модельный скан совпадает со сканом лекции 11: l = 1,2 м, 25 значений k от −1,5 до 1,5 1/м, ошибка размера 20 мкм, истинные ε = 0,12 мкм, или 0,12 мм·мрад, β0 = 6,94 м и α0 = −1,69; минимум размера по модели, 0,158 мм, достигается при k = 1,08 1/м. На линейном ускорителе инжектора ЦКП «СКИФ» эмиттанс измеряется тем же методом, но размер рассчитывается полной моделью ускорителя, а подгонка выполняется методом COBYQA, не требующим производных.
Демонстрация:
подгонка эмиттанса
01
25 измерений размера с ошибкой 20 мкм
04
absolute_sigma=True: ошибки измерений
05
χ²/dof, корреляция, перетяжка
Что наблюдается
Без весов ε = 0,126 ± 0,045 мкм, с весами и absolute_sigma=True — 0,119 ± 0,009 мкм, в пять раз точнее. χ²/dof = 0,74. Перетяжка: 0,166 мм без весов и 0,157 мм с весами при 0,158 мм у модели.
root@lab — запись со стенда
Демонстрация. Скрипт quadfit.py воспроизводит скан квадрупольной линзой с теми же данными, что и демонстрация лекции 11: генератор случайных чисел с зерном 42 добавляет к расчётным размерам нормальный шум 20 мкм в 25 точках. Модель sigma2 записана для квадрата размера, и curve_fit вызывается трижды с одним начальным приближением. Без весов получим ε = 0,126 ± 0,045 мкм, β0 = 6,58 ± 2,35 м и α0 = −1,61 ± 0,64: погрешность эмиттанса составляет 36 %. С sigma, равным пересчитанной погрешности квадрата 2σ·20 мкм, ε = 0,119 ± 0,007 мкм, β0 = 6,97 ± 0,44 м и α0 = −1,71 ± 0,12. С absolute_sigma=True значения параметров те же, а погрешности больше — 0,009 мкм, 0,51 м и 0,13: они отражают заданную ошибку 20 мкм, а не разброс остатков, и отличаются от предыдущей строки множителем, равным корню из приведённого χ². Сам приведённый χ² равен 0,74 и согласуется с единицей для 22 степеней свободы, поэтому оценка ошибки измерений не противоречит данным. Все три подгонки содержат истинные значения в пределах погрешностей, но взвешенная точнее в пять раз: погрешность эмиттанса около 7 % против 36 %. Коэффициент корреляции ε и β0 равен −0,99: параметры определяются совместно, и для величины, вычисляемой из нескольких параметров, необходима вся матрица pcov, а не только её диагональ. Наконец, размер в перетяжке по подгонке без весов равен 0,166 мм, по взвешенной — 0,157 мм при 0,158 мм у модели: подгонка без весов хуже всего описывает именно точки у перетяжки, которые несут информацию об эмиттансе. В лекции 11 по тем же 25 измерениям строится график с отрезками погрешностей.
Интерполяция
таблицы
from scipy import interpolate V = np.array([0.0, 1.0, 2.0, 3.0, 4.0, 5.0]) # мВ T = np.array([0.0, 25.3, 50.1, 76.4, 101.8, 128.0]) np.interp(2.5, V, T) # 63.25 spl = interpolate.CubicSpline(V, T) spl(2.5) # 63.21 spl.derivative()(2.5) # 26.5 °C/мВ np.interp(6.0, V, T), spl(6.0) # 128.0, 158.1
np.interp CubicSpline
между узлами отрезки, излом гладкая кривая
за таблицей крайнее значение продолжение полинома
производная ступенчатая spl.derivative()
Важно
Интерполяция проходит через каждую точку, включая шум: шумные данные сглаживаются или подгоняются моделью.
Пояснение
interp1d отнесён к устаревшим интерфейсам: вместо него np.interp, CubicSpline, make_interp_spline.
Изложение. Калибровочная кривая или справочная таблица известна лишь в узлах, а значение требуется между ними. Рассмотрим калибровку термопары — шесть пар «напряжение — температура». Кусочно-линейная интерполяция соединяет соседние узлы отрезками; для неё достаточно функции np.interp, которая при 2,5 мВ даёт 63,25 °C. Кубический сплайн CubicSpline представляет собой кусочный кубический полином с непрерывными первой и второй производными: при 2,5 мВ он даёт 63,21 °C, а метод derivative возвращает производную как новую функцию: наклон калибровочной кривой dT/dV при 2,5 мВ равен 26,5 °C/мВ, что соответствует чувствительности термопары около 38 мкВ/°C. Таким образом, сплайн обеспечивает гладкую кривую и её производные, а линейная интерполяция не добавляет к данным ничего, кроме отрезков. За пределами таблицы np.interp возвращает крайнее значение 128,0, а сплайн по умолчанию продолжает крайний полином и при 6 мВ даёт 158,1 °C, поэтому экстраполяция требует осознанного решения. Класс interp1d в документации SciPy отнесён к устаревшим и вне таблицы по умолчанию возбуждает ValueError; для новых программ рекомендуются np.interp, CubicSpline и make_interp_spline. Интерполяция проходит точно через все узлы, включая испорченные шумом, поэтому шумные измерения сглаживаются функцией make_smoothing_spline или описываются моделью через curve_fit.
Итоги
лекции
01
Массив NumPy — числа одного типа подряд; форма и шаги задают, как читать буфер.
02
Срез — представление, маска и список индексов — копия; оси и broadcasting заменяют циклы.
03
Тип элементов определяет память, диапазон и точность; переполнение и округление происходят без сообщений.
04
Таблица pandas — столбцы-массивы с индексом; меняется только операцией над ней самой.
05
groupby, merge, resample и rolling заменяют циклы по строкам; apply — последнее средство.
06
SciPy — проверенные методы; допуски solve_ivp и веса curve_fit задаются явно, погрешности берутся из pcov.
Изложение. Подведём итог. Массив NumPy хранит числа одного типа подряд, а форма и шаги описывают, как читать этот буфер; поэтому срезы и транспонирование не копируют данные, а reshape копирует их, лишь когда шаги не позволяют иначе. Срез — представление, выборка по маске или списку индексов — копия, а запись по маске меняет исходный массив. Операции по осям и broadcasting заменяют циклы, но промежуточные массивы занимают память. Тип элементов определяет память, диапазон и точность, а переполнение целых и потеря точности вещественных происходят без сообщений. Таблица pandas состоит из столбцов-массивов с общим индексом; с версии 3.0 любой результат индексации ведёт себя как копия, и изменить таблицу можно только операцией над ней самой. Группировка, объединение, пересчёт на сетку и скользящие окна заменяют циклы по строкам, а apply остаётся последним средством. SciPy предоставляет проверенные численные методы, но точность и смысл результата задаются явно: допуски solve_ivp по умолчанию дают около трёх верных знаков, погрешности параметров curve_fit зависят от весов и аргумента absolute_sigma, а преобразование данных меняет веса точек.
Упражнения
для самопроверки
01
Энергии частиц
массив .npy: гистограмма, среднее и разброс по осям, отбор по маске
02
Матрицы перехода
произведение матриц элементов канала через @ и сверка с циклом
03
Журнал установки
CSV с датами: resample по минутам, скользящее среднее, поиск пропусков
04
Пассажиры «Титаника»
доля выживших по полу, классу и возрасту; заполнение возраста медианой группы
05
Скан линзой
подгонка σ(k) напрямую с ошибкой 20 мкм и сравнение с подгонкой σ²
Пояснение
Каждый векторный расчёт сверяется с прямым циклом на небольших данных, численный — с точным решением.
Для физика
Проект курса: данные эксперимента читаются в массивы и таблицы, а не в списки.
Изложение. Отдельного задания к лекции нет: упражнения служат самопроверке. Первое — прочитать массив энергий частиц из файла .npy, построить гистограмму, вычислить среднее и разброс по осям и отобрать частицы по условию маской. Второе — вычислить матрицу перехода через последовательность элементов канала как произведение матриц и сверить результат с циклом. Третье — прочитать журнал установки с метками времени, пересчитать его на минутную сетку, сгладить скользящим средним и найти интервалы без показаний. Четвёртое — по таблице пассажиров «Титаника» вычислить долю выживших по полу, классу и возрастной группе и заполнить пропущенный возраст медианой группы. Пятое — подогнать к данным скана квадрупольной линзой модель самого размера σ(k) с погрешностью 20 мкм и сравнить эмиттанс и его погрешность с подгонкой квадратов. Каждый векторный расчёт сверяется с прямым циклом на небольших данных, а численный — с точным решением там, где оно известно.
Источники
Изложение. Основой лекции служат главы книги курса «Научные библиотеки Python», «NumPy и pandas», «SciPy» и приложение «Массивы NumPy». Для самостоятельного изучения рекомендуются документация NumPy, pandas и SciPy, включая учебник pandas для начинающих, описание изменений pandas 3.0 и примеры к каждой функции SciPy, обзорная статья разработчиков NumPy в журнале Nature, книга создателя pandas У. Маккинни «Python for Data Analysis», доступная в открытом виде на сайте автора (третье издание 2022 года описывает pandas 2.0, без копирования при записи и типа str), и книга Н. Ружье «From Python to NumPy» о векторной записи вычислений.
Спасибо за внимание
Вопросы
phys-dev.github.io/soft-dev-book
Изложение. Надпись на последнем слайде — pd, общепринятое сокращение имени pandas при импорте, парное к np на титуле. Лекция завершается ответами на вопросы.