Python-bloggers

Update of Swiss Mortality

This article was first published on Python – Michael's and Christian's Blog , and kindly contributed to python-bloggers. (You can report issue about the content on this page here)
Want to share your content on python-bloggers? click here.

In 2021, I wrote a blog post about Swiss mortality and it turned out to be among the most read posts I have written so far. Four years later, I think it’s time for an update with the following improvements:

We use the exact same data sources, all publicly available:

As 4 years ago, I caution against any misinterpretation: I show only crude death rates (CDR) which do not take into account any demographic shift like changing distributions of age.

The first figure shows the CDR per year for several countries, Switzerland (CHE) among them. We fetch the data from the internet, pick some countries of interest, filter on combined gender only (pl.col("Sex") == pl.lit("b") with “b” for both), aggregate and plot. Thanks to this blog post , I was able to integrate the altair/vega-light charts created in Python directly into this wordpress text. The difference is that I exported the altair charts as html and directly copy&pasted it into this text as html block because the html also contains the data to be plotted (as opposed to the default json output).

from datetime import datetime
import polars as pl
import altair as alt

# https://altair-viz.github.io/user_guide/large_datasets.html
alt.data_transformers.enable("vegafusion")

df_original = pl.read_csv(
    "https://www.mortality.org/File/GetDocument/Public/STMF/Outputs/stmf.csv",
    skip_rows=2,
    # Help polars a bit:
    schema_overrides={
        "D65_74": pl.Float64,
        "D75_84": pl.Float64,
        "D85p": pl.Float64,
        "DTotal": pl.Float64,
    },
)

df_mortality = df_original.filter(
    # Select country of interest and only "both" sexes.
    # Note: Germany "DEUTNP" and "USA" have short time series.
    pl.col("CountryCode").is_in(["CAN", "CHE", "FRATNP", "GBRTENW", "SWE"]),
    pl.col("Sex") == pl.lit("b"),
).with_columns(
    # Change to ISO-3166-1 ALPHA-3 codes
    CountryCode=pl.col("CountryCode").replace(
        {"FRATNP": "FRA", "GBRTENW": "England & Wales"},
    ),
    # Create population pro rata temporis (exposure) to ease aggregation
    Population=pl.col("DTotal") / pl.col("RTotal"),
).with_columns(
    # We think that the data uses ISO 8601 week dates and we set the weekday
    # to 1, i.e., Monday.
    Date=(
        pl.col("Year").cast(pl.String)
        + "-W" + pl.col("Week").cast(pl.String).str.zfill(2)
        + "-1"
    ).str.to_date(format="%G-W%V-%u")
)

chart = (
    alt.Chart(
        df_mortality.filter(pl.col("Year") <= 2024)
        # The Covid-19 peaks in 2020 are better seen on weekly resolution.
        .group_by("Year", "CountryCode")
        .agg(pl.col("Population").sum(), pl.col("DTotal").sum())
        .with_columns(
            CDR=pl.col("DTotal") / pl.col("Population"),
        )
    )
    .mark_line(tooltip=True)
    .encode(
        x="Year:T",
        y=alt.Y("CDR:Q", scale=alt.Scale(zero=False)),
        color="CountryCode:N",
    )
    .properties(
        title="Crude Death Rate per Year",
        width=400,  # default 300
    )
    .interactive()
)
# chart.save("crude_death_rate.html")
chart







function showError(el, error){ el.innerHTML = ('

' + '

JavaScript Error: ' + error.message + '

' + "

This usually means there's a typo in your chart specification. " + "See the javascript console for the full traceback.

" + '

'); throw error; } const el = document.getElementById('cdr_yearly'); vegaEmbed("#cdr_yearly", spec, embedOpt) .catch(error => showError(el, error)); })(vegaEmbed);

Crude death rate (CDR) for Canada (CAN), Switzerland (CHE), England & Wales, France (FRA) and Sweden (SWE). Data as of 05.07.2025.


Note that the y-axis does not start at zero. Nevertheless, we see values between 0.007 and 0.0105. The big spike that we observed in the beginning of 2021 is now flattened. In 2021 all those countries showed a CDR of over 0.01, now most are below 0.09 in 2020. This shows that the data as of February 2021 was incomplete as I mentioned. Now we have the complete picture and it looks better—fortunately!

This time, I also add a chart with weekly CDRs to demonstrate the seasonality effects.

chart = (
    alt.Chart(
        df_mortality.filter(
            pl.col("CountryCode") <= pl.lit("CHE"),
            # Last 12 years
            pl.col("Year") > pl.col("Year").max() - 12,
        ).with_columns(
            CDR=pl.col("DTotal") / pl.col("Population"),
        )
    )
    .mark_line(tooltip=True)
    .encode(
        x="Date:T",
        y=alt.Y("CDR:Q", scale=alt.Scale(zero=True)),
    )
    .properties(
        title="Crude Death Rate per Week for Switzerland",
        width=400,  # default 300
    )
    .interactive()
)
# chart.save("crude_death_rate_per_week.html")
chart







To leave a comment for the author, please follow the link and comment on their blog: Python – Michael's and Christian's Blog .

Want to share your content on python-bloggers? click here.
Exit mobile version