Files
CoronaFlask/coronaflask/corona.py
Wim Pomp 10b2ff52e1 - add path to sys.path
- pandas.concat
2022-05-13 21:27:54 +02:00

184 lines
7.4 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
pandas.set_option('display.max_rows', 500)
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 pandas.concat((p, pandas.DataFrame(w).T))
@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)