From 38a9cb39e801b62f985c76e32b36ac3083c85590 Mon Sep 17 00:00:00 2001 From: Benedikt Bastin Date: Sun, 17 Jan 2021 02:34:49 +0100 Subject: [PATCH] feat: Created dashboard and reworked folder structure --- dashboard_template.xhtml | 122 +++++++++++++++++++++++++ {Plots => data}/.gitignore | 0 plot.py | 129 +++++++++++++++++++++++---- requirements.txt | 2 + site/.gitignore | 4 + {Quellen => site/archive}/.gitignore | 0 site/rki-dashboard.css | 59 ++++++++++++ 7 files changed, 300 insertions(+), 16 deletions(-) create mode 100644 dashboard_template.xhtml rename {Plots => data}/.gitignore (100%) create mode 100644 site/.gitignore rename {Quellen => site/archive}/.gitignore (100%) create mode 100644 site/rki-dashboard.css diff --git a/dashboard_template.xhtml b/dashboard_template.xhtml new file mode 100644 index 0000000..aefde8a --- /dev/null +++ b/dashboard_template.xhtml @@ -0,0 +1,122 @@ + + + + + + + + + +

Dashboard

+

+ Quelle: Robert-Koch-Institut (RKI)
+ Stand: {{ stand }} +

+ +
+

+ Bislang wurden {{ total_vaccinations }} Impfungen1 innerhalb von {{ days_since_start }} Tagen2 vorgenommen. + Das entspricht einem Anteil von {{ total_vaccinations_percentage }} % der Bevölkerung3. +

+

+

+

+ Durchschnittlich fanden seit Start täglich {{ mean_vaccinations_daily }} Impfungen statt. + Mit dieser durschnittlichen Rate dauert es bis zum {{ mean_vaccinations_daily_herd_immunity }}, bis {{ herd_immunity }} %4 der Bevölkerung die erste Impfung erhalten haben, + und bis zum {{ mean_vaccinations_daily_done }} für 100 %. +

+

+ Am {{ last_date }} wurden {{ last_date_day_rate }} Impfungen vorgenommen. + Mit der Rate vom {{ last_date }} dauert es bis zum {{ last_date_day_rate_herd_immunity }} für {{ herd_immunity }} % und bis zum {{ last_date_day_rate_done }} für 100 %5. +

+

+ In den letzten sieben Tagen wurden {{ mean_vaccinations_last_seven_days }} Impfungen vorgenommen. + Mit dieser Rate dauert es bis zum {{ mean_vaccinations_last_seven_days_herd_immunity }} für {{ herd_immunity }} % und bis zum {{ mean_vaccinations_last_seven_days_done }} für 100 %. +

+
+
+

Was ist dieses Dashboard?

+

+ Dieses Dashboard soll einen Überblick über den aktuellen Stand sowie die Entwicklungen der Impfungen in Deutschland geben. + Inspiriert wurde es durch impfdashboard.de, ein Impfdashboard, das ebenfalls die Daten des Robert-Koch-Instituts auswertet und vom Bundesministerium für Gesundheit betrieben wird. +

+

+ Als zusätzliche Informationen rechnet es aber aus, wie lange es dauert könnte, bis die Impfungen abgeschlossen sind oder wenigstens eine Herdenimmunität erreicht wird (sofern die aktuelle Schätzung der für eine Herdenimmunität benötigten Bevölkerung korrekt ist). + Dafür wird einerseits die durchschnittliche Impfrate aller Tage und andererseits die Impfrate der letzten sieben Tage herangezogen, die noch zu impfende Bevölkerung ausgerechnet und diese durch die Impfrate geteilt. + Dadurch erhält man die lineare Extrapolation, wenn jeden weiteren Tag genau mit dieser Impfrate weitergeimpft würde. +

+
+
+

Wie genau sind die Daten?

+

+ Die Datenquelle ist das Robert-Koch-Institut. + Für die Meldungen der jeweiligen Tage gibt es immer etwas Meldeverzug, das heißt, dass teilweise Meldungen einige Tage im Nachhinein noch korrigiert werden. + Das allerdings für gewöhnlich nur nach oben, und bislang hat das auch nur wenig Unterschied verursacht. +

+

+ Für die Extrapolation sieht das etwas anders aus. + Die lineare Extrapolation nimmt natürlich an, dass sich an dem Vorgang des Impfens über einen längeren Zeitraum nichts signifikant ändert. + Diese Annahme ist aber falsch; + aktuell (Stand: 17. Januar 2021) sind die meisten Impfzentren beispielsweise noch nicht geöffnet, stattdessen sind mobile Impfteams unterwegs, die hauptsächlich die Altenheime besuchen. + Dadurch liegt die aktuelle Impfrate deutlich unter dem, was bei den Impfzentren unter voller Last zu erwarten ist. + Entsprechend darf man vorsichtig optimistisch hoffen, dass die Impfrate in den nächsten Wochen und Monaten noch stark ansteigen wird. + Dennoch wird es vermutlich ungefähr anderthalb Jahre dauern, bis die meisten Menschen in Deutschland geimpft wurden. +

+

+ Ich finde den Ausblick, auch wenn die Schätzung sehr unpräzise sein wird, dennoch interessant, deshalb habe ich das gemacht. +

+
+ +
+ + +
+ Abbildung 1: + Tägliche Impfquote, kumulierte Impfungen und lineare Extrapolation bis 10 % der Bevölkerung Deutschlands +
+ +
+ +
+ + +
+ Abbildung 2: + Tägliche Impfquote, kumulierte Impfungen und lineare Extrapolation bis 70 % der Bevölkerung Deutschlands +
+ +
+
+ + +
+ Abbildung 3: + Tägliche Impfquote, kumulierte Impfungen und lineare Extrapolation bis 100 % der Bevölkerung Deutschlands +
+ +
+ +
+

Fußnoten

+
    +
  1. Gezählt werden verabreichte Dosen, unabhängig ob erste oder zweite Dosis.
  2. +
  3. Starttermin der Impfungen war der 27. Dezember 2020.
  4. +
  5. Bevölkerungsstand vom 31. Dezember 2019: {{ einwohner_deutschland }}.
  6. +
  7. {{ herd_immunity }} % ist der Wert, bei dem aktuell von einer Herdenimmunität ausgegangen wird.
  8. +
  9. Die täglichen Impfraten unterliegen starken Schwankungen und sind daher wenig aussagekräftig.
  10. +
+
+ + diff --git a/Plots/.gitignore b/data/.gitignore similarity index 100% rename from Plots/.gitignore rename to data/.gitignore diff --git a/plot.py b/plot.py index 9ad3d08..559a938 100644 --- a/plot.py +++ b/plot.py @@ -10,14 +10,15 @@ import re import requests as req import locale import os.path +import shutil locale.setlocale(locale.LC_ALL, 'de_DE.UTF-8') -sources_folder = 'Quellen' -plots_folder = 'Plots' - +site_folder = 'site/' +data_folder = 'data/' einwohner_deutschland = 83190556 +herd_immunity = 0.7 today = datetime.date.today() print_today = today.isoformat() @@ -30,17 +31,18 @@ plt.rcParams["figure.figsize"] = [11.69, 8.27] # Download -filename = '{}/{}_Impfquotenmonitoring.xlsx'.format(sources_folder, filename_now) +data_filename = '{}/{}_Impfquotenmonitoring.xlsx'.format(data_folder, filename_now) r = req.get('https://www.rki.de/DE/Content/InfAZ/N/Neuartiges_Coronavirus/Daten/Impfquotenmonitoring.xlsx?__blob=publicationFile') -with open(filename, 'wb') as outfile: +with open(data_filename, 'wb') as outfile: outfile.write(r.content) -rki_file = pd.read_excel(filename, sheet_name=None, engine='openpyxl') + +rki_file = pd.read_excel(data_filename, sheet_name=None, engine='openpyxl') raw_data = rki_file['Impfungen_proTag'] @@ -61,10 +63,41 @@ days_extrapolated = int(np.ceil(to_be_vaccinated / mean_vaccinations_daily)) extrapolated_dates = np.array([dates[0] + datetime.timedelta(days=i) for i in range(days_extrapolated)]) +mean_vaccinations_daily_done = extrapolated_dates[-1] +mean_vaccinations_daily_herd_immunity = extrapolated_dates[int(np.ceil(days_extrapolated * herd_immunity))] + +days_extrapolated_with_todays_rate = int(np.ceil(to_be_vaccinated / daily.iloc[-1])) + +last_date = dates.iloc[-1] + +last_date_day_rate = daily.iloc[-1] +last_date_day_rate_done = dates[0] + datetime.timedelta(days=days_extrapolated_with_todays_rate) +last_date_day_rate_herd_immunity = dates[0] + datetime.timedelta(days=int(np.ceil(days_extrapolated_with_todays_rate * herd_immunity))) + + extrapolated_vaccinations = mean_vaccinations_daily * range(1, days_extrapolated + 1) +days_since_start = (dates.iloc[-1].date() - dates[0].date()).days +print(days_since_start) + +mean_vaccinations_last_seven_days = np.mean(daily[-7:]) +mean_vaccinations_last_seven_days_int = int(np.round(mean_vaccinations_last_seven_days)) + +days_extrapolated_last_seven_days = int(np.ceil(to_be_vaccinated / mean_vaccinations_last_seven_days)) + +extrapolated_vaccinations_last_seven_days = total_vaccinations + mean_vaccinations_last_seven_days * range(-days_since_start, days_extrapolated - days_since_start) + +mean_vaccinations_last_seven_days_done = dates.iloc[-1] + datetime.timedelta(days=days_extrapolated_last_seven_days) +mean_vaccinations_last_seven_days_herd_immunity = dates.iloc[-1] + datetime.timedelta(days=int(np.ceil(days_extrapolated_last_seven_days * herd_immunity))) + + + mean_vaccinations_daily_up_to_date = np.round(cumulative / range(1, len(cumulative) + 1)) +print(extrapolated_vaccinations[:20]) +print(extrapolated_vaccinations_last_seven_days[:20]) +print(cumulative) + # Stand aus Daten auslesen #stand = dates.iloc[-1] #print_stand = stand.isoformat() @@ -79,14 +112,22 @@ print_stand = stand_date.isoformat() filename_stand = stand_date.strftime("%Y%m%d%H%M%S") + +archive_folder = site_folder + 'archive/' + filename_stand + +if os.path.isdir(archive_folder): + print('Archive folder {} already exists'.format(archive_folder)) +else: + os.mkdir(archive_folder) + def plot_extrapolation_portion(percentage): print_percentage = int(percentage * 100) - plot_filename = '{}/{}_extrapolated_to_{}_percent'.format(plots_folder, filename_stand, print_percentage) - plot_filename_latest = '{}/latest_extrapolated_to_{}_percent'.format(plots_folder, print_percentage) + archive_plot_filename = '{}/extrapolated_to_{}_percent'.format(archive_folder, print_percentage) + latest_plot_filename = '{}/extrapolated_to_{}_percent'.format(site_folder, print_percentage) - if os.path.isfile(plot_filename + 'pdf'): - print('Plot {} already exists'.format(plot_filename)) + if os.path.isfile(archive_plot_filename + '.pdf'): + print('Plot {} already exists'.format(archive_plot_filename)) return fig, ax = plt.subplots(1) @@ -111,7 +152,8 @@ def plot_extrapolation_portion(percentage): ax2.set_xlim(xmax=dates[0] + datetime.timedelta(days=percentage * days_extrapolated)) ax2.grid(True) ax2.plot(dates, cumulative, color='red', label='Kumulierte Impfungen') - ax2.plot(extrapolated_dates, extrapolated_vaccinations, color='orange', label='Extrap. kumulierte Impfungen\n({:n} Impfungen/Tag)'.format(mean_vaccinations_daily_int)) + ax2.plot(extrapolated_dates, extrapolated_vaccinations, color='orange', label='Extrap. kumulierte Impfungen (Ø gesamt)\n{:n} Impfungen/Tag'.format(mean_vaccinations_daily_int)) + ax2.plot(extrapolated_dates, extrapolated_vaccinations_last_seven_days, color='goldenrod', label='Extrap. kumulierte Impfungen (Ø 7 Tage)\n{:n} Impfungen/Tag'.format(mean_vaccinations_last_seven_days_int)) #ax2.plot() ax.legend(loc='upper left') @@ -128,14 +170,69 @@ def plot_extrapolation_portion(percentage): ax2.set_ylabel('Kumulierte Impfungen') - plt.savefig(plot_filename + '.pdf') - plt.savefig(plot_filename + '.png') - plt.savefig(plot_filename_latest + '.pdf') - plt.savefig(plot_filename_latest + '.png') + plt.savefig(archive_plot_filename + '.pdf') + plt.savefig(archive_plot_filename + '.png') + plt.savefig(latest_plot_filename + '.pdf') + plt.savefig(latest_plot_filename + '.png') plt.close() - print('Created plot {} as pdf and png'.format(plot_filename)) + print('Created plot {} as pdf and png'.format(archive_plot_filename)) plot_extrapolation_portion(0.1) +plot_extrapolation_portion(0.7) plot_extrapolation_portion(1.0) + + +def render_dashboard(): + dashboard_filename = 'site/index.xhtml' + dashboard_archive_filename = 'site/archive/{}/index.xhtml'.format(filename_stand) + stylesheet_filename = 'site/rki-dashboard.css' + stylesheet_archive_filename = 'site/archive/{}/rki-dashboard.css'.format(filename_stand) + + if os.path.isfile(dashboard_archive_filename): + print('Dashboard {} already exists'.format(dashboard_archive_filename)) + return + + from jinja2 import Template, Environment, FileSystemLoader, select_autoescape + env = Environment( + loader=FileSystemLoader('./'), + autoescape=select_autoescape(['html', 'xml', 'xhtml']) + ) + + german_text_date_format = '%d. %B %Y' + df = german_text_date_format + + german_text_datetime_format = '%d. %B %Y, %H:%M:%S Uhr' + dtf = german_text_datetime_format + + latest_dashboard_filename = site_folder + 'index.xhtml' + archive_dashboard_filename = archive_folder + + template = env.get_template('dashboard_template.xhtml') + template.stream( + stand = stand_date.strftime(dtf), + filename_stand = filename_stand, + einwohner_deutschland = '{:n}'.format(einwohner_deutschland).replace('.', ' '), + herd_immunity = '{:n}'.format(int(herd_immunity * 100)), + total_vaccinations = '{:n}'.format(total_vaccinations).replace('.', ' '), + total_vaccinations_percentage = '{:.3n}'.format(total_vaccinations_percentage * 100), + days_since_start = days_since_start, + last_date = last_date.strftime(df), + last_date_day_rate = '{:n}'.format(last_date_day_rate).replace('.', ' '), + mean_vaccinations_daily = '{:n}'.format(mean_vaccinations_daily_int).replace('.', ' '), + mean_vaccinations_daily_herd_immunity = mean_vaccinations_daily_herd_immunity.strftime(df), + mean_vaccinations_daily_done = mean_vaccinations_daily_done.strftime(df), + last_date_day_rate_herd_immunity = last_date_day_rate_herd_immunity.strftime(df), + last_date_day_rate_done = last_date_day_rate_done.strftime(df), + mean_vaccinations_last_seven_days = '{:n}'.format(mean_vaccinations_last_seven_days_int).replace('.', ' '), + mean_vaccinations_last_seven_days_herd_immunity = mean_vaccinations_last_seven_days_herd_immunity.strftime(df), + mean_vaccinations_last_seven_days_done = mean_vaccinations_last_seven_days_done.strftime(df) + ).dump('site/index.xhtml') + + shutil.copyfile(dashboard_filename, dashboard_archive_filename) + shutil.copyfile(stylesheet_filename, stylesheet_archive_filename) + + print('Created dashboard') + +render_dashboard() diff --git a/requirements.txt b/requirements.txt index 5e7a8f3..1e70fad 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,3 +3,5 @@ matplotlib pandas openpyxl requests +jinja2 + diff --git a/site/.gitignore b/site/.gitignore new file mode 100644 index 0000000..86ddff1 --- /dev/null +++ b/site/.gitignore @@ -0,0 +1,4 @@ +* +!.gitignore +!archive + diff --git a/Quellen/.gitignore b/site/archive/.gitignore similarity index 100% rename from Quellen/.gitignore rename to site/archive/.gitignore diff --git a/site/rki-dashboard.css b/site/rki-dashboard.css new file mode 100644 index 0000000..759bdcb --- /dev/null +++ b/site/rki-dashboard.css @@ -0,0 +1,59 @@ +body { + font-family: Noto Sans; + max-width: 1000px; + margin: 0px auto; + padding: 0.4em; + hyphens: auto; +} + +h1, h2, h3 { + font-weight: normal; +} + +a { + text-decoration: none; +} + +p { + text-align: justify; +} + +.data-text { + color: #666666; + font-size: 2em; + font-weight: normal; + line-height: 1.75em; +} + +.data-text em { + font-weight: inherit; + font-style: inherit; + /*color: #ffac12;*/ + color: #333333; + /*background-image: linear-gradient(rgba(255, 255, 255, 0) 90%, #f7bb3c 91%, #f7bb3c 98%, rgba(255, 255, 255, 0) 99%);*/ + text-decoration: underline; + text-decoration-color: #ffac12; +} + +sup { + line-height: 0; +} + + + +figure a { + color: inherit; +} + +figure img { + width: 90%; + height: auto; +} + +figure figcaption { + +} + +figure figcaption .ref { + font-weight: bolder; +}