Files
CoronaFlask/coronaflask/corona.py
2022-01-25 15:43:13 +01:00

182 lines
7.3 KiB
Python

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)