Importing the data

The data used in this post can be found at https://www.data.gv.at/covid-19/. After downloading the CSV file called CovidFaelle_Timeline.csv, we need to do some cleaning of the date column and split off some specific sets - the numbers for Austria, Volarlberg, Tirol and Wien. We also need to format the decimal numbers since the CSV file uses a ',' instead of a '.' as decimal.

get_new_data = False

if get_new_data:
    at_data = pd.read_csv("https://covid19-dashboard.ages.at/data/CovidFaelle_Timeline.csv", sep=';')
    at_data['Time'] = pd.to_datetime(at_data['Time'], format='%d.%m.%Y %H:%M:%S')
    at_data['SiebenTageInzidenzFaelle'] = [float('.'.join(x.split(','))) for x in at_data.SiebenTageInzidenzFaelle]
    at_data.to_csv("../data_sets/CovidFaelle_Timeline.csv", index=False)

else:
    at_data = pd.read_csv("../data_sets/CovidFaelle_Timeline.csv", sep=',')
    at_data['Time'] = pd.to_datetime(at_data['Time'], format='%Y-%m-%d %H:%M:%S')


current_date = at_data.iloc[-1,0]
all_austria = at_data.query("BundeslandID==10").sort_values(by='Time')
string_to_md = f"- Note that the latest date in this data is {current_date}."
display(Markdown(string_to_md))

latest_data = at_data.query(f"Time>='{str((current_date - datetime.timedelta(7)).date())}'")
saturdays_data = at_data.query("Time.dt.dayofweek == 5").copy()
saturdays_data['rate_change']=saturdays_data.groupby(by='Bundesland').SiebenTageInzidenzFaelle.pct_change().replace(np.inf, np.nan).fillna(0)*100
  • Note that the latest date in this data is 2021-04-01 00:00:00.

latest_rate_vbg = latest_data.query("Bundesland=='Vorarlberg'").SiebenTageInzidenzFaelle
latest_rate_aus = latest_data.query("Bundesland=='Österreich'").SiebenTageInzidenzFaelle

vbg_change = round(list(latest_rate_vbg)[-1] - list(latest_rate_vbg)[0]) / list(latest_rate_vbg)[0]
aus_change = round(list(latest_rate_aus)[-1] - list(latest_rate_aus)[0]) / list(latest_rate_aus)[0]
week_trend_vbg = f'<span style="color: green;">Down</span> {vbg_change:.1%}' if vbg_change < 0 else f'<span style="color: red;">Up</span> {vbg_change:.1%}'
week_trend_aus = f'<span style="color: green;">Down</span> {aus_change:.1%}' if aus_change < 0 else f'<span style="color: red;">Up</span>{aus_change:.1%}'

vbg_string = f"Weekly trend in **Vorarlberg**: {week_trend_vbg}"
aus_string = f"Weekly trend in **Austria**: {week_trend_aus}"

display(Markdown(vbg_string))
display(Markdown(aus_string))

Weekly trend in Vorarlberg: Up 29.6%

Weekly trend in Austria: Down -0.8%

source = saturdays_data.query("Time >= '2021-01-31'")
alt.Chart(source).mark_circle(
    opacity=0.8,
    stroke='black',
    strokeWidth=1
).encode(
    alt.X('yearmonthdate(Time):T', axis=alt.Axis(title='', labelAngle=-45)),
    alt.Y('Bundesland:N'),
    alt.Size('rate_change:Q',
            scale=alt.Scale(range=[0, 500]),
            legend=alt.Legend(title='Percentage change')
    ),
    alt.Color('Bundesland:N', legend=None),
    tooltip='rate_change'
).properties(
    width=600,
    height=320,
    title='Percentage change of incidence rate from the previous week'
)

Plotting the data

First we have the number of cases per 100,000 population across the whole of Austria. After a very sharp rise in cases during the end of October and the middle of November, the number of new cases have fallen due to the second lockdown. At the time of writting, the cases seem to have levelled off a little - though the Austrian government announced a third lockdown lasting a month starting from the 26th of December, in the expectation that cases will continue to rise over the festive period.

alt.Chart(all_austria).mark_bar(
    color='red',
    opacity=1,
    size=1
).encode(
    x=alt.X("yearmonthdate(Time):T", axis=alt.Axis(title='Date')),
    y=alt.Y("SiebenTageInzidenzFaelle:Q",
            axis=alt.Axis(title='Cases per 100k')),
).configure_axis(grid=True).configure_view(strokeWidth=0.1).properties(
    title='Number of cases per 100,000 in Austria', width=800
)

bars = alt.Chart(all_austria).mark_bar(
    color='red',
    opacity=1,
    size=2
).encode(
    x=alt.X("yearmonthdate(Time):T", axis=alt.Axis(title='Date')),
    y=alt.Y("AnzahlFaelle:Q", axis=alt.Axis(title='New cases')),
)

line = alt.Chart(all_austria).mark_line(
    color='blue',
    opacity=0.75,
    size=1.5,
).transform_window(
    rolling_mean='mean(AnzahlFaelle)',
    frame=[0, 7]
).encode(x='yearmonthdate(Time):T',
         y='rolling_mean:Q'
         ).properties(title='New cases per day with rolling mean', width=800)

alt.layer(bars, line, data=all_austria)

Next we have the 7 day incidence rate for states of Vorarlberg, Tirol and Wien compared to all of Austria.

leg_selection = alt.selection_multi(fields=['Bundesland'], bind='legend')
brush = alt.selection(type='interval', encodings=['x'])

base = alt.Chart(at_data).mark_line().encode(
    x=alt.X("yearmonthdate(Time):T", axis=alt.Axis(title='Date')),
    y=alt.Y("SiebenTageInzidenzFaelle:Q", axis=alt.Axis(title='Incidence rate')),
    tooltip='Bundesland',
    color='Bundesland',
    opacity=alt.condition(leg_selection, alt.value(2), alt.value(0.1))
).add_selection(leg_selection).properties(width=800)

upper = base.encode(
    alt.X('yearmonthdate(Time):T',axis=alt.Axis(title=''),
          scale=alt.Scale(domain=brush))
).properties(title='7 day incidence rate for states in Austria')

lower = base.properties(
    height=60
).add_selection(brush)

upper & lower

Finally we have the

brush = alt.selection(type='interval', encodings=['x'])
states = at_data['Bundesland'].unique()
states.sort()

selection = alt.selection_single(
    name='Select',
    fields=['Bundesland'],
    init={'Bundesland': 'Vorarlberg'},
    bind={'Bundesland': alt.binding_select(options=states)}
)

# scatter plot, modify opacity based on selection
bars = alt.Chart(at_data).mark_bar().add_selection(
    selection
).encode(
    x=alt.X("yearmonthdate(Time):T", axis=alt.Axis(title='Date')),
    y=alt.Y("SiebenTageInzidenzFaelle:Q", axis=alt.Axis(title='Incidence rate')),
    tooltip='SiebenTageInzidenzFaelle:Q',
    opacity=alt.condition(selection, alt.value(1), alt.value(0))
).properties(title=f'7 day incidence rate of individual states vs rolling mean across Austria', width=800)

line = alt.Chart(all_austria).mark_line(
    color='red',
    size=2,
).transform_window(
    rolling_mean='mean(SiebenTageInzidenzFaelle)',
    frame=[0, 7]
).encode(
    x='yearmonthdate(Time):T',
    y='rolling_mean:Q'
)

base = alt.layer(bars, line)
upper_bars = bars.encode(
    alt.X('yearmonthdate(Time):T',axis=alt.Axis(title='Date'),
          scale=alt.Scale(domain=brush))
)
upper_line = line.encode(
    alt.X('yearmonthdate(Time):T',axis=alt.Axis(title='Date'),
          scale=alt.Scale(domain=brush))
)

upper = alt.layer(upper_bars, upper_line)

lower = base.properties(
    height=60
).add_selection(brush)
upper & lower

Choose from the above dropdown menu to view the different states.