Источник: Nuances of Programming
Вы когда-нибудь задумывались о том, как здорово было бы использовать визуализацию данных с помощью D3 или React в Jupyter Notebook? Много раз, скорее всего. Jupyter Notebook и Google Colab Notebook идеально подходят для экспериментов с визуализацией данных: в одном месте удобно собраны логика обработки данных, препроцессинг, иллюстрации и документация.
Однако для добавления JavaScript в Notebook на Python потребуется громоздкий и повторяющийся код шаблонов. Что еще хуже, разные Notebook-среды имеют несовместимые API для передачи сообщений между Python и JavaScript. Например, если разработчик хочет, чтобы его библиотека работала как в Jupyter, так и в Colab, то ему придется использовать два разных формата связи — это неудобно.
Python-библиотека notebookJS была создана, чтобы решить эти проблемы. Теперь можно использовать JavaScript в аналитике данных на Python с помощью одной строчки кода!
Предпосылки
Написание визуализации с помощью любимого веб-стека (например, D3 и React) добавляет программированию гибкости. Однако в таком случае тестирование визуализации на разных наборах данных может стать проблемой. По моему опыту, мне приходилось либо жестко кодировать нужный датасет, либо писать собственный сервер для управления данными; ни один из вариантов не подходит для множественных экспериментов с разными наборами данных, препроцессингом или диаграммами.
// Жесткое кодирование датасета
d3.csv("data.csv", function(error, data) {
// Рисуем супер крутую диаграмму!
}
Экспериментировать в Python Notebooks — очень удобно, поскольку манипуляции с данными выполняются оперативно, а блокнот автоматически отслеживает все этапы вычислений. Обычно возможностей библиотек Python, таких как Matplotlib и Altair, полностью хватает для построения графика. Но бывают случаи, когда необходимой визуализации данных невозможно добиться при помощи готовых решений. Например, при работе с ML-конвейерами на базе Auto-Sklearn визуализацию придется написать с нуля.
Чтобы интегрировать пользовательские визуализации в Jupyter Notebook, придется написать много шаблонного кода: непосредственно для запуска визуализации в среде, а также для организации обмена данными между Python и JavaScript.
Поскольку красивая и детальная визуализация данных в Jupyter Notebook пригодится многим людям, было решено создать специальную библиотеку. Так и был разработан notebookJS, который предоставляет следующие возможности.
- Позволяет избежать многократного написания одинакового шаблонного кода для запуска визуализации в Jupyter Notebook.
- Автоматически подгружает библиотеки JavaScript из сети.
- Поддерживает разделение JavaScript и CSS кода на несколько отдельных файлов.
notebookJS
Для начала нужно скачать саму библиотеку:
pip install notebookjs
API notebookJS крайне прост в использовании. На самом-то деле обо всем позаботится лишь одна функция: метод execute_js выполнит JavaScript-функцию и самостоятельно настроит инфраструктуру для двунаправленной связи между Python и JavaScript с помощью обратных вызовов.
execute_js(
library_list,
main_function,
data_dict={},
callbacks={},
css_list=[],
)
Рассмотрим параметры функции execute_js подробнее:
- library_list — это список из строк, содержащий ссылку на JavaScript-библиотеку, JavaScript-код и JavaScript-пакет.
- main_function — это строка, содержащая идентификатор главной функции для её вызова. Главная функция вызывается с двумя параметрами: <div_id> (#my_div) и <data_dict>.
- data_dict — это словарь, содержащий те данные, которые предполагается использовать в качестве входных для главной функции.
- callbacks — это словарь, содержащий пары ключ-значение в формате {<callback_str_id> : <python_function>}. Собственно, данный словарь позволяет JavaScript-библиотеке использовать обратные вызовы для обмена данными с Python.
- css_list — это список из строк, содержащий ссылку на CSS-файл или же сами стили CSS.
При вызове функции execute_js также произойдет вызов указанной main_function со следующей сигнатурой:
function main_function(div_id, data_dict)
В качестве простого примера рассмотрим использование JavaScript-библиотеки для визуализации данных D3. Нарисуем круг в ячейке вывода:
# Установка ссылки на библиотеку D3
d3_lib_url = "https://d3js.org/d3.v3.min.js"
# Определение JS-функции при помощи Python-строки
js_string = “””
function draw_circle(div_id, data){
d3.select(div_id)
.append("div")
.style("width", "50px")
.style("height", "50px")
.style("background-color", data.color)
.style("border-radius", "50px")
}
“””
# Исполнение кода с использованием notebookjs
from notebookjs import execute_js
execute_js([d3_lib_url, draw_circle_lib], "draw_circle", {"color": "#4682B4"})
Загрузка локальных библиотек
Как мы могли удостовериться, запуск JavaScript-функции в среде Python Notebook осуществляется всего лишь одной строчкой кода (исключая настройку JavaScript-библиотеки). Стоит отметить, что JavaScript-код можно сохранить в отдельный файл, в последствии загружаемый через Python. Данные для визуализации при необходимости также загружаются из отдельного файла через Python, а передать их в JavaScript позволяет структура словаря (с внутренней конвертацией в JSON-формат).
Например, для многократного использования радиальной гистограммы достаточно всего лишь изменить Python-код загрузки набора данных и запустить notebookJS:
# Указание ссылки на библиотеку D3
d3_lib_url = "https://d3js.org/d3.v3.min.js"
# Загрузка локальных библиотек
with open("radial_bar.css", "r") as f:
radial_bar_css = f.read()
with open ("radial_bar_lib.js", "r") as f:
radial_bar_lib = f.read()
# Загрузка данных
import pandas as pd
energy = pd.read_csv("energy.csv").to_dict(orient="records")
# Построение диаграммы с помощью notebookJS
from notebookjs import execute_js
execute_js(library_list=[d3_lib_url, radial_bar_lib], main_function="radial_bar",
data_dict=energy, css_list=[radial_bar_css])
Обратные вызовы Python
Одно из лучших преимуществ notebookJs — это возможность настройки обратных вызовов Python. Во время активной работы над проектами в области науки о данных некоторые задачи удобнее и быстрее выполнить с помощью JavaScript (как пример — создание интерфейса и взаимодействий с пользователем). Но вот другие задачи оптимальнее выполняются с использованием Python (например, сложные вычисления и машинное обучение). Библиотека notebookJs как раз позволит нам применить на практике всё лучшее из обоих миров.
Рассмотрим элементарный пример: необходимо создать анимацию, выводящую на экран словосочетание “Hello World” на разных языках. Фразы хранятся в Python, а код интерфейса обрабатывается с помощью JavaScript.
JavaScript-функция отображения каждую секунду запрашивает у Python новую фразу “Hello World” с помощью идентификатора “get_hello”:
helloworld_js = """
function helloworld(div_id, data){
comm = new CommAPI("get_hello", (ret) => {
document.querySelector(div_id).textContent = ret.text;
});
setInterval(() => {comm.call({})}, 1000);
comm.call({});
}
"""
Python постоянно ожидает ежесекундного исполнения JavaScript-функции с идентификатором get_hello и незамедлительно отвечает:
import random
def hello_world_random(data):
hello_world_languages = [
"Ola Mundo", # Португальский
"Hello World", # Английский
"Hola Mundo", # Испанский
"Geiá sou Kósme", # Греческий
"Kon'nichiwa sekai", # Японский
"Hallo Welt", # Немецкий
"namaste duniya" # Хинди
]
i = random.randint(0, len(hello_world_languages)-1)
return {'text': hello_world_languages[i]}
Функция обратного вызова “соединена” с визуализацией благодаря специальному параметру обратного вызова:
from notebookjs import execute_js
execute_js(helloworld_js, "helloworld", callbacks={"get_hello": hello_world_random})
Ограничения
Пока что notebookJS не поддерживает библиотеки JavaScript ES6. Исходя из этого, в примерах используется библиотека D3 V3.
Если вам необходим JavaScript ES6, рекомендуется использовать инструмент сборки, такой как Webpack + Babel. С его помощью вы сможете скомпилировать весь JavaScript-код в один файл, чтобы преобразовать JavaScript ES6 в старый JavaScript.
Так как проектам свойственно расширяться, вышеописанный способ лучше всего подходит для управления несколькими библиотеками, а также для создания хорошо оптимизируемого кода. Узнать больше о настройке библиотеки Webpack можно в соответствующем разделе репозитория notebookJS на GitHub.
Итоги
notebookJS — это новый инструмент. Создатели библиотеки активно собирают отзывы о том, какие возможности программы им следует поддерживать. Дайте им знать, если у вас есть какие-либо вопросы или вы желаете сотрудничать!
В репозитории notebookJS на GitHub можно найти куда больше примеров. Живая демонстрация работы проекта доступна в соответствующем Colab Notebook.
Читайте также:
Перевод статьи Jorge Piazentin Ono: Introducing notebookJS: seamless integration between Python and JavaScript in Computational Notebooks