Použití interact
bylo opravdu přímočaré. Pro složitější aplikace už by ale byl samotný interact
příliš omezující. Proto jsou v knihovně panel
další možnosti, jak naprogramovat interaktivní aplikaci.
My teď použijeme modul param
. Další, ještě flexibilnější možnosti, jsou pak popsány v https://panel.holoviz.org/user_guide/Links.html.
Vytvoříme třídu CovidDashboard
, která bude funkcionalitou stejná jako dashboard s předchozí části. Čeho si hlavně všimněte:
param.Parameterized
.countries
, plotted_variable
, use_log_scale
, plot_type
), jsou na úrovní třídy (class attributes), tj. nevytvářejí se v metodě __init__
.param
, které nepopisují přímo widgety, ale typ dat (Selector
, Boolean
, ListSelector
)..value
jako u widgetů.__init__
.Závislost na stavu jednotlivých atributů vyjadřujeme pomocí dekorátoru param.depends
, který funguje podobně jako interact
.
Aplikaci vytváříme až v metodě view
(jméno metody je libovolné). To je také jediná metoda, kde se přímo použije modul panel
(pn
). V této metodě je pár pozoruhodných věcí:
pn.Row
dáváme funkce aniž bychom je volali (nejsou tam závorky) - panel
si je volá vnitřně až když potřebuje vykreslit aplikaci.self.param
, kterou jsme nijak nedefinovali. To je metoda třídy Parameterized
, která vytvoří widgety ze všech těch atributů typu Selector
, Boolean
atd., v závislosti na jejich typech a vlastnostech.import datetime as dt
import pandas as pd
import numpy as np
import param
import panel as pn
import plotly.express as px
pn.extension("plotly")
# staré známé načítání dat
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"})
covid_data = (
covid_data_raw
.groupby(["Country", "Date"])[["Confirmed", "Recovered", "Deaths"]]
.agg("sum")
.reset_index()
)
class CovidDashboard(param.Parameterized):
"""Simple COVID-19 dashboard
"""
# atributy na úrovni třídy - popisují data použitá pro interaci
countries = param.ListSelector(
default=["Czechia"], objects=sorted(covid_data["Country"].unique())
)
plotted_variable = param.Selector(
default="Confirmed", objects=["Confirmed", "Recovered", "Deaths"]
)
use_log_scale = param.Boolean(doc="Use log scale", default=False)
plot_type = param.Selector(
doc="Plot type", objects={"bar": px.bar, "line": px.line}
)
# param.depends zařizuje interaktivní závislot
@param.depends("countries", "plotted_variable", "use_log_scale", "plot_type")
def plot(self):
"""Plot data using plotly
"""
# tady už používáme self.countries atd., kde jsou k dispozici aktuální hodnoty
return self.plot_type(
covid_data.loc[covid_data["Country"].isin(self.countries)],
x="Date",
y=self.plotted_variable,
color="Country",
log_y=self.use_log_scale,
)
def view(self):
"""Create panel interactive application
"""
return pn.Row(self.param, self.plot)
Když vytvoříme instanci této třídy, ještě žádnou aplikaci nevytvoříme.
covid_dashboard = CovidDashboard()
covid_dashboard
Až teprve metoda view
vytváří aplikaci, v našem případě (skoro) stejnou jako jsme vytvořili pomocí interact
.
covid_dashboard.view()
Úkol: Změňte typ atributu countries
, aby šla vybrat pouze jedna země.
Pojďme náš dashboard trochu vylepšit.
# Odkomentujte pro instalaci iso3166 - potřebujeme pro ISO kódy států
# %pip install iso3166
import iso3166
def country_alpha3(country):
try:
return iso3166.countries.get(country).alpha3
except KeyError:
return None
Načítání dat si zabalíme do funkce - použijeme poté v třídě pro dashboard.
def get_covid_data():
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"})
covid_data = (
covid_data_raw
.groupby(["Country", "Date"])
.agg({"Confirmed": "sum", "Recovered": "sum", "Deaths": "sum", "Lat": "first", "Long": "first"})
.reset_index()
.assign(iso_alpha=lambda df: df["Country"].map(country_alpha3))
)
return covid_data
V aplikacích, které jsou spuštěné někde v oblacích, je velmi užitečné logovat co se děje. Pomůže nám to při odhalováních chyb, ladění rychlosti nebo obecně sledování kdo / jak aplikaci používá.
import logging
logging.basicConfig(level=logging.INFO)
Do našeho vylepšeného dashboardu přidáme následující:
class GreatCovidDashboard(param.Parameterized):
"""Almost the best COVID dashboard, (C) PyDataLadies 2020
"""
# výběr zemí - možnosti doplníme podle dat
countries = param.ListSelector(default=["Czechia"], objects=["Czechia"])
# výběr zobrazené veličiny
plotted_variable = param.Selector(
default="Confirmed", objects=["Confirmed", "Recovered", "Deaths"]
)
# přepínání logaritmického měřítka
use_log_scale = param.Boolean(doc="Use log scale", default=False)
# výběr typu grafu
plot_type = param.Selector(
doc="Plot type", objects={"bar": px.bar, "line": px.line}
)
# výběr datumu pro zobrazení dat na mapě
map_plot_date = param.Date(
# vybrané datum i meze změníme v update_covid_data na základě dat
dt.date(2020, 4, 1),
bounds=(dt.date(2020, 1, 1), dt.date(2020, 5, 1)),
)
def __init__(self, **params):
# vytvoříme si logger pro tuto třídu
self._logger = logging.getLogger("GreatCovidDashboard")
self._logger.info("Initializing")
# musíme zavolat i __init__ mateřské třídy
super().__init__(**params)
# "skrytý" atributu _covid_data aktualizujeme v metodě update_covid_data
self._covid_data = None
self.update_covid_data()
# na začátek vybereme poslední datum k zobrazení
self.map_plot_date = self._covid_data["Date"].max().date()
self._logger.info("Initialized")
def update_covid_data(self):
# načteme aktuální data do "skrytého" atributu _covid_data
self._logger.info("Loading COVID data")
self._covid_data = get_covid_data()
# a změníme parametry vybírátka data
self.param.map_plot_date.bounds = (
self._covid_data["Date"].min().date(),
self._covid_data["Date"].max().date(),
)
self.param.countries.objects = sorted(self._covid_data["Country"].unique())
# propery nám pomůže udělat "chytrý" atribut covid_data
# který používá "skrytý" _covid_data a aktualizuje ho pokud je třeba
@property
def covid_data(self):
# načítat data chci jen pokud jsou zastaralá
if self._covid_data["Date"].max() < (dt.date.today() - dt.timedelta(days=1)):
self.update_covid_data()
return self._covid_data
@param.depends("countries", "plotted_variable", "use_log_scale", "plot_type")
def plot(self):
"""Plot time evolution for one or more countries
"""
return self.plot_type(
self.covid_data.loc[self.covid_data["Country"].isin(self.countries)],
x="Date",
y=self.plotted_variable,
color="Country",
log_y=self.use_log_scale,
)
@param.depends("plotted_variable", "use_log_scale", "map_plot_date")
def map_plot(self):
"""Map-plot of COVID data on a specific date
"""
# vytvoříme si pomocný dataframe jen pro vybrané datum
data = self.covid_data.loc[self.covid_data["Date"].dt.date == self.map_plot_date]
if self.use_log_scale:
# přidáme sloupec log(plotted_variable)
color_column = f"log({self.plotted_variable})"
data = data.assign(**{color_column: np.log10(data[self.plotted_variable])})
else:
color_column = self.plotted_variable
return px.choropleth(
data,
locations="iso_alpha",
color=color_column,
hover_name="Country",
hover_data=["Confirmed", "Recovered", "Deaths"],
color_continuous_scale=px.colors.sequential.Plasma,
)
@param.depends("map_plot_date", "countries")
def selected_day_numbers(self):
"""Overview table for the selected date
"""
data = self.covid_data.loc[
(self.covid_data["Date"].dt.date == self.map_plot_date)
& (self.covid_data["Country"].isin(self.countries))
]
return pn.Column(
pn.pane.HTML(f"Situation on {self.map_plot_date}"),
pn.pane.DataFrame(
data.set_index("Country")[["Confirmed", "Recovered", "Deaths"]]
),
)
def view(self):
self._logger.info("Creating dashboard view")
return pn.Column(
pn.pane.HTML("<h1>Great COVID-19 Dashboard</h1>"),
self.selected_day_numbers,
pn.Row(
pn.Param(self.param, widgets={"map_plot_date": pn.widgets.DatePicker,}),
self.plot,
),
self.map_plot,
)
A teď už můžeme dashboard vytvořit.
great_covid_dashboard = GreatCovidDashboard()
A nakonec zobrazit.
great_covid_dashboard.view().servable(title="Great COVID-19 Dashboard")
Zkuste teď náš nový krásný dashboard vylepšit a zprovoznit na internetu.
location
funkce choropleth
.A pokud bude zbyde čas, zkuste třeba
MultiChoice
.Anebo vymyslete a naprogramujte vlastní vylepšení :)