Знакомство с Papermill

Как превратить Jupyter Notebook в рабочий процесс по обработке данных

1. Введение

Jupyter Notebook — это «золотой стандарт» в разведочном анализе данных (EDA-анализ) и отличный инструмент для документирования data science проектов. В основном аналитики работают в блокнотах итерационно, а не идут сверху вниз по ячейкам. Несмотря на это, в Jupyter Notebook можно воспроизвести пошаговый анализ данных, запуская ячейки с первой по последнюю.

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

1.1. Знакомство с Papermill

Papermill — это инструмент для параметризации и запуска блокнотов. Он превращает Jupyter Notebook в своего рода рабочий процесс, способный последовательно выполнять каждую ячейку без открытия JupyterLab (или Notebook). Пробелы по части логирования и автоматизации Papermill с лихвой восполняет запуском блокнотов в виде файлов и созданием отчетов для каждого выполнения.

ЦЕЛЬЮ данной статьи является интеграция Papermill и Jupyter Notebook для создания рабочего процесса обработки данных. Для наглядности мы создадим Python Notebook. В этом блокноте мы проведем простой анализ данных по метеопрогнозу через API (PyOWM), выполним первичную обработку данных, создадим несколько визуализаций и сформируем итоговый отчет.

Jupyter Notebook и другие необходимые файлы можно найти в репозитории проекта на GitHub. Если вы хотите повторить все сами, то установите библиотеки из papermill_env.yaml.

2. Настройка среды разработки

2.1. Установка Papermill и JupyterLab

Для начала создадим среду разработки в Conda (1, 2) и установим JupyterLab со всеми нужными библиотеками (3).

# 1) Создадим conda среду
conda create -n papermill python=3.7

# 2) Активируем ее
conda activate papermill 

# 3) Установим библиотеки через pip (или conda)
pip install papermill pyowm jupyterlab pandas seaborn boto3 pdfkit

После установки Papermill мы видим подробную информацию в терминале (4).

# 4) Документация по Papermill 
papermill -h
Использование: papermill [OPTIONS] NOTEBOOK_PATH OUTPUT_PATH.

Эта утилита выполняет по одному блокноту в подпроцессе. Papermill берет исходный блокнот, применяет к нему параметры, выполняет (4) блокнот с указанным ядром и сохраняет результат в конечном блокноте. 

NOTEBOOK_PATH и OUTPUT_PATH теперь можно заменить на `-` (представление stdout и stderr) или input/output между вертикальными слешами. То есть 

`<generate input>... | papermill | ...<process output>`

с `papermill - -`, заключенной в вертикальные слеши, прочитает блокнот из stdin и запишет его в stdout. 

Опции:
-p, --parameters TEXT...      — Параметры для передачи в ячейку параметров.
-r, --parameters_raw TEXT...  — Параметры для чтения в виде неотформатированной строки.
-f, --parameters_file TEXT    — Путь к YAML-файлу с параметрами
-y, --parameters_yaml TEXT    — YAML-строка для использования в качестве параметров.
-b, --parameters_base64 TEXT  — YAML-строка в кодировке Base64 для использования в качестве параметров.
--inject-input-path        — Добавляет путь к входному блокноту PAPERMILL_INPUT_PATH в качестве параметра блокнота.
--inject-output-path       — Добавляет путь к блокноту вывода PAPERMILL_OUTPUT_PATH в качестве параметра блокнота.
--inject-paths             — Добавляет пути к входному/выходному блокноту PAPERMILL_INPUT_PATH/PAPERMILL_OUTPUT_PATH в качестве параметров блокнота
--engine TEXT              — Название механизма выполнения, используемого при анализе блокнота.
--request-save-on-cell-execute / --no-request-save-on-cell-execute
— Запрос на сохранение блокнота после выполнения каждой ячейки
--prepare-only / --prepare-execute — Флажок для вывода блокнота без выполнения, но с применением параметров
-k, --kernel TEXT          — Название ядра для запуска
--cwd TEXT                 — Рабочая директория для запуска блокнота
--progress-bar / --no-progress-bar — Флажок для включения индикатора выполнения
--log-output / --no-log-output — Флажок для записи результата блокнота в настроенный журнал
--stdout-file FILENAME     — Файл для записи результата stdout
--stderr-file FILENAME     — Файл для записи результата stderr
--log-level [NOTSET|DEBUG|INFO|WARNING|ERROR|CRITICAL] — Установка уровня записи 
--start_timeout INTEGER    — Время (сек.) ожидания запуска ядра
--report-mode / --no-report-mode — Флажок для скрытия входных данных.
--version                  — Флажок для отображения версии
-h, --help                 — Показать это сообщение и выйти

Судя по документации, в основных функциях Papermill ничего сложного нет. Мы просто указываем путь к нужному Jupyter Notebook и задаем имя для блокнота вывода (он выполняет функцию журнала). К ряду других полезных опций мы вернемся позже.

2.2. Установка Jupyter Kernel

Несмотря на то, что Jupyter Notebook чаще всего ассоциируется с запуском на Python, он работает практически под любым языком программирования при установке нужного ядра. Papermill в сочетании с подходящим ядром позволяет запускать блокноты в различных средах, избегая проблем с отсутствующими библиотеками (5).

# 5) Установка ядра Jupyter для среды Papermill
pip install ipykernelipython kernel install --user --name=papermill-tutorial

3. Создание рабочего процесса

Мы воспользуемся Jupyter Notebook для анализа метеорологических данных. Суть вот в чем: мы извлечем данные по определенному городу через PyOWM (Python API), выполним первичную обработку данных, создадим графики и представим структурированную информацию в PDF-отчете.

3.1. Работа с метеорологическим PyOWM API

Как говорится на главной странице библиотеки, PyOWM — это клиентская обертка Python-библиотеки для Web API OpenWeatherMap. Она упрощает доступ к метеорологическим данным, предлагая «простую объектную модель». Единственное требование к использованию данной библиотеки — API-ключ, который можно бесплатно получить на OpenWeather.

3.2. Рабочий процесс. Часть 1: получение метеоданных через PyOWM API

В первой части рабочего процесса мы используем библиотеку PyOWM для получения информации об уже заданном городе city (в исходном блокноте — это Сан-Паулу, Бразилия). Мы перебираем объект forecast и структурируем возвращаемую информацию в DataFrame, чем существенно облегчаем себе жизнь в дальнейшем.

1. Доступ к pyown API

На первом этапе анализа мы получаем метеоданные по выбранному городу через pyown API.

Эта информация структурируется в словаре, а затем загружается в виде DataFrame Pandas для выполнения первичной обработки.

[1] #импорт библиотек

import pyowm
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
import pdfkit

1.1. Установка API-ключа PyOWM и определение переменных

Здесь мы определяем API-ключ для доступа к сервису OpenWeather, а также ячейку с параметром city , которая прописывается в Papermill.

In [2] # Установка API-ключа:

owm = pyowm.OWM('xxxxxxxxxxxPyOWM-API-keyxxxxxxxx')

In [3] # Определение параметров по умолчанию:

city = ‘Sao Paulo,BR’

1.2. Получение метеоданных по определенному городу

На данном этапе мы используем PyOWM для получения данных о городе и структурирования их сначала в словаре, а затем в DataFrame.

In [4] # Инстанцирование объекта forecast и получение метеоданных о городе:

fc = owm.three_hours_forecast(city)
forecast = fc.get_forecast()

In [5] # Создание словаря для структурирования метеоданных по выбранному городу:

dict_forecast = {
 ‘datetime’:[],
 ‘clouds’:[],
 ‘humidity’:[],
 ‘temp’:[],
 ‘temp_max’:[],
 ‘temp_min’:[],
 ‘detailed_status’:[],
 ‘icon_url’:[],
 ‘rain_vol’:[]
}

In [6] # Перебор объекта forecast для доступа к метеорологическим характеристикам (опциям):

for weather in forecast:
    dict_forecast['datetime'].append(str(weather.get_reference_time(timeformat='iso')))
dict_forecast['clouds'].append(weather.get_clouds())    dict_forecast['humidity'].append(weather.get_humidity())
dict_forecast['temp'].append(weather.get_temperature(unit='celsius').get('temp'))
dict_forecast['temp_max'].append(weather.get_temperature(unit='celsius').get('temp_max'))
dict_forecast['temp_min'].append(weather.get_temperature(unit='celsius').get('temp_min'))
dict_forecast['detailed_status'].append(weather.get_detailed_status())
dict_forecast['icon_url'].append(weather.get_weather_icon_url())
    if '3h' in weather.get_rain().keys():
        dict_forecast['rain_vol'].append(weather.get_rain().get('3h'))
    else:
dict_forecast['rain_vol'].append(0)

In [7] # Создание Dataframe из словаря:

df = pd.DataFrame.from_dict(dict_forecast)
df.head()

Out [7]

3.3. Рабочий процесс. Часть 2: получение метеоданных через PyOWM API

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

2. Создание визуализации

На этом этапе мы будем создавать графики из структурированных данных через seaborn.

2.1. Температурный график

График показывает метеопрогноз с максимальной, минимальной и средней температурой на ближайшие 5 дней.

In [14] # Создание графика температур на ближайшие 5 дней

fig = plt.figure()
sns_plot = sns.lineplot(data=df_temp, style="event",markers=True, dashes=False)
sns_plot.set_title(f'Temperature forecast for the next 5 days', fontsize=20)
sns_plot.set_xlabel('Date', fontsize=14)
sns_plot.set_ylabel('Temperature Celsius', fontsize=14)
sns_plot.set_xticklabels(df_temp.index, rotation=20)
sns_plot.grid(True)

sns_plot.legend(labels=['Min. Temperature', 'Max Temperature', 'Average Temperature'])
fig.set_size_inches(12, 6)

temperature_plot = f"{city.split(',')[0].replace(' ','_')}_temperature.png"
sns_plot.figure.savefig(temperature_plot, 
                  dpi=300, facecolor='w', 

Out [14]

2.2. График влажности, осадков и облачности

В этом графике мы объединяем данные об осадках и влажности.

In [15] # Создание Dataframe с ожидаемым объемом осадков по дням:

df_rain_per_day = df.resample(‘D’, on=’datetime’).sum()[[‘rain_vol’]]
df_rain_per_day.index = df_rain_per_day.index.date

Out [15]

In [17]

fig = plt.figure()
# Линейный график влажности и облачности:
ax1 = fig.add_subplot(211)
ax1 = sns.lineplot(data=df_mean[['clouds', 'humidity']], markers=True, dashes=False)
ax1.set_xticks([])
ax1.set_title(f'Expected humidity and rain volume for the next 5 days', fontsize=20)
ax1.set_ylabel('Percentage', fontsize=14)
ax1.grid(True)

# Столбцы с общим объемом осадков по дням:
ax2 = fig.add_subplot(212)
ax2 = sns.barplot(x=df_rain_per_day.index, y='rain_vol', 
                  data=df_rain_per_day,
                  palette="Blues_d")
ax2.set_xticklabels(df_temp.index, rotation=30)
ax2.set_ylabel('Total Rain Volume in mm', fontsize=14)
ax2.set_xlabel('Date', fontsize=14)
fig.set_size_inches(12, 6)

rain_humidity_plot = f"{city.split(',')[0].replace(' ','_')}_rain_humidity.png"
fig.savefig(rain_humidity_plot, 
              dpi=300, facecolor='w', 

3.4. Рабочий процесс. Часть 3: создание прогноза погоды в PDF

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

3. Создание метеосводки

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

In [49] # Задаем начальные и конечные даты для анализа:

today = str(df_mean.index.min()).replace(‘-’, ‘/’)
last_day = str(df_mean.index.max()).replace(‘-’, ‘/’)

In [50] # HTML-шаблон для добавления данных и графиков:

report_template = f’’’
<!DOCTYPE html>
 <html>
 <head>
 <meta charset=’utf-8'>
 <title>Weather Forecast with PyOWM</title>
 <link rel=’stylesheet’ href=’report.css’>
 <style>
 h1 {{
 font-family: Arial;
 font-size: 300%;
 }}
 h2 {{
 font-family: Arial;
 font-size: 200%;
 }}
 @page {{
 size: 7in 9.25in;
 margin: 27mm 16mm 27mm 16mm;
 }}
 </style> 
 </head>
 <h1 align=”center”>Weather forecast for {city}</h1>
 <h2 align=”center”>Initial date: {today}</h2>
 <h2 align=”center”>Final date: {last_day}</h2>
 
 <figure>
 <img src=”{temperature_plot}” width=”1200" height=”600">
 </figure>
 <figure>
 <img src=”{rain_humidity_plot}” width=”1200" height=”600">
 </figure> 
 </html>
‘’’

In [51] # Сохранение HTML-строки в файл:

html_report = f"{city.split(',')[0].replace(' ','_')}_report.html"
with open(html_report, "w") as r:
    r.write(report_template)

In [52] # Использование pdfkit для создания графика в PDF:

pdfkit.from_file(html_report, f”{city.split(‘,’)[0].replace(‘ ‘, ‘_’)}_weather_report_for.pdf”)

3.5. Окончательная проверка перед подключением к Papermill

Сразу после завершения анализа в Jupyter Notebook, рекомендуется протестировать рабочий процесс через перезапуск ядра и запуск всех ячеек (Run > Run all cells). Если мы видим, что все ячейки выполнились успешно, и был получен ожидаемый результат, то наш блокнот готов к интеграции с Papermill. Ожидаемый метеопрогноз, который наш рабочий процесс сгенерировал для Сан-Паулу, представлен на Рис. 2. В дальнейшем мы настроим Jupyter Notebook на принятие любого города в качестве параметра рабочего процесса и его автоматическое выполнение через Papermill.

Рис. 2. Прогноз погоды, сгенерированный рабочим процессом для Сан-Паулу (Бразилия).

4. Настройка блокнота для Papermill

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

4.1. Определение параметров в JupyterLab

При запуске блокнота через JupyterLab мы должны будем создать ячейку параметров рабочего процесса со значениями по умолчанию. Далее мы выделяем ячейку с параметрами, выбираем Notebook Tools(иконка с гаечным ключом на панели настроек слева) и Advanced Tools(Рис. 3).

Рис. 3. Настройка ячейки блокнота в JupyterLab для получения параметров из Papermill.

В поле Cell Metadata добавляем следующее описание:

{
    "tags": [
        "parameters"
    ]
}

Не забудьте сохранить изменения, нажав на иконку вверху поля. Теперь ваш блокнот в JupyterLab готов к получению параметров из Papermill.

4.2. Определение параметров в Jupyter Notebook

Для настройки ячейки параметров в Jupyter Notebook нужно нажать View > Cell Toolbar > Tags. Далее прописываем и добавляем тег parameters в нужную ячейку блокнота (Рис. 4).

Рис. 4. Настройка ячейки блокнота в Jupyter Notebook для получения параметров из Papermill.

5. Выполнение Papermill

Запустить Papermill можно из командной строки или через Python API. Для запуска Jupyter Notebooks через терминал мы выполним следующую команду (6):

# 6) Запуск Papermill из терминала
papermill weather_forecast_using_pyowm.ipynb 
          weather_forecast_using_pyowm_output.ipynb 
          -p city 'Sao Paulo,BR' 
          -k papermill-tutorial

Первые два параметра — это название целевого блокнота (из сессии 3) и название блокнота вывода (выполненная версия ввода). Для перечисления параметров используется -p, поэтому здесь мы описываем название и значение каждого параметра (в нашем случае есть только city). И, наконец, через -k указывается ядро. Тут мы выбираем papermill-tutorial, созданное в шаге 5).

Если бы мы захотели запустить тот же процесс через Python API в Papermill, то написали бы следующее:

import papermill as pm

pm.execute_notebook('weather_forecast_using_pyowm.ipynb',
                    'weather_forecast_using_pyowm_output.ipynb',
                    parameters={'city':'Sao Paulo,BR'},
                    kernel_name='papermill-tutorial')

5.1. Блокнот вывода

С помощью Python API в Papermill можно при желании объединить выполнение блокнота с другими действиями. Например, если бы в процессе выполнения вдруг возникла ошибка, то мы могли бы проанализировать выходной файл, обнаружить проблему и сохранить информацию в структуре базы данных.

Файлы блокнотов Jupyter (с расширением .ipynb) — это JSON-файлы с информацией о тексте каждой ячейки, исходном коде, выводе и метаданных. Papermill создает файл вывода, который представляет собой блокнот входа, выполненный с пользовательскими параметрами. По сути, здесь присутствует вся нужная информация для обработки документа. То есть мы можем использовать блокнот вывода в качестве некоего журнала логирования данных при выполнении рабочего процесса. Один из способов сохранения выходных JSON-файлов — это использование базы данных «ключ-значение» NoSQL (Amazon DynamoDB, MongoDB, CassandraDB, BigTable и т.д.).

6. Заключение

Papermill — это простой и удобный инструмент, преобразующий Jupyter Notebook в рабочий процесс обработки данных. Он расширяет возможности использования блокнотов, устраняет ограничения среды по визуализации/документации и предлагает готовую к запуску платформу. Papermill можно использовать как быстрое и качественное решение для прототипирования рабочих процессов перед созданием ETL, работающих с более сложными процессами передачи данных (как, например, Airflow или Luigi).

Интеграционные возможности данной платформы безграничны. Мы уверены, что Papermill обзаведется своим сообществом, которое направит развитие проекта в серьезное русло.

Специально для сайта ITWORLD.UZ. Новость взята с сайта NOP::Nuances of programming