Как использовать исследовательские блокноты [Лучшие практики]

Лучшие практики использования исследовательских блокнотов

Блокноты Jupyter стали одним из самых спорных инструментов в сообществе специалистов по науке о данных. Есть некоторые рычащие критики, а также страстные фанаты. Тем не менее, многие ученые-данные согласятся, что они могут быть действительно ценными – если использовать их хорошо. Именно на это мы собираемся сосредоточиться в этой статье, которая является второй в моей серии о Шаблоны программного обеспечения для науки о данных и разработки ML. Я покажу вам лучшие практики использования блокнотов Jupyter для исследовательского анализа данных.

Но сначала нам нужно понять, почему блокноты были созданы в научном сообществе. Когда наука о данных была привлекательной, блокноты еще не существовали. Прежде всего, у нас был IPython, который интегрировался в среды разработки, такие как Spyder, которые пытались имитировать работу RStudio или Matlab. Эти инструменты получили значительное распространение среди исследователей.

В 2014 году проект Jupyter возник из IPython. Его использование стремительно возросло, главным образом благодаря исследователям, перешедшим работать в промышленность. Однако подходы к использованию блокнотов, которые хорошо работают для научных проектов, не всегда успешно переносятся на анализы, проводимые для бизнес- и продуктовых подразделений предприятий. Не редкость, что молодым специалистам-данным, принятым сразу после университета, трудно соответствовать новым ожиданиям, с которыми они сталкиваются в отношении структуры и оформления своих анализов.

В этой статье мы поговорим о блокнотах Jupyter, в основном с точки зрения бизнеса и продукта. Как я уже упоминал, блокноты Jupyter – это спорная тема, так что давайте сразу перейдем к моему мнению.

Блокноты Jupyter следует использовать только для исключительно исследовательских задач или ад-хок анализа.

Блокнот должен быть всего лишь отчетом. Сам код, который он содержит, не должен иметь значения. Важны только результаты, которые он генерирует. Идеально, мы должны иметь возможность скрыть код в блокноте, потому что это просто способ ответить на вопросы.

Например: Каковы статистические характеристики этих таблиц? Что является свойствами этого набора данных для обучения? Каково влияние размещения этой модели в производстве? Как мы можем обеспечить превосходство этой модели над предыдущей? Как выполнилось это AB-тестирование?

Блокноты Jupyter полезны в различных областях и для разных целей
Блокноты Jupyter полезны в различных областях и для разных целей | Источник: Автор

Блокнот Jupyter: рекомендации по эффективному повествованию

Создание блокнотов Jupyter, в основном, является способом рассказа истории или ответа на вопрос о проблеме, которую вы исследовали. Но это не означает, что вам нужно показывать явную работу, которую вы сделали, чтобы прийти к вашему выводу.

Блокноты должны быть уточненными.

Они в первую очередь создаются для того, чтобы писатель понимал проблему, а также для того, чтобы их коллеги могли получить этот знания без необходимости глубокого погружения в проблему самостоятельно.

Охват

Нелинейная и древовидная структура исследования наборов данных в блокнотах, которые обычно содержат нерелевантные разделы исследования, которые не привели к ответу, не является окончательным видом блокнота. Блокнот должен содержать минимальный контент, лучше всего отвечающий на вопросы. Вы всегда должны комментировать и объяснять каждое предположение и вывод. Руководства для руководителей всегда рекомендуются, так как они идеальны для заинтересованных вопросов или ограниченного времени. Они также являются отличным способом подготовки рецензентов-коллег к полному просмотру блокнота.

Аудитория

Целевая аудитория блокнотов, как правило, достаточно технически подкована или бизнес-ориентирована. Следовательно, от вас ожидается использование сложных терминов. Тем не менее, руководства для руководителей или заключения всегда следует писать простым языком и делать ссылки на разделы с более подробными объяснениями. Если вам сложно создавать блокнот для нетехнической аудитории, то, возможно, стоит рассмотреть возможность создания презентации. Там вы можете использовать информационную графику, настраиваемую визуализацию и более широкие способы объяснения ваших идей.

Различные заинтересованные стороны ученого-аналитика имеют различные требования
Различные заинтересованные стороны ученого-аналитика имеют различные требования | Источник: Автор

Контекст

Всегда предоставляйте контекст для решаемой проблемы. Данные самостоятельно недостаточны для создания цельной истории. Необходимо включить анализ в рамки области, в которой мы работаем, чтобы аудитория могла комфортно читать его. Используйте ссылки на существующую базу знаний компании для поддержки ваших утверждений и соберите все ссылки в специальном разделе блокнота.

Как структурировать содержимое блокнота Jupyter

В этом разделе я объясню типичную структуру блокнота, которую я обычно использую. Может показаться, что это много работы, но я рекомендую создать шаблон блокнота со следующими разделами, оставив места для специфики вашей задачи. Такой настроенный шаблон сэкономит вам много времени и обеспечит последовательность во всех блокнотах.

  1. Заголовок: Желательно, название связанной задачи JIRA (или другого программного обеспечения управления задачами), связанной с задачей. Это позволяет вам и вашей аудитории однозначно связать ответ (блокнот) с вопросом (задачей JIRA).
  2. Описание: Что вы хотите достичь в этой задаче? Это должно быть очень кратко.
  3. Оглавление: Записи должны ссылаться на разделы блокнота, позволяя читателю перейти к интересующей его части. (Jupyter создает HTML-якоря для каждого заголовка, которые извлекаются из оригинального заголовка с помощью headline.lower().replace(” “, “-“), поэтому вы можете ссылаться на них с помощью обычных ссылок Markdown, например [название раздела](#название-раздела). Вы также можете размещать свои собственные якоря, добавляя <a id=’ваш-якорь’></a> к ячейкам Markdown.)
  4. Ссылки: Ссылки на внутреннюю или внешнюю документацию с фоновой информацией или специфической информацией, используемой в представленном в блокноте анализе.
  5. В двух словах или краткое изложение: Очень кратко объясните результаты всего исследования и выделите основные выводы (или вопросы), к которым вы пришли.
  6. Введение и фон: Поместите задачу в контекст, добавьте информацию о ключевых предпосылках бизнеса относительно проблемы и более подробно объясните задачу.
  7. Импорты: Импорты библиотек и настройки. Настройте параметры для сторонних библиотек, таких как matplotlib или seaborn. Добавьте переменные окружения, такие как даты, для определения окна исследования.
  8. Исследуемые данные: Опишите таблицы или наборы данных, которые вы исследуете/анализируете, и ссылкуйте их источники или связывайте их с записями в каталоге данных. Желательно указать, как создается каждый набор данных или таблица и с какой частотой он обновляется. Вы также можете связать этот раздел с другой документацией.
  9. Ячейки анализа
  10. Вывод: Подробное объяснение ключевых результатов, полученных в разделе Анализ, с ссылками на конкретные части блокнота, где читатели могут найти дальнейшие пояснения.

Не забывайте всегда использовать форматирование Markdown для заголовков и выделения важных утверждений и цитат. Вы можете ознакомиться с различными вариантами синтаксиса Markdown в документации по Markdown Cells — Jupyter Notebook 6.5.2.

Пример шаблона для исследовательского блокнота
Пример шаблона для исследовательского блокнота | Источник: Автор

Как организовать код в блокноте Jupyter

Для исследовательских задач код для создания SQL-запросов, обработки данных с использованием pandas или создания графиков не является важным для читателей.

Тем не менее, это важно для рецензентов, поэтому мы всё же должны поддерживать хорошее качество и читабельность.

Вот мои советы по работе с кодом в блокнотах:

Перемещение вспомогательных функций в обычные модули Python

В целом, импортирование функций, определенных в модулях Python, лучше, чем определение их в блокноте. Во-первых, различия в Git в файлах .py намного легче читать, чем различия в блокнотах. Читатель также не должен знать, что функция делает под капотом, чтобы понять блокнот.

Например, у вас обычно есть функции для чтения данных, выполнения SQL-запросов и предварительной обработки, преобразования или обогащения набора данных. Все они должны быть перемещены в файлы .py, а затем импортированы в блокнот, чтобы читатель видел только вызов функции. Если рецензенту нужны дополнительные сведения, он всегда может посмотреть на модуль Python непосредственно.

Я нахожу это особенно полезным для функций построения графиков, например. Обычно я могу многократно использовать одну и ту же функцию для создания гистограммы в моем блокноте. Мне потребуется внести небольшие изменения, например, использование другого набора данных или другого заголовка, но общий макет и стиль графика будут такими же. Вместо копирования и вставки одного и того же фрагмента кода я просто создаю модуль utils/plots.py и создаю функции, которые можно импортировать и настраивать с помощью предоставления аргументов.

Вот очень простой пример:

import matplotlib.pyplot as plt
import numpy as np
 
def create_barplot(data, x_labels, title='', xlabel='', ylabel='', bar_color='b', bar_width=0.8, style='seaborn', figsize=(8, 6)):
    """Создание настраиваемой гистограммы с использованием Matplotlib.
 
    Параметры:
    - data: Список или массив данных для построения графика.
    - x_labels: Список меток для оси x.
    - title: Заголовок графика.
    - xlabel: Метка для оси x.
    - ylabel: Метка для оси y.
    - bar_color: Цвет столбцов (по умолчанию синий).
    - bar_width: Ширина столбцов (по умолчанию 0.8).
    - style: Применяемый стиль Matplotlib (например, 'seaborn', 'ggplot', 'default').
    - figsize: Кортеж, задающий размер фигуры (ширина, высота).
 
    Возвращает:
    - Ничего
    """
    # Установка стиля Matplotlib
    plt.style.use(style)
 
    # Создание фигуры и оси
    fig, ax = plt.subplots(figsize=figsize)
 
    # Генерация позиций по оси x для столбцов
    x = np.arange(len(data))
 
    # Создание гистограммы
    ax.bar(x, data, color=bar_color, width=bar_width)
 
    # Установка меток оси x
    ax.set_xticks(x)
    ax.set_xticklabels(x_labels)
 
    # Установка меток и заголовка
    ax.set_xlabel(xlabel)
    ax.set_ylabel(ylabel)
    ax.set_title(title)
 
    # Показать график
    plt.show()
 
# Пример использования в ячейке блокнота:
create_barplot(
    data,
    x_labels,
    title=”Пользовательский график”,
    xlabel=”Категории”,
    ylabel=”Значения”,
    bar_color=”skyblue”,
    bar_width=0.6,
    style=”seaborn”,
    figsize=(10,6)
)

При создании этих модулей Python помните, что код все еще является частью исследовательского анализа. Поэтому, если вы не используете его в другой части проекта, он не должен быть идеальным. Достаточно читаемым и понятным для ваших рецензентов.

Размещение функций для построения графиков, загрузки данных, подготовки данных и реализации метрик оценки в обычных модулях Python позволяет сосредоточиться на исследовательском анализе в блокноте Jupyter
Размещение функций для построения графиков, загрузки данных, подготовки данных и реализации метрик оценки в обычных модулях Python позволяет сосредоточиться на исследовательском анализе в блокноте Jupyter | Источник: Автор

Использование SQL напрямую в ячейках Jupyter

Есть случаи, когда данные не находятся в оперативной памяти (например, в pandas DataFrame), но в хранилище данных компании (например, Redshift). В таких случаях основная часть исследования и обработки данных будет проводиться с помощью SQL.

Есть несколько способов использования SQL в блокнотах Jupyter. JupySQL позволяет писать SQL-код прямо в ячейках блокнота и отображает результат запроса, как если бы это было pandas DataFrame. Вы также можете сохранять SQL-скрипты в сопутствующих файлах или вспомогательных модулях Python, о которых мы говорили в предыдущем разделе.

Большинство выбора между одним или другим зависит от ваших целей:

Если вы проводите исследование данных из нескольких таблиц хранилища данных и хотите показать коллегам качество и правильность данных, то обычно лучшим вариантом является отображение SQL-запросов внутри блокнота. Ваши рецензенты будут ценить то, что они могут непосредственно видеть, как вы делали запросы к этим таблицам, какие соединения вы должны были сделать для получения определенных представлений, какие фильтры вам нужно было применять и т.д.

Однако, если вы просто генерируете набор данных для проверки модели машинного обучения, и основной акцент блокнота – показ разных метрик и объяснительных выводов, то я бы рекомендовал скрыть извлечение данных как можно больше и сохранить запросы в отдельном файле SQL-скрипта или модуле Python.

Далее мы рассмотрим пример использования обоих вариантов.

Чтение и выполнение из файлов .sql

Мы можем использовать файлы .sql, которые открываются и выполняются из блокнота через библиотеку подключения к базе данных.

Предположим, у нас есть следующий запрос в файле select_purchases.sql:

SELECT * FROM public.ecommerce_purchases WHERE product_id = 123

Затем мы могли бы определить функцию для выполнения SQL-скриптов:

import psycopg2
 
def execute_sql_script(filename, connection_params):
    """
    Выполнение SQL-скрипта из файла с помощью psycopg2.
 
    Параметры:
    - filename: Имя файла SQL-скрипта для выполнения.
    - connection_params: Словарь с параметрами подключения PostgreSQL,
                        такими как 'host', 'port', 'database', 'user' и 'password'.
 
    Возвращает:
    - None
    """
    # Извлечение параметров подключения
    host = connection_params.get('host', 'localhost')
    port = connection_params.get('port', '5432')
    database = connection_params.get('database', '')
    user = connection_params.get('user', '')
    password = connection_params.get('password', '')
 
    # Установление подключения к базе данных
    try:
        conn = psycopg2.connect(
            host=host,
            port=port,
            database=database,
            user=user,
            password=password
        )
        cursor = conn.cursor()
 
        # Чтение и выполнение SQL-скрипта
        with open(filename, 'r') as sql_file:
            sql_script = sql_file.read()
            cursor.execute(sql_script)
        
        # Извлечение результата в Pandas DataFrame
        result = cursor.fetchall()
        column_names = [desc[0] for desc in cursor.description]
        df = pd.DataFrame(result, columns=column_names)

        # Применение изменений и закрытие подключения
        conn.commit()
        conn.close()
        return df

    except Exception as e:
        print(f"Ошибка: {e}")
        if 'conn' in locals():
            conn.rollback()
            conn.close()

Обратите внимание, что мы предоставили значения по умолчанию для параметров подключения к базе данных, чтобы не указывать их каждый раз. Однако помните, что никогда не храните секреты и другую чувствительную информацию в ваших сценариях Python! (Позже в серии мы обсудим разные решения этой проблемы.)

Теперь мы можем использовать следующий однострочник в нашем блокноте для выполнения скрипта:

df = execute_sql_script('select_purchases.sql', connection_params)

Использование JupySQL

Традиционно, ipython-sql был выбранным инструментом для выполнения запросов SQL из Jupyter-блокнотов. Но его создатель прекратил его разработку в апреле 2023 года и рекомендует перейти на JupySQL, который является активно поддерживаемым форком. Впредь все улучшения и новые возможности будут добавляться только в JupySQL.

Чтобы установить библиотеку и использовать ее с Redshift, мы должны выполнить следующее:

pip install jupysql sqlalchemy-redshift redshift-connector 'sqlalchemy<2'

(Вы также можете использовать его вместе с другими базами данных, такими как snowflake или duckdb,)

В вашем блокноте Jupyter теперь вы можете использовать магическую команду %load_ext sql для включения SQL и использовать следующий фрагмент для создания движка sqlalchemy Redshift:

от os импортировать environ
из sqlalchemy импортировать create_engine
из sqlalchemy.engine импортировать URL

user = environ["REDSHIFT_USERNAME"]
password = environ["REDSHIFT_PASSWORD"]
host = environ["REDSHIFT_HOST"]

url = URL.create(
    drivername="redshift+redshift_connector",
    username=user,
    password=password,
    host=host,
    port=5439,
    database="dev",
)

engine = create_engine(url)

Затем, просто передайте движку команду:

%sql engine --alias redshift-sqlalchemy

И вы готовы к работе!

Теперь все так же просто, как использование команды magic и написание любого запроса, который вы хотите выполнить, и вы получите результаты в выводе ячейки:

%sql
SELECT * FROM public.ecommerce_purchases WHERE product_id = 123

Убедитесь, что ячейки выполняются в порядке

Я рекомендую всегда выполнять все ячейки кода перед загрузкой блокнота в свой репозиторий. Jupyter блокноты сохраняют состояние вывода каждой ячейки при ее выполнении. Это означает, что написанный или отредактированный вами код может не соответствовать показанному выводу ячейки.

Запуск блокнота сверху вниз – это также хороший способ проверить, зависит ли ваш блокнот от какого-либо ввода пользователя для правильного выполнения. В идеале все должно работать без вашего вмешательства. В противном случае, ваш анализ, скорее всего, нельзя воспроизвести другими – или даже самим вами в будущем.

Один из способов проверить, что блокнот был выполнен в правильном порядке, – это использование pre-commit hook nbcheckorder. Он проверяет, являются ли номера вывода ячейки последовательными. Если они не являются таковыми, это указывает на то, что ячейки блокнота не были выполнены одна за другой, и не позволяет подтвердить изменения коммита Git.

Пример .pre-commit-config.yaml:

- repo: local
   rev: v0.2.0
   hooks:
     - id: nbcheckorder

Если вы еще не используете pre-commit, я настоятельно рекомендую вам использовать этот небольшой инструмент. Я рекомендую вам начать изучать его через эту introduction to pre-commit Эллиота Джордана. Позже вы можете изучить его обширную документацию, чтобы понять все его функции.

Очистка вывода ячеек

Еще лучше, чем предыдущий совет, очистите все выводы ячеек в блокноте. Одно из преимуществ, которое вы получаете, заключается в том, что вы можете игнорировать состояния и выводы ячеек, но с другой стороны, это заставляет рецензентов запустить код локально, если они хотят увидеть результаты. Есть несколько способов сделать это автоматически.

Вы можете использовать nbstripout вместе с pre-commit, как объясняет создатель инструмента Флориан Ратхгебер на GitHub:

- repo: local
  rev: 0.6.1
  hooks:
    - id: nbstripout

Вы также можете использовать nbconvert –ClearOutputpPreprocessor в настраиваемом pre-commit hook, как объясняет Юрий Жауниарович:

- repo: local
    hooks:
      - id: jupyter-nb-clear-output
        name: jupyter-nb-clear-output
        files: \.ipynb$
        stages: [ commit ]
        language: python
        entry: jupyter nbconvert --ClearOutputPreprocessor.enabled=True --inplace
        additional_dependencies: [ 'nbconvert' ]

Создание и публикация отчетов с помощью Jupyter notebook

И вот вопрос, с которым в индустрии пока не справились. Какой самый лучший способ делиться блокнотами с командой и внешними заинтересованными сторонами?

<!–В отношении обмена анализами из блокнотов Jupyter поле делится на три различных типа команд, которые способствуют различным способам работы.

Команды переводчиков

Эти команды считают, что люди из бизнес-или продуктовых подразделений не будут чувствовать себя комфортно при чтении блокнотов Jupyter. Поэтому они адаптируют свой анализ и отчеты для ожидаемой аудитории.

Команды переводчиков извлекают свои результаты из блокнотов и добавляют их в систему знаний своей компании (например, Confluence, Google Slides и т. д.). Как негативный побочный эффект, они теряют часть трассируемости блокнотов, потому что теперь сложнее просматривать историю версий отчета. Но они, наверное, будут утверждать, что они способны более эффективно доносить свои результаты и анализ заинтересованным сторонам.

Если вы хотите сделать это, я рекомендую сохранить ссылку между экспортированным документом и блокнотом Jupyter, чтобы они всегда были в синхронизации. В этой настройке вы можете сохранять блокноты с меньшим количеством текста и выводов, акцентируя внимание больше на необработанных фактах или данных. Вы будете использовать систему документирования для расширения руководства и комментариев по каждому из выводов. Таким образом, вы можете разделить два основных результата – исследовательский код и полученные выводы.

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

Эти команды используют локальные блокноты Jupyter и делятся ими с другими бизнес-подразделениями, создавая решения, приспособленные к системе знаний и инфраструктуре их компании. Они действительно считают, что бизнес и заинтересованные стороны в продукцию должны быть способны понять блокноты данных ученых и настойчиво настаивают на необходимости сохранить полностью прослеживаемое обстоятельство от выводов обратно к сырым данным.

Однако маловероятно, что финансовая команда отправится на GitHub или Bitbucket, чтобы прочитать ваш блокнот.

Я видел несколько решений, внедренных в этой области. Например, вы можете использовать инструменты, такие как nbconvert, чтобы создавать PDF-файлы из блокнотов Jupyter или экспортировать их в виде HTML-страниц, чтобы ими можно было легко поделиться с любым человеком, даже за пределами технических команд.

Вы даже можете переместить эти блокноты в S3 и разрешить их быть размещенным в качестве статического веб-сайта с отображаемым видом. Вы можете использовать рабочий процесс CI/CD для создания и отправки HTML-отображения вашего блокнота в S3 при объединении кода в определенную ветку.

Приверженцы инструментов сторонних разработчиков

Эти команды используют инструменты, которые позволяют не только разрабатывать блокноты, но и делиться ими с другими людьми в организации. Это обычно включает работу с такими сложностями, как обеспечение безопасного и простого доступа к внутренним хранилищам данных, озерам данных и базам данных.

Некоторые из самых широко используемых инструментов в этой области – это Deepnote, Amazon SageMaker, Google Vertex AI и Azure Machine Learning. Все они являются полноценными платформами для выполнения блокнотов, которые позволяют создавать виртуальные среды на удаленных машинах для выполнения вашего кода. Они предоставляют интерактивную визуализацию, исследование данных и эксперименты, что упрощает весь жизненный цикл науки о данных. Например, Sagemaker позволяет вам визуализировать всю информацию об экспериментах, которую вы отслеживали с помощью Sagemaker Experiments, и Deepnote также предлагает кликабельные визуализации с их Chart Blocks.

Кроме того, Deepnote и SageMaker позволяют вам делиться блокнотом с любыми коллегами, чтобы просматривать его или даже обеспечить совместную работу в реальном времени, используя ту же среду выполнения.

Есть также альтернативы с открытым исходным кодом, такие как JupyterHub, но усилия по настройке и поддержке, которые вам нужно предпринять для его работы, не оправдываются. Установка JupyterHub на собственности может быть не оптимальным решением, и только в очень немногих случаях это имеет смысл (например, очень специализированные виды рабочих нагрузок, требующие специфического оборудования). Используя облачные сервисы, вы можете использовать преимущества экономического масштабирования, которые гарантируют гораздо более надежную архитектуру, чем другие компании, работающие в другом бизнесе, могут предложить. Вам необходимо учесть начальные затраты на настройку, делегировать его поддержку команде операций платформы, чтобы поддерживать его работоспособность для ученых данных, а также обеспечить безопасность и конфиденциальность данных. Поэтому доверие управляемым сервисам избавит вас от бесконечных головных болей по поводу инфраструктуры, которую лучше не иметь.

Мои общие советы для изучения этих продуктов: Если ваша компания уже использует облачного провайдера, такого как AWS, Google Cloud Platform или Azure, возможно, имеет смысл принять их решение по использованию блокнота, так как доступ к инфраструктуре вашей компании, вероятно, будет проще и кажется менее рискованным.

Принятие эффективной практики использования блокнотов Jupyter

В этой статье мы обсудили лучшие практики и советы по оптимизации использования блокнотов Jupyter.

Самое важное вывод:

Всегда подходите к созданию блокнота с учетом целевой аудитории и конечной цели. Таким образом, вы знаете, насколько сосредоточиться на разных аспектах блокнота (код, анализ, краткое изложение и т. д.).

В целом, я настоятельно рекомендую использовать блокноты Jupyter только для ответов на исследовательские вопросы и отчетности.

Производственные артефакты, такие как модели, наборы данных или гиперпараметры, не должны быть связаны с блокнотами. Они должны иметь свои источники в производственных системах, которые могут быть воспроизведены и повторно запущены. Например, SageMaker Pipelines или Airflow DAGs, которые управляются и тщательно тестируются.

Эти последние мысли о прослеживаемости, воспроизводимости и сопутствующих связях будут отправной точкой для следующей статьи в моей серии Software Patterns in Data Science and ML Engineering, которая сосредоточится на том, как повысить уровень ваших навыков ETL. Часто игнорируемый учеными-исследователями данных, я считаю, что владение ETL является основой и критичным шагом для гарантии успеха любого проекта машинного обучения.