First commit.

This commit is contained in:
Wim Pomp
2022-01-25 15:43:13 +01:00
parent 60365b97f1
commit 63c3964036
7 changed files with 259 additions and 0 deletions

181
coronaflask/corona.py Normal file
View File

@@ -0,0 +1,181 @@
import os
import re
import datetime
import pandas
import numpy as np
from matplotlib.figure import Figure
import matplotlib.dates as mdates
import mpld3
import requests
import io
class Plots:
@staticmethod
def get_date(date_s):
date_d = {k: int(i) + (2000 if k == 'year' else 0) for i, k in zip(date_s.split('/'), ('month', 'day', 'year'))}
date_o = datetime.datetime(**date_d)
return int(mdates.date2num(date_o) + 0.5), date_s, date_o
@staticmethod
def fmt_axis(ax):
now = int(mdates.date2num(datetime.datetime.now()) + 0.5)
ax.set_xlim(now - 100, now)
ax.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d'))
ax.xaxis.set_major_locator(mdates.DayLocator(interval=5))
xy = [a.get_xydata() for a in ax.get_children() if hasattr(a, 'get_xydata')]
if len(xy):
xy = np.vstack(xy)
ax.set_ylim(Plots.getylim(xy[:, 1], xy[:, 0], ax.get_xlim(), log=ax.get_xscale() == 'log'))
@staticmethod
def getylim(y, x=None, xlim=None, margin=0.05, log=False):
""" get limits for plots according to data
copied from matplotlib.axes._base.autoscale_view
y: the y data
optional, for when xlim is set manually on the plot
x: corresponding x data
xlim: limits on the x-axis in the plot, example: xlim=(0, 100)
margin: what fraction of white-space to have at all borders
y and x can be lists or tuples of different data in the same plot
wp@tl20191220
"""
y = np.array(y).flatten()
if log:
y = np.log(y)
if x is not None and xlim is not None:
x = np.array(x).flatten()
y = y[(np.nanmin(xlim) < x)*(x < np.nanmax(xlim))*(np.abs(x) > 0)]
if not np.any(np.isfinite(y)):
return 0, 1
if len(y) == 0:
return -margin, margin
y0t, y1t = np.nanmin(y), np.nanmax(y)
if np.isfinite(y1t) and np.isfinite(y0t):
delta = (y1t - y0t) * margin
if y0t == y1t:
delta = 0.5
else: # If at least one bound isn't finite, set margin to zero
delta = 0
if log:
return np.exp(y0t - delta), np.exp(y1t + delta)
else:
return y0t - delta, y1t + delta
@staticmethod
def transform(a, b, x, xlim):
return np.polyval(np.polyfit(Plots.getylim(a, x, xlim), Plots.getylim(b, x, xlim), 1), a)
class Corona:
def __init__(self):
self.home = os.path.dirname(__file__)
self.update()
def update(self):
self.last_update = datetime.datetime.now()
self.confirmed_global = self.get_data(
'https://raw.githubusercontent.com/CSSEGISandData/COVID-19/master/csse_covid_19_data/csse_covid_19_time_series/time_series_covid19_confirmed_global.csv')
self.deaths_global = self.get_data(
'https://raw.githubusercontent.com/CSSEGISandData/COVID-19/master/csse_covid_19_data/csse_covid_19_time_series/time_series_covid19_deaths_global.csv')
self.regions = sorted(self.confirmed_global['Province/State'].tolist())
@staticmethod
def get_data(url):
with requests.get(url, allow_redirects=True) as r:
with io.BytesIO(r.content) as b:
p = pandas.read_csv(b)
null = p['Province/State'].isnull()
p.loc[null, 'Province/State'] = p.loc[null, 'Country/Region']
# make series searchable by region
countries = p['Country/Region'].unique()
states = p['Province/State'].unique()
for country in countries:
if country not in states:
ca = p.query('`Country/Region`=="{}"'.format(country))
cb = ca.sum(axis=0)
cb['Province/State'] = country
cb['Country/Region'] = country
cb['Lat'] /= len(ca)
cb['Long'] /= len(ca)
p.loc[p.index.max() + 1] = cb
# make World entry by summing
w = np.sum(p)
w.name = p.index.max() + 1
w['Province/State'] = 'World'
w['Country/Region'] = 'World'
w['Lat'] = 0
w['Long'] = 0
return p.append(w)
@staticmethod
def plot_series(fig, ax, dates_number, dates_obj, cum, llabel=None, rlabel=None):
ax.plot(dates_obj, cum, 'ro-')
Plots.fmt_axis(ax)
ax.set_ylim(Plots.getylim(cum, dates_number, ax.get_xlim()))
dcum = cum - np.interp(dates_number, dates_number + 1, cum)
bx = ax.twinx()
p, = bx.plot(dates_obj, Plots.transform(cum, dcum, dates_number, ax.get_xlim()), 'o', color='none')
mpld3.plugins.connect(fig, mpld3.plugins.PointLabelTooltip(p, labels=[f'{n:.2g}' for n in cum]))
p, = bx.plot(dates_obj, dcum, 'ko-')
bx.patch.set_alpha(0.0)
mpld3.plugins.connect(fig, mpld3.plugins.PointLabelTooltip(p, labels=[f'{n:.2g}' for n in dcum]))
Plots.fmt_axis(bx)
if llabel is not None:
ax.set_ylabel(llabel, color='r')
if rlabel is not None:
bx.set_ylabel(rlabel, color='k')
def plot_cases(self, place, sicktime=14):
if datetime.datetime.now() - self.last_update > datetime.timedelta(hours=1):
self.update()
n = self.confirmed_global.query(f'`Province/State`=="{place}"')
d = self.deaths_global.query(f'`Province/State`=="{place}"')
if n.empty:
n = self.confirmed_global.query(f'`Country/Region`=="{place}"')
d = self.deaths_global.query(f'`Country/Region`=="{place}"')
n = pandas.DataFrame(n.sum()).T
d = pandas.DataFrame(d.sum()).T
dates = [Plots.get_date(c) for c in n.columns if re.match('[\d/]+', c) is not None]
dates_number, dates_str, dates_obj = zip(*sorted(dates))
sick_cum = np.array([int(n[date]) for date in dates_str])
death_cum = np.array([int(d[date]) for date in dates_str])
dates_number = np.array(dates_number)
sick_now = sick_cum - np.interp(dates_number, dates_number + sicktime, sick_cum)
fig = Figure(figsize=(16, 8), dpi=100)
ax = fig.add_subplot(4, 1, 1)
self.plot_series(fig, ax, dates_number, dates_obj, sick_cum, 'sick cumulative', 'daily new cases')
ax.set_title('cumulative and new daily cases')
ax = fig.add_subplot(4, 1, 2)
self.plot_series(fig, ax, dates_number, dates_obj, sick_now, 'sick now', 'daily change')
ax.set_title('current sick and daily change')
ax = fig.add_subplot(4, 1, 3)
reproduction_rate = sick_now / np.clip(np.interp(dates_number, dates_number + 4, sick_now), 1e-15, np.inf)
p, = ax.plot(dates_obj, np.clip(reproduction_rate, 0, 3), 'ro-')
ax.plot((np.nanmin(dates_number), np.nanmax(dates_number)), (1, 1), '--k')
mpld3.plugins.connect(fig, mpld3.plugins.PointLabelTooltip(p, labels=[f'{n:.2f}' for n in reproduction_rate]))
Plots.fmt_axis(ax)
ax.set_ylim(0.5, 1.5)
ax.set_title('reproduction rate')
ax.set_ylabel('reproduction rate', color='r')
ax = fig.add_subplot(4, 1, 4)
self.plot_series(fig, ax, dates_number, dates_obj, death_cum, 'diseased cumulative', 'daily diseased')
ax.set_title('cumulative and new deaths')
fig.autofmt_xdate()
fig.tight_layout(h_pad=2)
return mpld3.fig_to_html(fig)