Семестр 3 · Модуль 3 · Неделя 8
SciPy: численные расчёты и честная визуализация
quad · odeint/solve_ivp · minimize · curve_fit · interp1d · scipy.stats ·
принципы честной визуализации
2 ч теории. Данные — reaction.csv (25 точек
«эксперимента»). После лекции — live-coding и задачи семинара.
Прежде чем начать: подумайте, как бы вы «проверили закон»
по точкам с шумом — например, зависимость скорости реакции от температуры.
Подбор кривой — как физики проверяют законы
- По экспериментальным точкам с шумом восстановить параметры модели —
это метод Методик вычислений и Исследования операций.
- Так Аррениус проверял зависимость скорости реакции от температуры
(экспонента), так подбирают затухание маятника, рост популяции.
- Интегралы и дифференциальные уравнения — база следующих курсов
численных методов.
Что за «модель» вы бы выбрали для данных, которые сначала
быстро падают, потом выходят на плато?
scipy.integrate.quad: определённый интеграл
from scipy.integrate import quad
from scipy.stats import norm
quad(lambda x: x**2, 0, 1)
(0.33333333333333337, 3.7e-15)
quad(norm.pdf, -np.inf, np.inf)
(0.9999999999999998, 1.0e-08)
quad(f, a, b) возвращает кортеж
(значение, оценка ошибки).
- ∫₀¹ x² dx = 1/3 — сверили с аналитикой; площадь под нормальной кривой ≈ 1 —
свойство функции плотности.
- Всегда показывайте оба числа: без ошибки значение неполно.
Как проверить, что quad посчитал правильно, если аналитического
ответа нет?
odeint / solve_ivp: решение дифференциальных уравнений
from scipy.integrate import odeint
def decay(y, t, k):
return -k * y # y' = -k·y
t = np.linspace(0, 10, 50)
sol = odeint(decay, 5.0, t, args=(0.3,))
sol[[0, 25, -1], 0]
[5.0 1.082 0.249]
- Функция правой части:
dy/dt = f(y, t, ...) — для
экспоненциального распада y' = -k·y.
- Аналитически y(10) = 5·exp(-0.3·10) ≈ 0.249 — совпало.
solve_ivp — современное API с dense_output.
Что изменится в решении, если в модели поставить
+k·y? Когда это реалистично?
scipy.optimize.minimize: поиск минимума
from scipy.optimize import minimize, rosen
minimize(rosen, x0=[0, 0], method='BFGS')
fun: 2.84e-11
x: [1.0 1.0]
- Минимизация многомерной функции — «двигатель» curve_fit.
- Функция Розенброка имеет минимум в точке (1, 1) — метод BFGS нашёл его
с точностью до 10⁻¹¹.
- Методы:
Nelder-Mead (без производных), BFGS
(градиентные).
Что произойдёт, если стартовать minimize из
точки (0, 0), а не из окрестности минимума?
curve_fit: подбор параметров модели к данным
from scipy.optimize import curve_fit
def model(t, A, lam, c):
return A * np.exp(-lam * t) + c
popt, pcov = curve_fit(model, x, y, p0=[5.0, 0.3, 0.5])
perr = np.sqrt(np.diag(pcov))
resid = y - model(x, *popt)
rmse = np.sqrt(np.mean(resid**2))
A = 4.94 ± 0.10
λ = 0.30 ± 0.02
c = 0.53 ± 0.09
RMSE = 0.121
curve_fit — метод наименьших квадратов; возвращает
(popt, pcov), где pcov — ковариационная матрица.
perr = sqrt(diag(pcov)) — ошибки параметров; параметры
близки к истинным (A=5, λ=0.3, c=0.5).
- Остатки (справа) колеблются вокруг нуля без тренда — модель «не врет».
Зачем показывать остатки, если модель «красиво легла» на данные?
Разумный p0: почему без него curve_fit «застревает»
curve_fit(model, x, y, p0=[1.0, 1.0, 1.0])
A = 4.94, λ = 0.30, c = 0.53 RMSE = 0.121 (хорошо)
curve_fit(model, x, y, p0=[0.0, 0.0, 0.0])
A = -19773, λ ≈ 0.0, c = 19777 RMSE = 0.503 (застрял)
- Нелинейная подгонка решает задачу минимизации — она может сойтись в
ложном локальном минимуме.
- Стартовые значения оцениваются по данным, а не угадываются:
при t=0 значение ≈ A+c ≈ 5.5, «хвост» при t=10 ≈ c ≈ 0.5.
- Если параметры «разъехались» или RMSE большой — проверьте p0.
Как оценить p0 для модели затухания, не зная истинных
параметров? Что подсказывает форма данных?
interp1d: интерполяция по узлам
from scipy.interpolate import interp1d
f_lin = interp1d(xn, yn, kind='linear')
f_cub = interp1d(xn, yn, kind='cubic')
f_lin(2.5); f_cub(2.5)
1.35 1.44
- Интерполяция проходит через узлы и не даёт параметров —
в отличие от curve_fit.
- Кубическая гладкая, линейная — ломаная; в середине интервала они близки.
- Применение: восстановить функцию по табличным значениям (сенсор, замеры).
Когда нужен interp1d, а когда curve_fit? Что «знает»
интерполятор о физике вашей задачи?
Экстраполяция: правдоподобный «мусор»
f_lin(6.5) # вне диапазона узлов
ValueError: A value (6.5) in x_new is above the
interpolation range's maximum value (5).
interp1d(xn, yn, kind='linear',
fill_value='extrapolate')(6.5)
-0.3 # отрицательное при положительных данных — «мусор»
- По умолчанию
interp1d запрещает выход за
пределы диапазона — это защита от глупой ошибки.
- С
fill_value='extrapolate' он «продолжает» кривую — и
получаются правдоподобные, но бессмысленные значения.
- Правило: без экстраполяции; если предсказание нужно —
используйте модель с физикой (curve_fit) и оговаривайте границы.
Почему экстраполяция линейным интерполятором дала −0.3,
хотя все данные положительные?
scipy.stats: описательная статистика и тесты
from scipy.stats import ttest_ind, pearsonr
ttest_ind(ages_male, ages_female)
TtestResult(statistic=-1.118, pvalue=0.271)
pearsonr(df['Fare'], df['Survived'])
(-0.218, 0.176)
ttest_ind — гипотеза о равенстве средних двух выборок:
p = 0.27 > 0.05 → различия средних незначимы (по нашим данным).
pearsonr — корреляция: r ≈ −0.22, p ≈ 0.18 — связь слабая
и статистически незначимая.
- p-value — мера, а не приговор; корреляция ≠ причинность.
Что значит «p = 0.27» простыми словами? Можно ли по этим
данным утверждать, что пол влияет на возраст?
Честная визуализация: масштаб осей
- Слева ось Y обрезана (118–128): рост ~2% выглядит как «рывок в 3 раза».
- Справа ось с нуля: виден скромный, но честный рост.
- Правила: бары с нуля; не обрезать ось без маркера разрыва
(
//); подписывать единицы; не прятать выбросы.
В каких отраслях обрезка оси Y — обычная практика
«продажи продают»? Почему это этическая проблема для аналитика?
Ещё манипуляции: 3D-эффекты, размер маркера, базы
- 3D/объём у bar-диаграмм искажает восприятие (верх
столбца читается по-разному).
- Размер маркера должен быть пропорционален данным, а
не «ради красоты».
- Честные проценты: всегда уточняйте базу («вырос на
50%» — от чего?).
- Не скрывайте выбросы и «нулевые» значения.
Почему «объёмный» столбец в 8 раз «тяжелее» плоского,
хотя высота та же? Как это обманывает глаз?
Типичные ошибки недели 8
- curve_fit без разумного p0 — застревание в локальном
минимуме, параметры «мусорные» (RMSE большой).
- Экстраполяция interp1d за пределы данных — правдоподобные,
но бессмысленные значения.
- Путаница «подобрать параметры» (curve_fit) и
«интерполировать» (interp1d).
- Полином высокой степени «для красоты» — переобучение, кривая скачет.
quad вызывают с векторной функцией без понимания, что
подынтегральная функция должна быть скалярной.
- Остатки не показаны — нельзя оценить, «врёт» ли модель систематически.
- Вывод «корреляция → причинность» без оговорок.
Студент подобрал полином 5-й степени, и он «идеально»
прошёл через все точки. Почему это плохая модель?
Вопросы для проверки понимания
- Что возвращает
quad и как проверить точность?
- Как подобрать параметры экспоненциальной модели к данным с шумом?
- Чем интерполяция отличается от экстраполяции и почему последняя опасна?
- Какой график «вводит в заблуждение» масштабом и почему?
- Чем
ttest_ind отличается от pearsonr?
Ответьте письменно за 3 минуты — это мини-самооценка перед
семинаром.
Переход: на семинаре вы посчитаете интегралы, решите ОДУ,
подберёте кривую к reaction.csv и «почините» манипулирующий график.
Что дальше
- Семинар недели 8: три уровня задач — «Интегрирование +
статистика» (базовый/стандартный), «Подбор кривой + интерполяция»
(стандартный), «ОДУ + честный график» (продвинутый), «экспонента vs
полином» (challenge).
- ДЗ недели 8: аппроксимировать экспериментальные данные
моделью, обосновать выбор, показать остатки (autograder:
fit_model).
- К концу недели вы умеете интегрировать, решать ОДУ,
подбирать параметры модели и строить честные графики.
Готовы? Откройте семинар-8.md, возьмите reaction.csv и
подберите первую модель.