{ "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "plt.style.use('ggplot')\n", "%config InlineBackend.figure_formats = ['svg']\n", "import oscovida as ov\n", "fig, ax = plt.subplots(figsize=(10, 3))\n", "cases, deaths, label = ov.get_country_data(\"Germany\") \n", "ov.plot_daily_change(ax=ax, series=cases, color=\"C1\", \n", " labels=(\"Germany\", \"cases\"));" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# COVID19 infections in Germany are increasing. Are the summer holidays to blame?" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As in many other (European) countries, we see a marked increase in new daily COVID 19 infections (around August 2020) in [Germany](https://oscovida.github.io/html/Germany.html) as shown in the plot above. \n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The reason for this is unclear. We can speculate that two of the possible origins might be or include:\n", "\n", "(i) people's perception of the danger from COVID19 has changed after the first wave has passed, and social distancing and hygene measures may not be taken as seriously as before. This will increase the R number which reflects how many people one infected person can infect, and could explain such an increase. \n", "\n", "(ii) the beginning of the summer holidays and associated travel activity is likely to mix people from different regions, people who don't normally meet, and may also be associated with a more relaxed attitude towards social distancing. So the holiday season could also be to blame.\n", "\n", "In this notebook, we try to present some data to investigate possibility (ii). \n", "\n", "The answer is not conclusive (and the holiday season has not finished yet), but the plots may be interesting nevertheless.\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Data preparation" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "germany = ov.fetch_data_germany()\n", "bundeslaender = sorted(germany['Bundesland'].drop_duplicates())\n", "\n", "holidays = pd.read_csv(\"germany-summer-holidays-2020.csv\", index_col=0)\n", "\n", "# plot parameters\n", "n_bundeslaender = len(bundeslaender) \n", "plot_start_date='2020-06-01'\n", "plot_end_date='2020-09-30'" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Plot 1: daily new cases per Bundesland, together with summer holiday period" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": false }, "outputs": [], "source": [ "fig, axes = plt.subplots(n_bundeslaender+1, 1, figsize=(12, 24))\n", "for i, region in enumerate(bundeslaender[:n_bundeslaender]):\n", " ax = axes[i]\n", " \n", " cases, deaths, label = ov.get_country_data(\"Germany\", region=region)\n", " \n", " # plot dots for daily changes\n", " plot_cases = cases[plot_start_date:plot_end_date].diff().dropna()\n", " ax.plot(plot_cases, 'o', color='C1', alpha=0.2)\n", " \n", " # plot smoothed line\n", " daily, diffsmooth1, diffsmooth2 = ov.compute_daily_change(cases)\n", " diffsmooth2[0][plot_start_date:plot_end_date].plot(ax=ax, linewidth=4, color='C1')\n", " \n", " ax.legend([f\"{region} daily new cases\", \"smoothed\"], loc='upper left')\n", " ax.set_xlim(left=plot_start_date, right=plot_end_date)\n", " \n", " # plot holidays\n", " begin, end = holidays.loc[region]\n", " ax.axvspan(begin, end, facecolor='#2ca02c', alpha=0.3)\n", " ax.tick_params(left=True, right=True, labelleft=True, labelright=True)\n", " ax.yaxis.set_ticks_position('both')\n", "\n", " if i == 100:\n", " ax.tick_params(top=True, labeltop=True)\n", " ax.xaxis.set_ticks_position('both')\n", " ax.xaxis.set_label_position('top') \n", "\n", "\n", "if i == 15: # add overview plot for Germany\n", " ax = axes[16]\n", " region = \"Deutschland (Germany)\"\n", " cases, deaths, label = ov.get_country_data(\"Germany\")\n", "\n", " daily, diffsmooth1, diffsmooth2 = ov.compute_daily_change(cases)\n", " cases[plot_start_date:plot_end_date].diff().plot(ax=ax, style='ob', alpha=0.2)\n", " diffsmooth2[0][plot_start_date:plot_end_date].plot(ax=ax, linewidth=4, color='C1')\n", "\n", " ax.legend([f\"{region} daily new cases\", \"smoothed\"], loc='upper left')\n", " ax.set_xlim(left=plot_start_date, right=plot_end_date)\n", "\n", " # plot holidays, combining the holiday season into one interval\n", " begin = holidays.begin.min()\n", " end = holidays.end.max()\n", " ax.axvspan(begin, end, facecolor='#2ca02c', alpha=0.3);\n", " ax.tick_params(left=True, right=True, labelleft=True, labelright=True)\n", " ax.yaxis.set_ticks_position('both')\n", "\n", " \n", "fig.autofmt_xdate() # minor plotting improvements\n", "axes[0].tick_params(top=True, labeltop=True)\n", "fig.savefig('2020-are-summer-holidays-triggering-rising-cases-plot-cases.pdf')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The dots show the number of new infections per day. The line is a smoothed average to make it easier to identify trends. The question is if the blue line starts to grow, and if this is primarily the case during the school holidays.\n", "\n", "The last plot at the bottom (which shows accumulated data for Germany) shows this increasing trend from middle of July onwards." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Plot 2: R-value per Bundesland, together with summer holiday period" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "n_bundeslaender = 16\n", "fig, axes = plt.subplots(n_bundeslaender+1, 1, figsize=(14, 24))\n", "for i, region in enumerate(bundeslaender[:n_bundeslaender]):\n", " ax = axes[i]\n", " \n", " # plot COVID data\n", " cases, deaths, label = ov.get_country_data(\"Germany\", region=region)\n", " daily, diffsmooth1, diffsmooth2 = ov.compute_daily_change(cases)\n", " R = ov.compute_R(diffsmooth2[0])\n", " \n", " # reduce data set to what is displayed\n", " Rplot = R[plot_start_date:plot_end_date]\n", " Rplot.plot(ax=ax, color='g', linewidth=4.5)\n", "\n", " # Plot line at R = 1\n", " ax.axhline(y=1)\n", " \n", " ax.legend([f\"{region} R\",], loc='upper left')\n", " ax.set_xlim(left=plot_start_date, right=plot_end_date)\n", " ax.set_ylim(bottom=0.5, top=2)\n", " \n", " # plot holidays\n", " begin, end = holidays.loc[region]\n", " ax.axvspan(begin, end, facecolor='#2ca02c', alpha=0.3)\n", "\n", " \n", "if i == 15:\n", " # add overview plot for Germany\n", " ax = axes[16]\n", " region = \"Deutschland (Germany)\"\n", " cases, deaths, label = ov.get_country_data(\"Germany\")\n", "\n", " daily, diffsmooth1, diffsmooth2 = ov.compute_daily_change(cases)\n", " R = ov.compute_R(diffsmooth2[0])\n", "\n", " # reduce data set to what is displayed\n", " Rplot = R[plot_start_date:plot_end_date]\n", " Rplot.plot(ax=ax, color='g', linewidth=4.5)\n", "\n", " # Plot line at R = 1\n", " ax.axhline(y=1)\n", "\n", " ax.legend([f\"{region} R\",], loc='upper left')\n", " ax.set_xlim(left=plot_start_date, right=plot_end_date)\n", " ax.set_ylim(bottom=0.5, top=2)\n", "\n", " # plot holidays, combining the holiday season into one interval\n", " begin = holidays.begin.min()\n", " end = holidays.end.max()\n", " ax.axvspan(begin, end, facecolor='#2ca02c', alpha=0.3);\n", "\n", "fig.autofmt_xdate() # minor plotting improvements\n", "axes[0].tick_params(top=True, labeltop=True)\n", "\n", "fig.savefig('2020-are-summer-holidays-triggering-rising-cases-plot-r.pdf')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The thin red line shows the line which corresponds to the R-value being equal to 1. An R-value of 1 means that the number of daily infections will not change (because on average one infected person will infect exactly one other person). A larger value means that the number of daily new cases will increase. So the question to ask is if the R-value is greater or smaller than 1.0, and if that change correlates with the summer holidays.\n", "\n", "The clearest answer is probably given by the last plot which shows the data for the whole of Germany. In that plot, it is visible that the R-value becomes greater than 1 around the middle of July, and remains there (corresponding to an ongoing increase of daily new infections). " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Discussion\n", "\n", "If the holidays season is to blame for the increase, do we expect the numbers of new cases to start growing immediately when the holidays start? No: first people need to travel and return (we can assume one or two weeks for this to take place), and they also need to develop symptoms and be tested - so a delay by a few weeks is probably expected. Similarly, we don't expect numbers to decrease immediately at the end of the holidays, but with a few weeks delay (as people who return from travel on the last day of their holidays, may still take one or two weeks to develop symptoms, be tested, and the test be reported).\n", "\n", "\n", "The plots of the new cases per day for each Bundesland can be confusing because they have different y-scales. We show absolute numbers here, and scale the y-axis so that the line fills the available space. This allows to see trends, but the impact for infections numbers in Germany overall will depend whether there are 100s of new cases as in Nordrhein-Westfalen or Bavaria, or very few as in Thüringen. Their combined impact can be seen in the summed data for Germany overall. The same is true for the R-values shown in the second plot: an R-value of 1.5 in an area with very few infections results in small total increase of cases across Germany, wheras an R-value of 1.1 in an area with many infections will create more new cases. \n", "\n", "Can we expect the R-value and the number of new infections to drop again when the holiday season is completed (i.e. the middle of September)? If the hypothesis that the travel activity associated with the summer holidays is causing the increased number of new infections, then this would be expected (with a few weeks delay). However, other changes may be taking place the same time: for example the opening of the schools may have an effect. \n", "\n", "(Text written 23 August 2020. Updated 30 August 2020.)\n", "\n", "To get pdf images of the plots, execute this notebook on Binder [see below]. Then the figures [plot1 pdf](2020-are-summer-holidays-triggering-rising-cases-plot-cases.pdf) and [plot2 pdf](2020-are-summer-holidays-triggering-rising-cases-plot-r.pdf) are created and can be downloaded." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Appendix" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Notebook was last executed:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import time\n", "time.asctime()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ov.display_binder_link('2020-are-summer-holidays-triggering-rising-cases.ipynb')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Data source for the holidays is https://www.schulferien.org/deutschland/ferien/2020/ :" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "holidays" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# ov.make_compare_plot_germany(region_subregion=\"Hamburg\", compare_with_local=bundeslaender);" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.3" } }, "nbformat": 4, "nbformat_minor": 4 }