Інтерактивна візуалізація даних з 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

1 про “Інтерактивна візуалізація даних з Python за допомогою Bokeh”

  1. Hola,
    Estoy tratando de descargarme los dos ficheros:
    Así que tomé dos archivos: uno con emisiones de CO2 de Gapminder.org y otro del curso en DataCamp (porque ese archivo ya estaba preprocesado 😀 yeeeeees, soy un bastardo perezoso 😀 ). También puedes descargar estos archivos desde aquí

    pero no puedo. Me los podrías enviar?

    Muchas gracias

Leave a Reply

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