# !pip install folium scikit-learn scipy
import numpy as np
import pandas as pd
import folium
import matplotlib.pyplot as plt
from sklearn.cluster import KMeans
from scipy.spatial import distance
%matplotlib inline
Cette étude porte sur la construction d'une carte épidémiologique afin de mieux comprendre l'épidémie de choléra dans le quartier de Soho à Londres en 1854. Par l'analyse des données, nous cherchons à trouver le centre de l'épidémie et prouver sa proximité avec l'une des pompes d'une quartier.
data_death = pd.read_csv("deaths.csv")
data_pumps = pd.read_csv("pumps.csv")
data_death_pumps = pd.read_csv("deaths_and_pumps.csv")
print("""
Death dataset columns : {}
Pumps dataset columns : {}
Death/Pumps dataset columns : {}
""".format(list(data_death.columns), list(data_pumps.columns), list(data_death_pumps.columns)))
On renomme les colonnes pour éviter les typos à cause des majuscules et des espaces.
death_cols = {
list(data_death.columns)[0]: 'd_count',
list(data_death.columns)[1]: 'x',
list(data_death.columns)[2]: 'y'}
pump_cols = {
list(data_pumps.columns)[0]: 'name',
list(data_pumps.columns)[1]: 'x',
list(data_pumps.columns)[2]: 'y'}
d_p_cols = {
list(data_death_pumps.columns)[0]: 'death_per_pumps',
list(data_death_pumps.columns)[1]: 'x',
list(data_death_pumps.columns)[2]: 'y'}
data_death.rename(columns=death_cols, inplace=True)
data_pumps.rename(columns=pump_cols, inplace=True)
data_death_pumps.rename(columns=d_p_cols, inplace=True)
Un petit regard sur la donnée.
print(data_death.head())
print('\n')
print(data_pumps.head())
print('\n')
print(data_death_pumps.head())
print("Donnée manquante dans le dataset death.csv : {}".format(len(data_death[data_death.isnull().any(axis=1)])))
print("Donnée manquante dans le dataset pumps.csv : {}".format(len(data_pumps[data_pumps.isnull().any(axis=1)])))
print("Donnée manquante dans le dataset death_and_pumps.csv : {}".format(len(data_death_pumps[data_death_pumps.isnull().any(axis=1)])))
data_death_df = data_death.groupby(['x', 'y']).d_count.count().to_frame()
data_death_df.reset_index(inplace=True)
death_coordinates = data_death_df[["x","y"]]
death_coordinates = death_coordinates.values.tolist()
soho_c = death_coordinates[0]
death_map = folium.Map(location=soho_c, tiles='Stamen Toner', zoom_start=17)
for p in range(0, len(death_coordinates)):
folium.CircleMarker(death_coordinates[p], radius=2*int(data_death_df['d_count'][p]),
color='blue', fill=True, fill_color='blue',
opacity = 0.4).add_to(death_map)
death_map
On y ajoute ensuite les emplacements de pompes.
pump_coordinates = data_pumps[["x","y"]]
pump_coordinates = pump_coordinates.values.tolist()
death_pump_map = death_map
for p in range(0, len(pump_coordinates)):
folium.Marker(pump_coordinates[p],
popup='Name : {}'.format(data_pumps['name'][p]),
icon=folium.Icon(color='red', icon='info-sign')).add_to(death_pump_map)
death_pump_map
Sur la carte précédente on voit très clairement par un cercle de diamètre supérieur aux autres, que la plus grande densité de décès se trouve au plus près de la pompe de Broad St. Essayons de le démontrer par l'analyse.
On peut par exemple utiliser l'algorithme K-means pour former des clusters et vérifier quelle pompe se trouve au centre du cluster contenant le plus de cas.
On commence par initialiser K-means avec un nombre de clusters correspondant au nombre de pompes.
n_pumps = len(data_pumps)
kmeans = KMeans(n_clusters = n_pumps, init ='k-means++')
kmeans.fit(data_death[data_death.columns[1:3]])
data_death['cluster_label'] = kmeans.fit_predict(data_death[data_death.columns[1:3]])
centers = kmeans.cluster_centers_
labels = kmeans.predict(data_death[data_death.columns[1:3]])
On observe la répartition des clusters.
data_death.plot.scatter(x = 'x', y = 'y', c=labels, s=20,
ylim=[data_pumps['y'].min()-0.001, data_pumps['y'].max()-0.001],
xlim=[data_pumps['x'].min()+0.0015, data_pumps['x'].max()+0.001], cmap='viridis')
plt.scatter(centers[:, 0], centers[:, 1], c='red', s=100, alpha=0.8)
plt.show()
Ici on voit que le nombre de clusters est trop grand. Pour certains points, l'appartenance à un cluster plus qu'un autre n'apparaît pas clair. On voit d'ailleurs sur la carte que beaucoup de pompe sont à l'extérieur du centre de l'épidémie. Il nous faut trouver le nombre optimal de cluster possible. Pour ça on utilise la méthode Elbow Curve)
K_clusters = range(1, n_pumps)
kmeans = [KMeans(n_clusters=i) for i in K_clusters]
Y_axis = data_death[['x']]
X_axis = data_death[['y']]
score = [kmeans[i].fit(Y_axis).score(Y_axis) for i in range(len(kmeans))]
plt.plot(K_clusters, score)
plt.xlabel('Nombre de clusters')
plt.ylabel('Score')
plt.title('Elbow Curve')
plt.show()
La courbe nous montre que le nombre de K optimal est 3.
kmeans = KMeans(n_clusters = 3, init ='k-means++')
kmeans.fit(data_death[data_death.columns[1:3]])
data_death['cluster_label'] = kmeans.fit_predict(data_death[data_death.columns[1:3]])
centers = kmeans.cluster_centers_
labels = kmeans.predict(data_death[data_death.columns[1:3]])
data_death.plot.scatter(x = 'x', y = 'y', c=labels, s=20,
ylim=[data_pumps['y'].min()-0.001, data_pumps['y'].max()-0.001],
xlim=[data_pumps['x'].min()+0.0015, data_pumps['x'].max()+0.001], cmap='viridis')
plt.scatter(centers[:, 0], centers[:, 1], c='red', s=100, alpha=0.8)
plt.show()
On récupère le cluster qui contient le plus de cas de décès.
cluster_by_death = data_death.groupby('cluster_label').count()['d_count']
max_cluster = cluster_by_death.idxmax()
print('Cluster avec le plus grand nombre de morts : {}'.format(max_cluster))
cluster_by_death
On peut placer les clusters sur la map pour en avoir une meilleure représentation et vérifier que le cluster trouvé se trouve près de la pompe de Broad St.
cluster_map = death_pump_map
for p in range(0, len(centers)):
folium.Marker(centers[p],
popup='Cluster : {}'.format(p),
icon=folium.Icon(color='green')).add_to(death_pump_map)
cluster_map
On vérifie notre hypothèse visuellement. On peut aussi calculer la distance euclidienne entre le cluster 0 et les pompes pour vérifier qu'il est au plus près de la pompe de Broad St.
c_0_coordinates = centers[max_cluster]
euclidean_distances = []
for i in pump_coordinates:
euclidean_distances.append(distance.euclidean(i, c_0_coordinates))
L'indice de la distance minimale nous donne l'indice de la pompe au centre de l'épidémie.
pump_idx = euclidean_distances.index(min(euclidean_distances))
data_pumps.iloc[pump_idx]
On prouve donc par clustering que la pompe de Broad St. est au centre de l'épidémie.