Nauč se Python > Kurzy > Datový kurz PyLadies > Interaktivní vizualizace a aplikace > Interaktivní vizualizace a aplikace I.

Interaktivní vizualizace a aplikace

Při práci s daty je mnoho příležitostí, kdy se hodí interaktivita. Při vizualici se hodí zvětšování / změnšování měřítka, výběr podoblasti, ukázání vykreslených hodnot apod. Nebo při datové anlýze obecně se může hodit interaktivně v notebooku měnit nějaký parametr (třeba hyperparametr pro strojové učení). Anebo chceme dát výsledky naší skvělé analýzy k dispozici "netechnickým" kolegům nebo kamarádům, kteří (zatím) nedokáží Jupyter notebook spustit.

Tady si ukážeme, jak si s takovými úkoly poradit pomocí dvou nástrojů: plotly, resp. především plotly express, a panel.

Existují i další nástroje, které poskytují podobné možnosti. Podrobný přehled najdete na https://pyviz.org/tools.html. Na interaktivní vizualizace jsou to především holoviews nebo altair. Na "dashboarding" pak dash, streamlit, voila nebo justpy.

Každý z těchto nástrojů má, jako obvykle, své výhody a nevýhody. Nejrozšířenějším nástrojem je Dash ze stejné dílny jako plotly, který poskytuje i enterprise řešení pro provoz aplikací. Dash je určitě dobrou volbou, jak se můžete dozvědět i na přednášce z pražského PyData Meetupu. Panel (a také Voila) se od Dash liší tím, že je lze použít i v Jupytere notebooku a pak notebook použít přímo jako aplikaci - a proto Panel použijeme my v této lekci.

Pár článků či přednášek, které se tématu týkají:

Pro a proti

Je potřeba ale říct, že všechny tyto přístupy mají své výrazné nevýhody a limity a nehodí se pro velké a složité aplikace. Možnosti interakcí v aplikaci jsou omezené a také mohou být pomalé. Robustní škálování pro mnoho uživatelů (velký provoz) je obecně složitější. Kdy tedy především použít co si tady ukážeme?

  • Na malou aplikaci pro omezený počet uživatelů (dashboard pro kolegy).
  • Na rychlý vývoj prototypu.

A co když chceme budovat velkou (webovou) aplikaci?

  • Zadáme vývojářskému týmu, aby v moderních JavaScript nástrojích typu React nebo Vue.js pro nás vytvořil krásný a rychlý "front-end", zatímco my vytvoříme v Pythonu "back-end", který s front-endem bude komunikovat např. pomocí JSON API, tak jak jsme viděli v naší lekci o API.
  • Když takový tým nemáme, naučíme se programovat JavaScriptu ... Ne, raději v TypeScriptu ...
  • ... raději najmeme ten vývojářský tým :-)

Instalace a import grafických knihoven

Pokud nemáte nainstalované knihovny plotly a panel, odkomentujte a spusťe příslušné řádky.

In [1]:
# instalace plotly
# %pip install plotly
In [2]:
# instalace panel
# POZOR: nainstalujte tuto (v době vzniku nejnovější) verzi, 
# s některými předešlými verzemi byl problém v násleném provozu na heroku

# %pip install bokeh==2.0.2 panel==0.9.5

Pro plotly express a panel se vžily zkratky px a pn, pod kterými je naimportujeme i my. Dále je pro použití panelu v notebooku potřeba zavolat pn.extension, v našem případě s argumentem "plotly" aby tyto dvě knihovny správně spolupracovaly. Je doporučené to udělat hned na začátku notebooku, resp. před použitím plotly nebo panel, jinak může dojít k problémům v zobrazování notebooku.

In [ ]:
import plotly.express as px
import panel as pn

pn.extension("plotly")

COVID-19 dashboard

Z onemocnění COVID-19 je v době vzniku tohoto kurzu většina z nás už dost nešťastná. Poskytuje ale příležitost k analýzám dat a vytváření vizualizací a dashboardů. Jejich přehled a hodnocení přináší např. i článek z MIT: The best, and the worst, of the coronavirus dashboards. Uvidíme, jestli se do příštího vydání dostane a náš dashboard :)

Data

Použijeme data z https://github.com/datasets/covid-19 (lehce přeformátovaná data z Johns Hopkins University: https://github.com/CSSEGISandData/COVID-19).

In [4]:
import pandas as pd
In [5]:
covid_data_raw = pd.read_csv(
    "https://raw.githubusercontent.com/datasets/covid-19/master/data/time-series-19-covid-combined.csv",
    parse_dates=["Date"],
).rename(columns={"Province/State": "State", "Country/Region": "Country"})
In [6]:
covid_data_raw
Out[6]:
Date Country State Lat Long Confirmed Recovered Deaths
0 2020-01-22 Afghanistan NaN 33.0 65.0 0.0 0.0 0.0
1 2020-01-23 Afghanistan NaN 33.0 65.0 0.0 0.0 0.0
2 2020-01-24 Afghanistan NaN 33.0 65.0 0.0 0.0 0.0
3 2020-01-25 Afghanistan NaN 33.0 65.0 0.0 0.0 0.0
4 2020-01-26 Afghanistan NaN 33.0 65.0 0.0 0.0 0.0
... ... ... ... ... ... ... ... ...
28989 2020-05-05 Zimbabwe NaN -20.0 30.0 34.0 5.0 4.0
28990 2020-05-06 Zimbabwe NaN -20.0 30.0 34.0 5.0 4.0
28991 2020-05-07 Zimbabwe NaN -20.0 30.0 34.0 5.0 4.0
28992 2020-05-08 Zimbabwe NaN -20.0 30.0 34.0 9.0 4.0
28993 2020-05-09 Zimbabwe NaN -20.0 30.0 35.0 9.0 4.0

28994 rows × 8 columns

Pro některé země je v datech více regionů. My si ale data agregujeme podle zemí dohromady.

In [7]:
covid_data = (
    covid_data_raw
    .groupby(["Country", "Date"])[["Confirmed", "Recovered", "Deaths"]]
    .agg("sum")
    .reset_index()
)
covid_data
Out[7]:
Country Date Confirmed Recovered Deaths
0 Afghanistan 2020-01-22 0.0 0.0 0.0
1 Afghanistan 2020-01-23 0.0 0.0 0.0
2 Afghanistan 2020-01-24 0.0 0.0 0.0
3 Afghanistan 2020-01-25 0.0 0.0 0.0
4 Afghanistan 2020-01-26 0.0 0.0 0.0
... ... ... ... ... ...
20378 Zimbabwe 2020-05-05 34.0 5.0 4.0
20379 Zimbabwe 2020-05-06 34.0 5.0 4.0
20380 Zimbabwe 2020-05-07 34.0 5.0 4.0
20381 Zimbabwe 2020-05-08 34.0 9.0 4.0
20382 Zimbabwe 2020-05-09 35.0 9.0 4.0

20383 rows × 5 columns

Interaktivní vizualizace

Zajímá nás průběh jedné konkrétní země - třeba Česka :)

In [8]:
# uložíme si to do proměnné, abychom to pak nespletli
country = "Czechia"
In [9]:
covid_selected_country = covid_data.query(f'Country == "{country}"')
covid_selected_country.tail()
Out[9]:
Country Date Confirmed Recovered Deaths
5118 Czechia 2020-05-05 7896.0 4006.0 257.0
5119 Czechia 2020-05-06 7974.0 4205.0 262.0
5120 Czechia 2020-05-07 8031.0 4371.0 270.0
5121 Czechia 2020-05-08 8077.0 4413.0 273.0
5122 Czechia 2020-05-09 8095.0 4447.0 276.0

Určitě bude zajímavé si data vykreslit v grafu. Zatím jsme používali hlavně knihovnu matplotlib. Ta toho umí opravdu hodně, navíc jde používat přímo pomocí DataFrame.plot. Chybí jí ale vlastnost, která je pro datovou analýzu a hlavně pak pro webové aplikace nesmírně užitečná: interaktivita. (Ona tedy nechybí úplně, ale je velmi omezená.)

Interaktivita nám umožňuje bohatší práci s vizualizacemi. Můžeme si zvětšit nějakou podoblast grafu. Můžeme zvětšovat / zmenšovat měřítko aby byly vidět třeba i malé rozdíly v hodnotách. V lekci a PCA jsme použili interakticní popisky jednotlivých symbolů v grafu na identifikaci příslušného záznamu v datech. A mnoho dalšího.

S plotly, resp. plotly-express, už jsme trochu pracovali. Pojďme tedy tuto knihovnu rovnou použít!

In [ ]:
px.bar(
    covid_selected_country,
    x="Date",
    y="Confirmed",
    title=f"Confirmed cases in {country}",
)

Teď byste měli v notebooku vidět graf - vývoj případů v Česku. Pojďme si s grafem chvilku hrát :)

Vyzkoušejte:

  • Vybrat a vyzkoušet nástroje v pravém horním rohu na zoom (změnu měřítka), pan (posun) nebo reset axis.
  • Zkuste se zastavit s kurzorem nad jedním ze slopců v grafu.

Úkol:

  1. Vykreslete průběh počtu nakažených pro Česko, Rakousko a Finsko v jednom grafu, každou zemi jinou barvou (použijte argument color). Na filtrování dat si vzpomeňte na .isin.
  2. Změňte veličinu na počet uzdravených.
  3. Změňte typ grafu na čárový (line).

Dokázali jsme si vytvořit celkem pěknou vizualizaci základních veličin, popisujích průběh pandemie COVID-19. Museli jsme si ale ručně vybírat a přepínat parametry vizualizace, např. výběr zemí nebo vykreslované veličiny.

Praktičtější by určitě bylo mít nějaké "klikátko" - "vybírávátko", které by nám parametry umožnilo nastavit.

Widgets - pomocné "věcičky"

V uživatelských grafických rozhraních (GUI), zejména těch používajících webové technologie, se právě pro tyto přiležitosti hodí widgety (český výraz se nepoužívá a ani pořádně neexistuje).

A jelikož máme to štěstí, že notebook má webové grafické rozhraní, můžeme widgety přidat přímo do něj. Jednou z možností je použít knihovnu panel.

Vytvoříme si rovnou úplně jednoduchý widget na výběr zěmě:

In [11]:
country_selection = pn.widgets.Select(options=sorted(covid_data["Country"].unique()), name="Country")

A teď si ho zobrazíme přího v notebooku:

In [ ]:
country_selection

V tuto chvíli můžete vybírat se zeznamu všech zemí, které jsou v našich datech. To je pohled vás jako uživatele. Ale co se stane z pohledu programátora, tedy co se stane v Pythonu?

Objekt country_selection nám nabízí několik atributů a metod, které nám umožňují zjistit, co uživatel udělal. V tomto případě se nejvíc hodí zjistit, jakou zemi uživatel vybral. A na to je .value.

In [13]:
country_selection.value
Out[13]:
'Afghanistan'

Úkol: Vyberte v buňce nahoře svou (ne)oblíbenou zemi a podívejte se, jestli se změnila hodnota country_selection.value. Je potřeba spouště znovu buňku, která zobrazuje country_selection widget? Co se stane, když jí spustíte znovu (třeba v jiné buňce)?

country_selection.value už je něco, co můžeme použít pro vykreslení grafu podobně jako před chvilkou:

In [ ]:
px.bar(
    covid_data.query(f'Country == "{country_selection.value}"'),
    x="Date",
    y="Confirmed",
    title=f"Confirmed cases in {country_selection.value}",
)

Teď jsme mohli vybrat jeden parametr. Hodilo by se nám ale vybírat více parametrů různých typů: výběr více zemí, vykreslená veličina, přepínač logaritmické škály nebo třeba typ zobrazení (bar nebo line plot). S volbou vhodných widgetů nám pomůže dokumentace panel (pozor, některé widgety nejsou v naší starší verzi dostupné). Abychom nemuseli každý widget zobrazovat zvlášť, použijeme komponenty z kategorie Layouts, nyní konkrétně Column.

In [15]:
# widget pro výběr více možností ze seznamu
countries_selection = pn.widgets.CrossSelector(
    name="Countries", options=sorted(covid_data["Country"].unique()), value=["Czechia"]
)
# výběr vykreslené veličiny
plotted_variable = pn.widgets.Select(
    name="Plotted variable", options=["Confirmed", "Recovered", "Deaths"]
)
# přepínač logaritmické škály
use_log_scale = pn.widgets.Checkbox(name="Use log scale", value=False)
# výběr typu grafu
plot_type = pn.widgets.Select(
    name="Plot type", options={"bar": px.bar, "line": px.line}
)

A teď to dáme vše dohromady pro zobrazení nad sebou, tj. do sloupce, a rovnou zobrazíme.

In [ ]:
selections = pn.Column(countries_selection, plotted_variable, use_log_scale, plot_type)

selections

Pokud chceme dát dvě komponenty vedle sebe, dáme je do jedné řady nebo Row:

In [ ]:
selections = pn.Column(
    countries_selection, pn.Row(plotted_variable, plot_type), use_log_scale
)

selections

A teď už můžeme vybrané možnosti použít:

In [ ]:
plot_type.value(
    covid_data.loc[covid_data["Country"].isin(countries_selection.value)],
    x="Date",
    y=plotted_variable.value,
    color="Country",
    log_y=use_log_scale.value,
)

Provázání uživatelských vstupů a výstupu

Naší aplikaci teď chybí už jen "maličkost" - provázat uživatelské vstupy s výstupem tak, aby se výstup automaticky aktualizoval. Aby se nám to podařilo, budeme muset náš kód zabalit do funkce, která aplikaci (dashboard) vytváří.

In [19]:
# argumenty funkce NEJSOU widgety
def create_dashboard(countries, plot_variable, plot_function, log_y):
    return plot_type.value(
        covid_data.loc[covid_data["Country"].isin(countries)],
        x="Date",
        y=plot_variable,
        color="Country",
        log_y=log_y,
    )

Funkci můžeme vyzkoušet a předat jí aktuální hodnoty z widgetů. Tím ale stále ještě nemáme interaktivní aplikaci.

In [ ]:
create_dashboard(
    countries=countries_selection.value,
    plot_variable=plotted_variable.value,
    plot_function=plot_type.value,
    log_y=use_log_scale.value,
)

Tím zbývajícím kouskem skládačky k interaktivní aplikaci je funkce interact. Ta nám "obalí" naši funkci, v tomto případě create_dashboard, a sváže hodnoty widgetů se vstupními argumenty naší funkce (která generuje grafický výstup na základě hodnot vstupních parametrů).

In [21]:
# nejprve interaktivní dashboard vytvoříme
interactive_dashboard = pn.interact(
    create_dashboard,
    countries=countries_selection,
    plot_variable=plotted_variable,
    plot_function=plot_type,
    log_y=use_log_scale,
)
In [ ]:
# a poté zobrazíme
interactive_dashboard

Teď si zkuste měnit hodnoty pomocí widgetů - výstup se bude hned měnit (tedy skoro hned - takto vytvořená aplikace není nejsvižnější - to je drobá daň za jednoduchust implementace).

Z notebooku k samostatné aplikaci

Zatím jsi si vše zprovoznil(a) ve svém notebooku. To je výborné, ale má to pořád ještě drobnou nevýhodu - poslat takový notebook někomu, kdo si pod pojmem Python vybaví pouze 🐍, je riskantní. Když totiž jako nápovědu uslyší, že si toho hada má pustit, nejspíš se lekne a uteče. Místo toho bychom raději předali jen odkaz na webovou "stránku", tedy na náš dashboard.

Začneme tím, že si aplikaci spustíme na svém počítači. To jde naštěstí velice jednoduše, dokonce můžeme použít přímo tento notebook. Jen musíme nějak vysvětlit, co přesně v té aplikaci má být. Na to slouží metoda servable.

In [ ]:
interactive_dashboard.servable(title="Můj COVID-19 dashboard")

V notebooku se nic nezměnilo, můžeme ale spustit notebook jako samostatnou aplikaci. V příkazové řádce to zajistí

bokeh serve dashboards.ipynb

Zobrazí se nám něco jako

2020-04-13 09:31:13,787 Starting Bokeh server version 1.4.0 (running on Tornado 6.0.4)
2020-04-13 09:31:13,799 User authentication hooks NOT provided (default user enabled)
2020-04-13 09:31:13,802 Bokeh app running at: http://localhost:5006/dashboards
2020-04-13 09:31:13,802 Starting Bokeh server with process id: 9220

Na třetím řádku jistě rozeznáte webový odkaz. Pravděpodobně tam bude http://localhost:5006/dashboards. Pokud tuto adresu otevřeš v prohlížeči, zobrazí se poslední verze našeho COVID dashboardu - ta, u které jsme zavolal metodu .servable().

Publikujeme na internet

V principu bychom mohli spustit aplikaci u sebe na počítači tak, aby ji mohli používat i další uživatelé. Na vnitřní síti (domácí, pracovní) by to bylo snadné (i když na pracovní síti a pracovním počítači by tomu mohla bránit bezpečnostní nastavení), přístup z vnějšího internetu by už byl komplikovanější.

Naštěstí nejsme v podobné situaci sami :) Takže existují více či méně složité a sofistikované způsoby, jak aplikaci spustit na nějakém serveru (v cloudu) a zpřístupnit z internetu. My si ukážeme, jak to funguje na Heroku. Podobné služby nabízí třeba AWS (Elastic Beanstalk), Google App Engine nebo Dokku on Digital Ocean. Výhodou Heroku je jednoduchost a možnost bezplatných služeb, nevýhodou pak rychle rostoucí cena a omezené možnosti.

Budeme postupovat v podstatě podle průvodce Getting started with Python.

Registrace a klient Heroku

Než začneme, je potřeba:

  1. Založit si bezplatný účet na https://signup.heroku.com/signup/dc.
  2. Nainstalovat Git:
  3. Alespoň základní konfigurace Gitu. V příkazovém řádku (vyplňte své jméno a email):
    git config --global user.name "Moje Jméno"
    git config --global user.email "muj@email.com"
  4. Nainstalovat si Heroku klienta:

Instalaci ověříme v příkazové řádce pomocí:

  1. Git:

    git config --list

    Mělo by se objevit něco na způsob

    user.name=Moje Jméno
    user.email=muj@email.com
  2. Heroku:

    heroku --version

    Tady by mělo být výstupem zhruba

    heroku/7.39.2 darwin-x64 node-v12.13.0

Go-live

Teď budeme chvilku pracovat v příkazové řádce. Než začnete psát příkazy, ujistěte se, že jste ve složce s tímto netebookem. Ideálně by v této složce měl být (zatím) jen tento notebook. Pokud to tak není, můžete si vytvořit složku novou a notebook tam přesunout nebo zkopírovat.

A můžeme začít:

  1. Musíme vytvořit Git repozitář:

    git init
  2. Pro Heroku potřebuje vytvořit soubor s názvem Procfile. Ten velice jednoduše říká, co že to vlastně chceme spustit. Bude tam příkaz bokeh serve (tak jako jsme spouštěli aplikaci lokálně) s pár přepínači navíc. Konkrétně soubor Procfile musí obsahovat tento řádek:

    web: bokeh serve --port=$PORT --allow-websocket-origin="*" --address=0.0.0.0 dashboards.ipynb
  3. Dalším souborem, který potřebujeme, je requirements.txt. O tom jste už možná slyšeli(y) - obsahuje seznam Python balíčků, které potřebuje daný Python projekt. Heroku tento soubor použije, aby před spuštěním aplikace nainstaloval vše potřebné. Pro náš dashboard musí býv v requirements.txt toto:

    bokeh==2.0.2
    panel==0.9.5
    pandas==1.0.3
    notebook==6.0.3
    plotly==4.5.4
  4. Soubory už máme připravené, potřebujeme je teď přidat do Git repozitáře. Na to použij tyto dva příkazy:

    git add dashboards.ipynb Procfile requirements.txt
    git commit -v -m "první verze dashboardu"
  5. A teď už můžeme publikovat. Tedy nejdříve se přihlásit pomocí

    heroku login

    (Postupujte dle pokynů - otevře se přihlašovací stránka ve webovém prohlížeči, která vás vyzve k zadání přihlašovacích údajů.)

  6. Pak ještě vytvořit Heroku aplikaci pomocí

    heroku create
  7. A nakonec už opravdu vypustit aplikaci na internet pomocí

    git push heroku master

Pokud vše půjde dobře, tento krok bude chvíli trvat. Postupně se bude vypisovat, co pro nás Heroku dělá:

Enumerating objects: 7, done.
Counting objects: 100% (7/7), done.
Delta compression using up to 4 threads
Compressing objects: 100% (4/4), done.
Writing objects: 100% (4/4), 940.72 KiB | 2.40 MiB/s, done.
Total 4 (delta 2), reused 0 (delta 0)
remote: Compressing source files... done.
remote: Building source:
remote:
remote: -----> Python app detected
remote: -----> Clearing cached dependencies
remote: -----> Installing python-3.8.2
remote: -----> Installing pip
remote: -----> Installing SQLite3
remote: Sqlite3 successfully installed.
remote: -----> Installing requirements with pip
remote:        Collecting bokeh==1.4.0
remote:          Downloading bokeh-1.4.0.tar.gz (32.4 MB)
remote:        Collecting panel==0.8.3
...

Nakonec by se mělo objevit něco jako

remote: -----> Launching...
remote:        Released v20
remote:        https://banana-sundae-72768.herokuapp.com/ deployed to Heroku
remote:
remote: Verifying deploy... done.
To https://git.heroku.com/banana-sundae-72768.git
   2652f01..8d65612  master -> master

Důležitý je odkaz pod řádkem "Released v20". To je totiž adresa, na které je náš dashboard dostupný "odkudkoli z internetu". Buď si ji zkopírujte do prohlížeče nebo použijte příkaz heroku open.


Toto je stránka lekce z kurzu, který probíhá nebo proběhl naživo s instruktorem.