Интерактивная визуализация данных в Python с помощью Bokeh

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

С официального сайта: “Bokeh – это интерактивная библиотека визуализации, которая направлена ​​на презентацию в современных веб-браузерах. Его цель состоит в том, чтобы обеспечить элегантную, лаконичную конструкцию разнообразной графики, а также расширить эту возможность благодаря высокопроизводительной интерактивности над очень большими или потоковыми наборами данных. Bokeh может помочь всем, кто хочет быстро и легко создавать интерактивные графики, панели инструментов и программы данных.” Я думаю, объяснение довольно четкое, но было бы лучше увидеть его в действии, не так ли?

Прежде чем начать, убедитесь, что Bokeh установлен в твоей среде, если же нет, придерживайся инструкций по установке здесь.

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

Поэтому я взял два файла: один с выбросами CO2 от Gapminder.org и другой из курса на DataCamp (поскольку этот файл уже был предварительно обработан – таааааааак, я ленивый что страшное). Также можешь скачать эти файлы отсюда.

Как мы начинаем анализировать данные? Правильно, импортируя необходимые пакеты и импортируя сами данные (очень важно: D). Затем мы выполняем некоторое EDA (предыдущий / разведывательный анализ данных), чтобы понять, с чем мы имеем дело, и после этого очищение и преобразование данных в формат, необходимый для анализа. Достаточно просто. Поскольку статья не сосредотачивается на этих шагах, я просто вставлю код ниже со всеми преобразованиями, которые я сделал.

import pandas as pd
import numpy as np

# Data cleaning and preparation
data = pd.read_csv('data/co2_emissions_tonnes_per_person.csv')
data.head()

gapminder = pd.read_csv('data/gapminder_tidy.csv')
gapminder.head()

df = gapminder[['Country', 'region']].drop_duplicates()
data_with_regions = pd.merge(data, df, left_on='country', right_on='Country', how='inner')
data_with_regions = data_with_regions.drop('Country', axis='columns')
data_with_regions.head()

new_df = pd.melt(data_with_regions, id_vars=['country', 'region'])
new_df.head()

columns = ['country', 'region', 'year', 'co2']
new_df.columns = columns

upd_new_df = new_df[new_df['year'].astype('int64') > 1963]
upd_new_df.info()
upd_new_df = upd_new_df.sort_values(by=['country', 'year'])
upd_new_df['year'] = upd_new_df['year'].astype('int64')

df_gdp = gapminder[['Country', 'Year', 'gdp']]
df_gdp.columns = ['country', 'year', 'gdp']
df_gdp.info()

final_df = pd.merge(upd_new_df, df_gdp, on=['country', 'year'], how='left')
final_df = final_df.dropna()
final_df.head()

np_co2 = np.array(final_df['co2'])
np_gdp = np.array(final_df['gdp'])
np.corrcoef(np_co2, np_gdp)

Кстати, выбросы СО2 и ВВП коррелируют, и довольно существенно – 0,78.

np.corrcoef(np_co2, np_gdp)
Out[138]:
array([[1. , 0.78219731],
[0.78219731, 1. ]])

А теперь давайте перейдем к части визуализации. И снова мы начинаем с необходимого импорта. Я все дальше объясню. Сейчас просто расслабься и импортируй.

from bokeh.io import curdoc
from bokeh.plotting import figure
from bokeh.models import HoverTool, ColumnDataSource, CategoricalColorMapper, Slider
from bokeh.palettes import Spectral6
from bokeh.layouts import widgetbox, row

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

regions_list = final_df.region.unique().tolist()
color_mapper = CategoricalColorMapper(factors=regions_list, palette=Spectral6)

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

На самом базовом уровне ColumnDataSource – это просто проекция между именами столбцов и списками данных. ColumnDataSource принимает параметр data, который является словарем, с именами столбцов как ключи и списки (или массивы) значений данных как значения. Если один позиционный аргумент передается в инициализатор ColumnDataSource, он будет принят как data (с официального сайта).

# Make the ColumnDataSource: source
source = ColumnDataSource(data={
'x': final_df.gdp[final_df['year'] == 1964],
'y': final_df.co2[final_df['year'] == 1964],
'country': final_df.country[final_df['year'] == 1964],
'region': final_df.region[final_df['year'] == 1964],
})

Мы начинаем с выборки наших данных только за один год и создаем словарь значений для x, y, страны и региона.

Следующим шагом является установление лимитов для наших осей. Мы можем это сделать, найдя минимальные и максимальные значения для “X” и “Y”.

# Save the minimum and maximum values of the gdp column: xmin, xmax
xmin, xmax = min(final_df.gdp), max(final_df.gdp)

# Save the minimum and maximum values of the co2 column: ymin, ymax
ymin, ymax = min(final_df.co2), max(final_df.co2)

После этого мы создаем нашу фигуру, где мы будем размещать все наши объекты визуализации. Даем ей название, устанавливаем ширину и высоту, а также устанавливаем оси. (Вот “Y” настроена на тип “log” только для лучшего вида – несколько типов были испытаны, и этот дал лучший результат)

# Create the figure: plot
plot = figure(title='Gapminder Data for 1964',
plot_height=600, plot_width=1000,
x_range=(xmin, xmax),
y_range=(ymin, ymax), y_axis_type='log')

Bokeh использует определение глифа для всех визуальных фигур, которые могут появиться на графике. Полный список символов, встроенных в Bokeh приводится ниже (не изобретая ничего – вся информация с официальной страницы):

Все эти глифы имеют общий минимальный интерфейс через базовый класс Glyph.

Мы не будем слишком углубляться со всеми этими формами и используем круг как один из самых простых глифов. Если вы хотели бы поиграть больше с другими формами, у вас есть вся необходимая документация и ссылки.

# Add circle glyphs to the plot
plot.circle(x='x', y='y', fill_alpha=0.8, source=source, legend='region',
color=dict(field='region', transform=color_mapper),
size=7)

Как же добавить эти круги? Задаем наш источник в параметре «source» глифа круг, указываем данные для «X» и «Y», добавляем условные обозначения для цветов и применяем ранее созданный ColorMapper для параметра «color», «fill_alpha» устанавливает незначительную прозрачность и “size” – это размер кругов, которые появятся на фигуре.

Далее мы улучшаем внешний вид нашей фигуры, устанавливая местоположение легенды и давая некоторые пояснения нашим осям.

# Set the legend.location attribute of the plot
plot.legend.location = 'bottom_right'

# Set the x-axis label
plot.xaxis.axis_label = 'Income per person (Gross domestic product per person adjusted for differences in purchasing power in international dollars, fixed 2011 prices, PPP based on 2011 ICP)'

# Set the y-axis label
plot.yaxis.axis_label = 'CO2 emissions (tonnes per person)'

На данный момент у нас есть основной и статический график за 1964 год, но в названии статьи есть слово, которое не соответствует данной ситуации “Интерактивный” O_O. Итак, давайте добавим немного интерактивности!

Для этого мы добавим ползунок с годами, поэтому в конце у нас будет визуализация для каждого имеющегося года. Cool! не так ли?

Ранее мы импортировали класс Slider, теперь пришло время его использовать! Таким образом, мы создаем объект этого класса со следующими параметрами: “start” – минимальный год, “end” – максимальный, “value” (начальное значение) – минимальный год снова, “step” (как быстро меняются значения на слайдере) – 1 год и “title”.

Также мы создаем обратный вызов для любых изменений, которые происходят на этом регуляторе. Обратные вызовы в Bokeh всегда имеют одинаковые входные параметры: attr, old, new. Мы будем обновлять наш источник данных на основе значения ползунка. Поэтому мы создаем новый словарь, который будет отвечать году с ползунка и на основе этого мы будем обновлять наш график. Также мы обновляем заголовок графика соответственно.

# Make a slider object: slider
slider = Slider(start=min(final_df.year), end=max(final_df.year), step=1, value=min(final_df.year), title='Year')


def update_plot(attr, old, new):
# set the `yr` name to `slider.value` and `source.data = new_data`
yr = slider.value

new_data = {
'x': final_df.gdp[final_df['year'] == yr],
'y': final_df.co2[final_df['year'] == yr],
'country': final_df.country[final_df['year'] == yr],
'region': final_df.region[final_df['year'] == yr],
}
source.data = new_data

# Add title to figure: plot.title.text
plot.title.text = 'Gapminder data for %d' % yr


# Attach the callback to the 'value' property of slider
slider.on_change('value', update_plot)

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

# Create a HoverTool: hover
hover = HoverTool(tooltips=[('Country', '@country'), ('GDP', '@x'), ('CO2 emission', '@y')])

# Add the HoverTool to the plot
plot.add_tools(hover)

HoverTool принимает список из tuples где первым значением является заголовок, а вторым – детали из источника данных.

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

# Make a row layout of widgetbox(slider) and plot and add it to the current document
layout = row(widgetbox(slider), plot)
curdoc().add_root(layout)

Мы это сделали! Поздравляю! Мы запускаем этот код и … Ничего. Никаких ошибок (или, возможно, некоторые ошибки, но потом ты их исправляешь, и больше нет ошибок) и ни программы, ни визуализации O_o. Почему, черт возьми, я потратил все это время на создание крутого графику и ничего не получилось? Даже нет объяснения, что я сделал неправильно.

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

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

bokeh serve --show my_python_file.py

И он автоматически откроет визуализацию в новой вкладке браузера.

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

Спасибо за твое внимание, надеюсь, что это маленькое вступление в Bokeh будет полезным для тебя и замечательного тебе дня! (Или ночи, если ты читаешь это перед сном: D)

P.S. Хочу попробовать еще и plotly, видел много положительных отзывов о нем.

P.S.S. Код на Github.


Фото Yosh Ginsu на Unsplash

Karma +1 when you share it:
Опубликовано в категории: Data Science

Leave a Reply

Your email address will not be published. Required fields are marked *