{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Analyse de l’épidémie de choléra à Soho (1854) — Reproduction de la carte de John Snow - Victor GERENTON\n", "\n", "Ce notebook a pour objectif de reproduire la célèbre carte de John Snow montrant la répartition des décès dus au choléra à Soho en 1854. À l'aide de données géographiques historiques et d'outils modernes de visualisation, nous allons :\n", "\n", "1. Visualiser les lieux de décès et les pompes à eau sur une carte interactive.\n", "2. Explorer des méthodes d'analyse spatiale pour mettre en évidence le rôle central de la pompe de Broad Street.\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Installation des biliothèques\n", "\n", "L'ensembles des biliothèques et leur version respective se trouvent dans le fichier 'requirements.txt'" ] }, { "cell_type": "code", "execution_count": 33, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Requirement already satisfied: geopandas==0.9.0 in /opt/conda/lib/python3.6/site-packages (from -r requirements.txt (line 1)) (0.9.0)\n", "Requirement already satisfied: folium==0.12.1 in /opt/conda/lib/python3.6/site-packages (from -r requirements.txt (line 2)) (0.12.1)\n", "Requirement already satisfied: branca==0.4.2 in /opt/conda/lib/python3.6/site-packages (from -r requirements.txt (line 3)) (0.4.2)\n", "Requirement already satisfied: rasterio==1.2.10 in /opt/conda/lib/python3.6/site-packages (from -r requirements.txt (line 4)) (1.2.10)\n", "Requirement already satisfied: pandas==1.1.5 in /opt/conda/lib/python3.6/site-packages (from -r requirements.txt (line 5)) (1.1.5)\n", "Requirement already satisfied: shapely==1.7.1 in /opt/conda/lib/python3.6/site-packages (from -r requirements.txt (line 6)) (1.7.1)\n", "Requirement already satisfied: fiona==1.8.20 in /opt/conda/lib/python3.6/site-packages (from -r requirements.txt (line 7)) (1.8.20)\n", "Requirement already satisfied: pyproj==2.6 in /opt/conda/lib/python3.6/site-packages (from -r requirements.txt (line 8)) (2.6.0)\n", "Requirement already satisfied: numpy==1.19.5 in /opt/conda/lib/python3.6/site-packages (from -r requirements.txt (line 9)) (1.19.5)\n", "Requirement already satisfied: jinja2>=2.9 in /opt/conda/lib/python3.6/site-packages (from folium==0.12.1->-r requirements.txt (line 2)) (2.11.0)\n", "Requirement already satisfied: requests in /opt/conda/lib/python3.6/site-packages (from folium==0.12.1->-r requirements.txt (line 2)) (2.23.0)\n", "Requirement already satisfied: cligj>=0.5 in /opt/conda/lib/python3.6/site-packages (from rasterio==1.2.10->-r requirements.txt (line 4)) (0.7.2)\n", "Requirement already satisfied: attrs in /opt/conda/lib/python3.6/site-packages (from rasterio==1.2.10->-r requirements.txt (line 4)) (19.3.0)\n", "Requirement already satisfied: certifi in /opt/conda/lib/python3.6/site-packages (from rasterio==1.2.10->-r requirements.txt (line 4)) (2020.4.5.1)\n", "Requirement already satisfied: affine in /opt/conda/lib/python3.6/site-packages (from rasterio==1.2.10->-r requirements.txt (line 4)) (2.3.1)\n", "Requirement already satisfied: setuptools in /opt/conda/lib/python3.6/site-packages (from rasterio==1.2.10->-r requirements.txt (line 4)) (45.2.0.post20200209)\n", "Requirement already satisfied: click-plugins in /opt/conda/lib/python3.6/site-packages (from rasterio==1.2.10->-r requirements.txt (line 4)) (1.1.1)\n", "Requirement already satisfied: click>=4.0 in /opt/conda/lib/python3.6/site-packages (from rasterio==1.2.10->-r requirements.txt (line 4)) (8.0.4)\n", "Requirement already satisfied: snuggs>=1.4.1 in /opt/conda/lib/python3.6/site-packages (from rasterio==1.2.10->-r requirements.txt (line 4)) (1.4.7)\n", "Requirement already satisfied: python-dateutil>=2.7.3 in /opt/conda/lib/python3.6/site-packages (from pandas==1.1.5->-r requirements.txt (line 5)) (2.8.1)\n", "Requirement already satisfied: pytz>=2017.2 in /opt/conda/lib/python3.6/site-packages (from pandas==1.1.5->-r requirements.txt (line 5)) (2019.3)\n", "Requirement already satisfied: six>=1.7 in /opt/conda/lib/python3.6/site-packages (from fiona==1.8.20->-r requirements.txt (line 7)) (1.14.0)\n", "Requirement already satisfied: munch in /opt/conda/lib/python3.6/site-packages (from fiona==1.8.20->-r requirements.txt (line 7)) (4.0.0)\n", "Requirement already satisfied: MarkupSafe>=0.23 in /opt/conda/lib/python3.6/site-packages (from jinja2>=2.9->folium==0.12.1->-r requirements.txt (line 2)) (1.1.1)\n", "Requirement already satisfied: chardet<4,>=3.0.2 in /opt/conda/lib/python3.6/site-packages (from requests->folium==0.12.1->-r requirements.txt (line 2)) (3.0.4)\n", "Requirement already satisfied: idna<3,>=2.5 in /opt/conda/lib/python3.6/site-packages (from requests->folium==0.12.1->-r requirements.txt (line 2)) (2.9)\n", "Requirement already satisfied: urllib3!=1.25.0,!=1.25.1,<1.26,>=1.21.1 in /opt/conda/lib/python3.6/site-packages (from requests->folium==0.12.1->-r requirements.txt (line 2)) (1.25.7)\n", "Requirement already satisfied: importlib-metadata; python_version < \"3.8\" in /opt/conda/lib/python3.6/site-packages (from click>=4.0->rasterio==1.2.10->-r requirements.txt (line 4)) (4.8.3)\n", "Requirement already satisfied: pyparsing>=2.1.6 in /opt/conda/lib/python3.6/site-packages (from snuggs>=1.4.1->rasterio==1.2.10->-r requirements.txt (line 4)) (2.4.6)\n", "Requirement already satisfied: zipp>=0.5 in /opt/conda/lib/python3.6/site-packages (from importlib-metadata; python_version < \"3.8\"->click>=4.0->rasterio==1.2.10->-r requirements.txt (line 4)) (2.1.0)\n", "Requirement already satisfied: typing-extensions>=3.6.4; python_version < \"3.8\" in /opt/conda/lib/python3.6/site-packages (from importlib-metadata; python_version < \"3.8\"->click>=4.0->rasterio==1.2.10->-r requirements.txt (line 4)) (4.1.1)\n", "----------------------------------------\n", "Toutes les bibliothèques sont installées !\n" ] } ], "source": [ "!pip install -r requirements.txt\n", "\n", "print((\"----------------------------------------\"))\n", "print(\"Toutes les bibliothèques sont installées !\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Importation des biliothèques" ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Toutes les bibliothèques sont importées !\n" ] } ], "source": [ "# Importation des bibliothèques nécessaires à notre analyse\n", "import geopandas as gpd\n", "import folium\n", "import matplotlib.pyplot as plt\n", "from shapely.geometry import Point\n", "\n", "print(\"Toutes les bibliothèques sont importées !\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Étape 1 — Chargement des données\n", "\n", "Nous utilisons ici les fichiers Shapefile fournis dans l’archive `SnowGIS_SHP` provenant de ce site :\n", "\n", "https://blog.rtwilson.com/john-snows-cholera-data-in-more-formats/\n", "\n", "\n", "Ces fichiers contiennent les données géographiques suivantes :\n", "\n", "- `Cholera_Deaths.shp` : emplacements des décès dus au choléra, avec le nombre de morts par adresse.\n", "- `Pumps.shp` : emplacements des pompes à eau publiques dans le quartier de Soho.\n", "\n", "Nous utilisons la bibliothèque `GeoPandas` pour lire ces fichiers et vérifier que les données sont bien chargées avec le bon système de coordonnées.\n" ] }, { "cell_type": "code", "execution_count": 29, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
IdCountgeometry
003POINT (529308.741 181031.352)
102POINT (529312.164 181025.172)
201POINT (529314.382 181020.294)
301POINT (529317.380 181014.259)
404POINT (529320.675 181007.872)
\n", "
" ], "text/plain": [ " Id Count geometry\n", "0 0 3 POINT (529308.741 181031.352)\n", "1 0 2 POINT (529312.164 181025.172)\n", "2 0 1 POINT (529314.382 181020.294)\n", "3 0 1 POINT (529317.380 181014.259)\n", "4 0 4 POINT (529320.675 181007.872)" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
Idgeometry
00POINT (529396.539 181025.063)
10POINT (529192.538 181079.391)
20POINT (529183.740 181193.735)
30POINT (529748.911 180924.207)
40POINT (529613.205 180896.804)
\n", "
" ], "text/plain": [ " Id geometry\n", "0 0 POINT (529396.539 181025.063)\n", "1 0 POINT (529192.538 181079.391)\n", "2 0 POINT (529183.740 181193.735)\n", "3 0 POINT (529748.911 180924.207)\n", "4 0 POINT (529613.205 180896.804)" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "CRS des décès : epsg:27700\n", "CRS des pompes : epsg:27700\n" ] } ], "source": [ "# Chargement des shapefiles avec GeoPandas\n", "deaths = gpd.read_file(\"Cholera_Deaths.shp\")\n", "pumps = gpd.read_file(\"Pumps.shp\")\n", "\n", "# Aperçu des données\n", "display(deaths.head())\n", "display(pumps.head())\n", "\n", "# Vérification du système de coordonnées (CRS)\n", "print(\"CRS des décès :\", deaths.crs)\n", "print(\"CRS des pompes :\", pumps.crs)\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Étape 2 — Conversion des coordonnées pour Folium\n", "\n", "Folium utilise sur le système de coordonnées **WGS 84** (latitude/longitude), qui est utilisé par défaut dans les cartes web comme OpenStreetMap. \n", "Cependant, nos fichiers shapefile utilisent actuellement le système **British National Grid** (EPSG:27700), qui fonctionne avec des coordonnées en mètres, propres au Royaume-Uni.\n", "\n", "Afin de pouvoir afficher correctement nos données sur une carte interactive avec Folium, il est nécessaire de les convertir vers le système de projection WGS 84 (**EPSG:4326**). \n", "Cette opération est appelée une **reprojection**. Elle permet à nos points d'apparaître au bon endroit sur la carte.\n", "\n", "Nous réalisons cette conversion avec la méthode .to_crs(epsg=4326) de GeoPandas, qui transforme les coordonnées de chaque géométrie." ] }, { "cell_type": "code", "execution_count": 30, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
IdCountgeometry
003POINT (-0.13793 51.51342)
102POINT (-0.13788 51.51336)
201POINT (-0.13785 51.51332)
301POINT (-0.13781 51.51326)
404POINT (-0.13777 51.51320)
\n", "
" ], "text/plain": [ " Id Count geometry\n", "0 0 3 POINT (-0.13793 51.51342)\n", "1 0 2 POINT (-0.13788 51.51336)\n", "2 0 1 POINT (-0.13785 51.51332)\n", "3 0 1 POINT (-0.13781 51.51326)\n", "4 0 4 POINT (-0.13777 51.51320)" ] }, "execution_count": 30, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Reprojection en EPSG:4326 (latitude/longitude)\n", "deaths_wgs84 = deaths.to_crs(epsg=4326)\n", "pumps_wgs84 = pumps.to_crs(epsg=4326)\n", "\n", "# Aperçu pour vérification\n", "deaths_wgs84.head()\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Étape 3 — Création de la carte interactive\n", "\n", "Nous utilisons la bibliothèque `folium` pour créer une carte interactive centrée sur le quartier de Soho.\n", "\n", "- Les **cercles rouges** représentent les décès dus au choléra. Leur taille est proportionnelle au nombre de morts enregistrés à cette adresse (`Count`).\n", "- Les **marqueurs bleus** représentent les pompes à eau publiques.\n", "\n", "Cette visualisation permet de retrouver la logique de la carte originale de John Snow, en montrant la concentration des décès autour de certaines pompes.\n" ] }, { "cell_type": "code", "execution_count": 31, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
Make this Notebook Trusted to load map: File -> Trust Notebook
" ], "text/plain": [ "" ] }, "execution_count": 31, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Obtenir le centre de la carte en prenant le centre des décès\n", "map_center = [deaths_wgs84.geometry.y.mean(), deaths_wgs84.geometry.x.mean()]\n", "\n", "# Créer la carte Folium centrée sur Soho\n", "m = folium.Map(location=map_center, zoom_start=17, tiles='cartodbpositron')\n", "\n", "# Ajouter les décès avec des cercles proportionnels à Count\n", "for _, row in deaths_wgs84.iterrows():\n", " folium.CircleMarker(\n", " location=[row.geometry.y, row.geometry.x],\n", " radius=row['Count'] * 1.5, # multiplier pour avoir une taille visible\n", " color='red',\n", " fill=True,\n", " fill_color='red',\n", " fill_opacity=0.6,\n", " popup=f\"{row['Count']} décès\"\n", " ).add_to(m)\n", "\n", "# Ajouter les pompes avec une icône bleue\n", "for _, row in pumps_wgs84.iterrows():\n", " folium.Marker(\n", " location=[row.geometry.y, row.geometry.x],\n", " icon=folium.Icon(color='blue', icon='tint', prefix='fa'),\n", " popup=\"Pompe à eau\"\n", " ).add_to(m)\n", "\n", "# Afficher la carte\n", "m\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Étape 4 — Visualisation de la densité des décès avec une Heatmap\n", "\n", "Pour identifier visuellement les zones les plus touchées par l’épidémie, nous utilisons une **carte de chaleur** (heatmap). \n", "Chaque point de décès est pondéré par le nombre de morts (`Count`). On observe ainsi une zone de concentration très nette autour d'une certaine pompe.\n", "On voyait déjà cela avec les cercles mais je trouve la heat map plus parlante.\n" ] }, { "cell_type": "code", "execution_count": 32, "metadata": { "scrolled": true }, "outputs": [ { "data": { "text/html": [ "
Make this Notebook Trusted to load map: File -> Trust Notebook
" ], "text/plain": [ "" ] }, "execution_count": 32, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from folium.plugins import HeatMap\n", "\n", "# Extraire les points avec leur pondération\n", "heat_data = [[row.geometry.y, row.geometry.x, row['Count']] for _, row in deaths_wgs84.iterrows()]\n", "\n", "# Nouvelle carte centrée sur Soho\n", "m_heat = folium.Map(location=map_center, zoom_start=17, tiles='cartodbpositron')\n", "\n", "# Ajouter la heatmap des décès\n", "HeatMap(heat_data, radius=15, blur=10, max_zoom=18).add_to(m_heat)\n", "\n", "# Ajouter les pompes pour référence\n", "for _, row in pumps_wgs84.iterrows():\n", " folium.Marker(\n", " location=[row.geometry.y, row.geometry.x],\n", " icon=folium.Icon(color='blue', icon='tint', prefix='fa'),\n", " popup=\"Pompe à eau\"\n", " ).add_to(m_heat)\n", "\n", "# Afficher la heatmap\n", "m_heat\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Conclusion\n", "\n", "Grâce à nos outils de visualisation de données géospatiales, nous avons pu reproduire et enrichir l’analyse historique de John Snow sur l’épidémie de choléra de 1854 à Soho.\n", "\n", "- Nous avons visualisé les lieux de décès avec des cercles proportionnels au nombre de morts.\n", "- Nous avons localisé les pompes à eau sur la même carte.\n", "- La carte de chaleur a clairement mis en évidence une forte concentration de décès autour d'une pompe spécifique, celle de Broad Street.\n", "\n", "\n", "Victor Gérenton\n", "---\n" ] } ], "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.6.4" } }, "nbformat": 4, "nbformat_minor": 5 }