diff --git a/module3/exo3/Exo3_Cholera.html b/module3/exo3/Exo3_Cholera.html new file mode 100644 index 0000000000000000000000000000000000000000..ec5bd7733f25c6f381b766b0575cee934a33d8ae --- /dev/null +++ b/module3/exo3/Exo3_Cholera.html @@ -0,0 +1,14287 @@ + + +
+ +En 1854, le quartier de Soho à Londres a vécu une des pires épidémies de choléra du Royaume-Uni, avec 616 morts. Cette épidémie est devenue célèbre à cause de l'analyse détaillée de ses causes réalisée par le médecin John Snow. Ce dernier a notamment montré que le choléra est transmis par l'eau plutôt que par l'air, ce qui était la théorie dominante de l'époque.
+ +Tout d'abord récupérons les données numériques mis à notre disposition sur ce blog. Nous pouvons retrouver sur ce site deux archives SnowGIS_SHP.zip et SnowGIS_KML.zip contenant des formats de fichiers différents. Le site mettait aussi à disposition des liens google où les données étaient directement accessibles mais malheureusement ces liens ne sont plus fonctionnels. Nous utiliserons donc les données d'une des archives téléchargées et importées afin de garantir la pérennité et l'accessibilité à ces données.
+Nous avons 4 informations à notre disposition dans ces archives :
+Étant donné que nous utiliserons la bibliothèque folium pour l'affichage de la carte, seules les deux premières nous seront utiles. Si la bibliothèque n'est pas présente dans l'environnement, il faudra l'installer avec la commande pip install folium
.
La première archive SnowGIS_SHP contient des fichiers SHP (Shapefiles) qui sont interpretable par la bibliothèque geopandas. Nous ne nous sera pas nécessaire d'utiliser l'autre archive avec les fichiers KML.
+Nous utiliserons la bibliothèque geopandas pour l'importation des données au format .shp
.
+Si la bibliothèque geopandas n'est pas présente dans l'environnement, il faudra l'installer avec la commande pip install geopandas
.
pip install geopandas
+
import geopandas as gpd
+death_cholera_location = gpd.read_file("Cholera_Deaths.shp", crs={"init": "epsg:4326"})
+pumps_location = gpd.read_file("Pumps.shp", crs={"init": "epsg:4326"})
+
Visualisons les données que nous avons concernant les décès dus au choléra. Comme il était indiqué, nous avons le nombre (count) et la localisation (geometry) des décès.
+ +death_cholera_location
+
Vérifions d'abord s'il n'y a pas de données manquantes.
+ +death_cholera_location.isna().any().any()
+
Bonne nouvelle, il n'y a pas de données manquantes.
+Nous pouvons d'ors et déjà remarquer que les points sont des coordonnées X/Y et non latitude/longitude car les valeurs de celles-ci doivent être comprises entre +90°/-90° et +180°/-180°. Or la bibliothèque folium prend en compte ces valeurs pour le positionnement de points. Il sera donc nécessaire d'effectuer une conversion.
+ +death_cholera_location = death_cholera_location.to_crs(epsg=4326) #Conversion au bon format
+
Regardons si la conversion a bien été faites.
+ +death_cholera_location
+
Les coordonnées ressemblent déjà plus à ce que l'on recherche. Faisons une vérification rapide sur la carte de la localisation avec les premières coordonnées.
+ +pip install folium
+
import folium as fl
+m = fl.Map(location=[-0.13667, 51.51334],zoom_start=5)
+m
+
Il semblerait que les coordonnées soient mauvaises. Après vérification sur la documentation de geopandas, POINT = (Longitude, Latitude). Et pour folium, nous rentrons location=[Latitude, Longitude]
.
Essayons d'inverser les valeurs et revérifions.
+ +m = fl.Map(location=[51.51342, -0.13793],zoom_start=10)
+m
+
Nous sommes bien à Londre, les coordonnées semblent donc être correct.
+Maintenant que nous nous sommes assuré de la conformité des données, réitérons la même chose pour la liste des pompes :
+ +pumps_location = pumps_location.to_crs(epsg=4326) #Conversion au bon format
+pumps_location
+
Essayons maintenant d'afficher des cercles de circonférence proportionnelle au nombre du décès et des symboles pour la localisation des pompes.
+Reprenons notre carte précédente avec un zoom plus important.
+ +m = fl.Map(location=[51.51334, -0.13667],zoom_start=17)
+for index, death_row in death_cholera_location.iterrows():
+ death_count = death_row['Count']
+ point_geometry = death_cholera_location.geometry[index]
+ fl.Circle(
+ radius=death_count,
+ location=[point_geometry.centroid.y,point_geometry.centroid.x], #Ne pas oublier que longitude et latitude sont inversées.
+ tooltip=death_count,
+ color='crimson',
+ fill=True,
+ ).add_to(m)
+m
+
Voilà pour la localisation des décès, le radius du cercle correspondant au nombre de décès.
+Ajoutons maintenant les pompes.
+ +for index, pump_row in pumps_location.iterrows():
+ point_geometry = pumps_location.geometry[index]
+ fl.Marker([point_geometry.centroid.y,point_geometry.centroid.x], popup='<b>Pompes '+str(index)+'</b>').add_to(m) #Ne pas oublier que longitude et latitude sont inversées.
+m
+
Nous pouvons en effet remarquer qu'une pompe se trouve au centre de ces décès et proche d'une zone ayant eu un grand nombre de décès. Il s'agit de la pompe se trouvant dans la rue Broadwick Street. Il s'agit de la pompe 0, soit la première de la liste fournit.
+Enfin de mettre en évidence la pompe de Broadwick Street comme ayant eu un impact dans l'épidémie de choléra, nous pouvons également comparer le nombre de décès autour de chacune des pompes dans un rayon définit. Pour cela, nous utiliserons la librairie haversine
pour le calcul de distance entre deux coordonnées.
pip install haversine
+
Enfin de délimiter et de définir une taille de rayon commune autour de chacune des pompes, calculons la distance moyenne des pompes entre elles.
+ +import haversine as hs
+from haversine import Unit
+import numpy as np
+
+list_range_mean = []
+for index_pump, pump_row in pumps_location.iterrows():
+ pump_point_geometry = pumps_location.geometry[index_pump]
+ list_relative_distances = []
+ # Pour chaque pompe, on calcule la distance relative avec les autres pompes.
+ for index, pump_row in pumps_location.iterrows():
+ if index != index_pump: # On oublie pas d'exclure la pompe de référence pour le calcule.
+ death_point_geometry = pumps_location.geometry[index]
+ distance = hs.haversine((death_point_geometry.centroid.y,death_point_geometry.centroid.x),(pump_point_geometry.centroid.y,pump_point_geometry.centroid.x), unit=Unit.METERS)
+ list_relative_distances.append(distance)
+ # On fait la moyenne des distance pour chaque pompes.
+ list_range_mean.append(np.mean(list_relative_distances))
+# Il suffit ensuite de calculer la moyenne de l'ensemble.
+max_range = np.mean(list_range_mean)
+# On affiche le résultat
+max_range
+
Les pompes sont donc, en moyenne, à 347 mètres l'une de l'autre.
+Calculons le nombre de décès se trouvant dans un rayon de 347 mètres autour de chaque pompe.
+ +import pandas as pd
+df_pump_death = pd.DataFrame(columns=['Pompe Index', 'Latitude', 'Longitude', 'Nombre de décès dans un rayon de 300m'])
+for index_pump, pump_row in pumps_location.iterrows():
+ death_count = 0
+ pump_point_geometry = pumps_location.geometry[index_pump]
+ for index, death_row in death_cholera_location.iterrows():
+ death_point_geometry = death_cholera_location.geometry[index]
+ distance = hs.haversine((death_point_geometry.centroid.y,death_point_geometry.centroid.x),(pump_point_geometry.centroid.y,pump_point_geometry.centroid.x), unit=Unit.METERS)
+ if distance <= max_range:
+ death_count += death_row['Count']
+
+ df_pump_death = df_pump_death.append({'Pompe Index': index_pump, 'Latitude': pump_point_geometry.centroid.y, 'Longitude': pump_point_geometry.centroid.x, 'Nombre de décès dans un rayon de 300m': death_count}, ignore_index=True)
+df_pump_death
+
On remarque tout de suite que la pompe avec le plus de décès dans un rayon moyen de 347 mètres est la pompe à l'index 0, qui correspond à la pompe de Broadwick Street.
+Visualisons le résultat sur la carte en modifiant l'opacité suivant le nombre de décès.
+ +m = fl.Map(location=[51.51334, -0.13667],zoom_start=15)
+max_dead = np.max(df_pump_death['Nombre de décès dans un rayon de 300m'])
+for index, pump_row in df_pump_death.iterrows():
+ fl.Circle(
+ radius=max_range,
+ stroke=True,
+ weight=5,
+ location=[pump_row['Latitude'],pump_row['Longitude']], #Ne pas oublier que longitude et latitude sont inversées.
+ tooltip=pump_row['Nombre de décès dans un rayon de 300m'],
+ color='crimson',
+ opacity=pump_row['Nombre de décès dans un rayon de 300m']/max_dead,
+ fillOpacity=pump_row['Nombre de décès dans un rayon de 300m']/max_dead,
+ fill=True,
+ ).add_to(m)
+ fl.Marker([pump_row['Latitude'],pump_row['Longitude']], popup='<b>Pompes '+str(index)+'</b>').add_to(m) #Ne pas oublier que longitude et latitude sont inversées.
+m
+
Et affichons uniquement le rayon de la pompe de Broadwick Street ainsi que l'emplacement des décès.
+ +m = fl.Map(location=[51.51334, -0.13667],zoom_start=16)
+# Emplacement des décès
+for index, death_row in death_cholera_location.iterrows():
+ death_count = death_row['Count']
+ point_geometry = death_cholera_location.geometry[index]
+ fl.Circle(
+ radius=death_count,
+ location=[point_geometry.centroid.y,point_geometry.centroid.x], #Ne pas oublier que longitude et latitude sont inversées.
+ tooltip=death_count,
+ color='crimson',
+ fill=True,
+ ).add_to(m)
+# Pompe de Broadwick Street
+fl.Circle(
+ radius=max_range,
+ stroke=True,
+ weight=5,
+ location=[df_pump_death.values[0][1],df_pump_death.values[0][2]],
+ tooltip=df_pump_death.values[0][3],
+ color='orange',
+ opacity=df_pump_death.values[0][3]/max_dead,
+ fillOpacity=df_pump_death.values[0][3]/max_dead,
+ fill=True,
+ ).add_to(m)
+fl.Marker([df_pump_death.values[0][1],df_pump_death.values[0][2]], popup='<b>Pompes de Broadwick Street</b>').add_to(m)
+m
+
Nous pouvons visualiser que la pompe de Broadwick est belle et bien au centre de cette épidémie.
+from folium.plugins import HeatMap
+
+df_death = pd.DataFrame(columns=['Index', 'Latitude', 'Longitude', 'Nb décès'])
+for index, death_row in death_cholera_location.iterrows():
+ point_geometry = death_cholera_location.geometry[index]
+ df_death = df_death.append({'Index': index, 'Latitude': point_geometry.centroid.y, 'Longitude': point_geometry.centroid.x, 'Nb décès': death_row['Count']}, ignore_index=True)
+
+
+m = fl.Map(location=[51.51334, -0.13667],zoom_start=16)
+HeatMap(data=df_death[['Latitude', 'Longitude', 'Nb décès']].groupby(['Latitude', 'Longitude']).sum().reset_index().values.tolist(), radius=20).add_to(m)
+fl.Marker([51.51334,-0.13667], popup='<b>Pompes de </b>').add_to(m) #Ne pas oublier que longitude et latitude sont inversées.
+m
+
+