Воспроизводимая фигура: скрипт вместо редактора
Фигура, собранная кодом, переживает рецензию, новые данные и собственную забывчивость автора.
Воспроизводимая фигура — это график, который полностью порождается скриптом из исходных данных, так что любой может пересобрать его той же командой и получить идентичный результат.
Почему не редактор
Соблазнительно «доделать» график в редакторе: подвинуть подпись, стереть выброс, подкрасить линию. Проблема в том, что эти правки нигде не записаны. Через полгода рецензент попросит изменить ось или добавить точку — и вы не сможете повторить ручные движения. А если данные обновились, всю ручную работу придётся делать заново, рискуя внести новую ошибку. Ручная правка фигуры — это технический долг, который всегда возвращается.
Скрипт же — это рецепт. Изменились данные — перезапустил, получил обновлённую фигуру за секунду. Рецензент спросил «а что если по-другому?» — поменял одну строку. Кто-то усомнился — прислал ему скрипт, он воспроизвёл сам. Это и есть научный стандарт.
Анатомия скрипта фигуры
Хороший скрипт читается сверху вниз как последовательность шагов: загрузка данных → расчёт → построение → оформление → сохранение в файл. Вот скелет на matplotlib (он рисует, поэтому не запускается в браузере — это образец для копирования):
import matplotlib.pyplot as plt
import numpy as np
# 1. данные (из файла, а не из головы)
data = np.loadtxt("experiment.csv", delimiter=",")
x, y = data[:, 0], data[:, 1]
# 2. расчёт (тренд)
coef = np.polyfit(x, y, 1)
trend = np.poly1d(coef)
# 3. построение
fig, ax = plt.subplots(figsize=(4, 3))
ax.scatter(x, y, s=15, alpha=0.7, label="измерения")
ax.plot(x, trend(x), "r-", label=f"тренд: {coef[0]:.2f}x+{coef[1]:.2f}")
# 4. оформление
ax.set_xlabel("время, с")
ax.set_ylabel("сигнал, мВ")
ax.legend()
# 5. сохранение в вектор
fig.savefig("figure1.pdf", bbox_inches="tight", dpi=300)Обратите внимание: подпись тренда вычисляется из данных (coef[0]), а не вписывается руками. Если данные изменятся, подпись обновится сама — рассинхрона быть не может.
Закрепляем случайность
Если в фигуре есть случайность (джиттер, бутстрэп, симуляция), её надо зафиксировать seed'ом, иначе «воспроизводимый» скрипт будет давать разную картинку. Покажем, что разные seed дают разный результат, а один и тот же seed — всегда одинаковый.
import random
def sample_mean(seed):
random.seed(seed)
return round(sum(random.random() for _ in range(5)) / 5, 4)
print("разные seed :", sample_mean(1), sample_mean(2))
print("один seed=42:", sample_mean(42), sample_mean(42))Вывод:
разные seed : 0.4992 0.5762 один seed=42: 0.3798 0.3798
Один и тот же seed даёт идентичный результат в обоих вызовах — фигура станет полностью детерминированной, а разные seed закономерно различаются.
Как работает под капотом
Воспроизводимость держится на трёх китах: данные хранятся отдельным файлом (а не вшиты в скрипт), весь процесс описан кодом, а версии библиотек зафиксированы (например, в requirements.txt). Тогда фигура — детерминированная функция от входа: одни и те же данные плюс один и тот же код всегда дают один и тот же PDF.
Воспроизводимость как часть науки
Кризис воспроизводимости — большая тема современной науки: значительная доля опубликованных результатов не удаётся повторить. Фигуры играют здесь особую роль, потому что именно через них читатель чаще всего знакомится с результатом. Если фигуру нельзя пересобрать из исходных данных, то невозможно и проверить, не закралась ли ошибка между данными и картинкой — а такие ошибки случаются сплошь и рядом: перепутанные столбцы, потерянный фильтр, случайно исключённая группа.
Поэтому ведущие журналы всё чаще требуют публиковать вместе со статьёй не только данные, но и код, порождающий фигуры. Хорошая привычка — держать рядом с каждой фигурой её скрипт под контролем версий (git), фиксировать версии библиотек и seed случайности. Тогда через год, когда придёт правка от рецензента или обнаружится новая порция данных, обновление фигуры займёт минуту, а не день, и вы будете уверены, что картинка соответствует именно этим данным.
Частые ошибки
- Финальные правки в редакторе — потерянный, неповторимый труд.
- Незафиксированный seed — «воспроизводимый» скрипт каждый раз рисует чуть другое.
- Данные, вписанные прямо в код числами — невозможно обновить и легко ошибиться.
- Подписи и числа в тексте фигуры, набранные вручную — рассинхронизируются с данными.
Итог
- Фигуру собирает скрипт, а не рука в редакторе.
- Данные — отдельный файл; код — рецепт; результат детерминирован.
- Случайность фиксируется seed'ом.
- Подписи и числа вычисляются из данных, а не вписываются.