Importing and preparing the data

We will be looking at data from the following countries:

  • Italy
  • Austria
  • Germany
  • Belgium
  • France
  • United Kingdom
  • Portugal

We begin by importing the data, and adding some new features so that we can compare the data from different countries. For example we calculate 'confirmed cases per 100k population', 'deaths per 100k' and 'new cases' since these are not initially in the dataset. This data is collected and preprocessed in this file.

import altair as alt
import pandas as pd

x_small_url = "https://raw.githubusercontent.com/idjotherwise/nlp-otherwise/master/data_sets/european_covid.csv"

Here is a random sample of 5 rows from the dataset.

x_small = pd.read_csv(x_small_url)
x_small.sample(5)
id date vaccines confirmed tests recovered deaths population confirmed_per deaths_per ratio tests_per vaccines_per new_cases new_cases_per
2583 Portugal 2021-12-29 19177996.0 1330158.0 26078371.0 1175217.0 18921.0 10283822.0 12934.471250 183.988015 1.422463 2.535864e+05 1.864870 26867.0 261.255008
4571 France 2022-06-18 145413200.0 29236926.0 278238869.0 NaN 145754.0 66977107.0 43652.118328 217.617641 0.498527 4.154238e+05 2.171088 0.0 0.000000
5285 Austria 2021-11-04 11792419.0 861429.0 98968990.0 776593.0 14077.0 8840521.0 9744.097661 159.232697 1.634145 1.119493e+06 1.333905 9767.0 110.479914
7008 Switzerland 2021-04-06 1672961.0 610837.0 5868819.0 NaN 9994.0 8513227.0 7175.152266 117.393792 1.636116 6.893765e+04 0.196513 2690.0 31.597889
2690 Portugal 2022-04-15 NaN NaN 40700808.0 NaN NaN 10283822.0 NaN NaN NaN 3.957751e+05 NaN 0.0 0.000000

Plotting the data

We will first look at the total numbers of cases and deaths in each country, before moving on to cases and deaths per 100k population.

In each of the charts below, you can click on the legend to filter the lines shown

Total cases per 100,000

leg_selection = alt.selection_multi(fields=['id'], bind='legend')

alt.Chart(x_small_url).mark_line().encode(
    x=alt.X("yearmonthdate(date):T", axis=alt.Axis(title='Date')),
    y=alt.Y("confirmed_per:Q", axis=alt.Axis(title='Confirmed per 100k')),
    tooltip=['id:N', 'confirmed_per:Q'],
    color=alt.Color('id:N', legend=alt.Legend(title="Countries")),
    opacity=alt.condition(leg_selection, alt.value(1), alt.value(0.2))
).transform_filter(alt.datum.confirmed_per>0).add_selection(leg_selection).properties(title='Total number of cases per 100,000 population for selected European Countries', width=600).interactive()

Total deaths per 100,000

alt.Chart(x_small_url).mark_line().encode(
    x=alt.X("yearmonthdate(date):T", axis=alt.Axis(title='Date')),
    y=alt.Y("deaths_per:Q", axis=alt.Axis(title='Deaths per 100k'), impute=alt.ImputeParams(value=50)),
    tooltip=["id:N", "deaths_per:Q", "yearmonthdate(date):T"],
    color=alt.Color('id:N', legend=alt.Legend(title="Countries")),
    opacity=alt.condition(leg_selection, alt.value(1), alt.value(0.2))
).transform_filter(alt.datum.deaths_per>0).add_selection(leg_selection).properties(title='Number of deaths per 100,000 population for selected European Countries', width=600).interactive()

Two week incidence rate

brush = alt.selection(type='interval', encodings=['x'])

base = alt.Chart(x_small_url).mark_line().transform_filter(alt.datum.new_cases_per>0).transform_window(
    rolling_mean='sum(new_cases_per)',
    frame=[-14, 0],
    groupby=['id:N']
).encode(
    x=alt.X("yearmonthdate(date):T",
            axis=alt.Axis(title='Date')
           ),
    y=alt.Y("rolling_mean:Q",
            axis=alt.Axis(title='Incidence rate')
           ),
    tooltip=['id:N', 'rolling_mean:Q'],
    color=alt.Color('id:N', legend=alt.Legend(title="Countries")),
    opacity=alt.condition(leg_selection, alt.value(1), alt.value(0.2))
).add_selection(leg_selection).properties(
    width=600,
    height=400,
    title='Number of new cases per 100,000 over two weeks for selected countries'
)

upper = base.encode(
    alt.X('yearmonthdate(date):T',axis=alt.Axis(title='Date'),
          scale=alt.Scale(domain=brush))
)

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

upper & lower

The ratio of confirmed cases and deaths gives an indication of what the case fatality rate is - it seems to be between 2 and 3%, assuming that the countries listed here are catching all positive cases (which they probably aren't, so it's likely lower than this).

Case fatality rate

base = alt.Chart(x_small_url).mark_line().transform_filter(alt.datum.ratio>0).encode(
    x=alt.X("yearmonthdate(date):T", axis=alt.Axis(title='Date')),
    y=alt.Y("ratio:Q", axis=alt.Axis(title='Ratio of deaths per case')),
    tooltip='id:N',
    color=alt.Color('id:N', legend=alt.Legend(title="Countries")),
opacity=alt.condition(leg_selection, alt.value(1), alt.value(0.2))
).add_selection(leg_selection).properties(title='The ratio of deaths to confirmed cases (case fatality rate)', width=600)

upper = base.encode(
    alt.X('yearmonthdate(date):T',axis=alt.Axis(title='Date'),
          scale=alt.Scale(domain=brush))
)

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

upper & lower

Vaccines

In the chart below we plot the number vaccines given per population - this means that if the number is 1, then the country has given the equivalent of 1 shot for each person in the country. Since not everyone in the countries are eligible to get the vaccine, a ratio of 1 means that many people have recieved two jabs. Note also that some kinds of vaccines (the J&J's Janssen vaccine, for example) only require 1 shot so the goal is not neccesarily to reach exactly 2 shots per person in the whole country.

alt.Chart(x_small_url).mark_line().transform_filter(alt.datum.vaccines_per>0).encode(
    x=alt.X("yearmonthdate(date):T", axis=alt.Axis(title='Date')),
    y=alt.Y("vaccines_per:Q", axis=alt.Axis(title='Number of vaccines given')),
    tooltip=['id:N', 'vaccines_per:Q'],
    color=alt.Color('id:N', legend=alt.Legend(title="Countries")),
    opacity=alt.condition(leg_selection, alt.value(1), alt.value(0.2))
).add_selection(leg_selection).properties(title='Number of vaccines given', width=600).interactive()