First commit.
This commit is contained in:
2
coronaflask/__init__.py
Normal file
2
coronaflask/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
from . import app
|
||||
from . import corona
|
||||
30
coronaflask/app.py
Normal file
30
coronaflask/app.py
Normal file
@@ -0,0 +1,30 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from flask import Flask, render_template
|
||||
from markupsafe import escape
|
||||
|
||||
if __package__ is None:
|
||||
import corona
|
||||
else:
|
||||
from . import corona
|
||||
|
||||
app = Flask(__name__)
|
||||
Cor = corona.Corona()
|
||||
|
||||
|
||||
@app.route("/")
|
||||
def world():
|
||||
return cor('World')
|
||||
|
||||
|
||||
@app.route('/<place>')
|
||||
def cor(place):
|
||||
return render_template('index.html', place=place, figure=Cor.plot_cases(escape(place)), regions=Cor.regions)
|
||||
|
||||
|
||||
def main():
|
||||
app.run()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
181
coronaflask/corona.py
Normal file
181
coronaflask/corona.py
Normal 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)
|
||||
13
coronaflask/templates/index.html
Normal file
13
coronaflask/templates/index.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<!doctype html >
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>{{place}}</title>
|
||||
</head>
|
||||
<body>
|
||||
{{figure|safe}}
|
||||
<br />
|
||||
{% for region in regions %}
|
||||
<a href="{{region}}">{{region}}</a>
|
||||
{% endfor %}
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user