<SYSTEM_TASK:>
Given the following text description, write Python code to implement the functionality described below step by step
<END_TASK>
<USER_TASK:>
Description:
 2A.ml - Clustering - correction
Ce notebook utilise les données des vélos de Chicago Divvy Data. Il s'inspire du challenge créée pour découvrir les habitudes des habitantes de la ville City Bike. L'idée est d'explorer plusieurs algorithmes de clustering et comment trafiquer les données pour les faire marcher.
Step1:  Les données
Elles ont été prétraitées selon le notebook Bike Pattern 2. Elles représentent la distribution du nombre de vélos partant (startdist) et arrivant (stopdist). On utilise le clustering pour découvrir les différents usages des habitants de Chicago avec pour intuition le fait que les habitants de Chicago utilise majoritairement les vélos pour aller et venir entre leur appartement et leur lieu de travail. Cette même idée mais à Paris est illustrée par ce billet de blog 
Step2:  Les données sont agrégrées par tranche de 10 minutes soit 144 période durant la journée et 288 nombre pour les départs et arrivées de vélos. Cela explique les dimensions de la matrice.
k-means
On cherche à trouver différentes zones de la villes pour différents usages. Zones de résidences, zones de travail, zone d'amusements et on suppose que les heures de départs et d'arrivées reflètent ces usages. Dans une zone de travail, typiquement le quartier d'affaires, les vélos arriveront principalement le matin et repartiront le soir. Ce sera l'inverse pour une zone de résidences. C'est pour cela que les arrivées et les départs des vélos ont été agrégés par jour de la semaine. La distribution des arrivées risquent d'être bien différentes le week-end.
Step3:  Let's draw the clusters.
Step4:  On a réussi à isoler plusieurs usages différents. On voit les départs les matin et les arrivées le soir, le modèle inversé, un autre... Mais les-a-t-on tous trouvés ?
Exercice 1 
Step5:  L'algorithme dbscan utilise la proximité des points pour classer les observations. On se retrouve dans le cas où il n'y a pas vraiment de frontière entre les clusters et tous les points se retrouvent associés en un unique cluster excepté quelques points aberrants.
L'algorithme DBScan ne fonctionne pas sur ces données. Une des raisons que la distance dans un espace vectoriels avec tant de dimensions n'est pas loin d'une information type binaire 
Step6:  Une carte des stations un jour de semaine.
<ASSISTANT_TASK:>
Python Code:
from jyquickhelper import add_notebook_menu
add_notebook_menu()
%matplotlib inline
Explanation: 2A.ml - Clustering - correction
Ce notebook utilise les données des vélos de Chicago Divvy Data. Il s'inspire du challenge créée pour découvrir les habitudes des habitantes de la ville City Bike. L'idée est d'explorer plusieurs algorithmes de clustering et comment trafiquer les données pour les faire marcher.
End of explanation
from pyensae.datasource import download_data
file = download_data("features_bike_chicago.zip")
file
import pandas
features = pandas.read_csv("features_bike_chicago.txt", sep="\t", encoding="utf-8", low_memory=False, header=[0,1])
features.columns = ["station_id", "station_name", "weekday"] + list(features.columns[3:])
features.head()
features.shape
Explanation: Les données
Elles ont été prétraitées selon le notebook Bike Pattern 2. Elles représentent la distribution du nombre de vélos partant (startdist) et arrivant (stopdist). On utilise le clustering pour découvrir les différents usages des habitants de Chicago avec pour intuition le fait que les habitants de Chicago utilise majoritairement les vélos pour aller et venir entre leur appartement et leur lieu de travail. Cette même idée mais à Paris est illustrée par ce billet de blog : Busy areas in Paris.
End of explanation
names = features.columns[3:]
from sklearn.cluster import KMeans
clus = KMeans(10)
clus.fit(features[names])
pred = clus.predict(features[names])
set(pred)
features["cluster"] = pred
features[["cluster", "weekday", "station_id"]].groupby(["cluster", "weekday"]).count()
nb = features[["cluster", "weekday", "station_id"]].groupby(["cluster", "weekday"]).count()
nb = nb.reset_index()
nb[nb.cluster.isin([0, 3, 5, 6])].pivot("weekday","cluster", "station_id").plot(kind="bar");
Explanation: Les données sont agrégrées par tranche de 10 minutes soit 144 période durant la journée et 288 nombre pour les départs et arrivées de vélos. Cela explique les dimensions de la matrice.
k-means
On cherche à trouver différentes zones de la villes pour différents usages. Zones de résidences, zones de travail, zone d'amusements et on suppose que les heures de départs et d'arrivées reflètent ces usages. Dans une zone de travail, typiquement le quartier d'affaires, les vélos arriveront principalement le matin et repartiront le soir. Ce sera l'inverse pour une zone de résidences. C'est pour cela que les arrivées et les départs des vélos ont été agrégés par jour de la semaine. La distribution des arrivées risquent d'être bien différentes le week-end.
End of explanation
centers = clus.cluster_centers_.T
import matplotlib.pyplot as plt
fig, ax = plt.subplots(centers.shape[1], 2, figsize=(10,10))
nbf = centers.shape[0] // 2
x = list(range(0,nbf))
col = 0
dec = 0
colors = ["red", "yellow", "gray", "green", "brown", "orange", "blue"]
for i in range(centers.shape[1]):
    if 2*i == centers.shape[1]:
        col += 1
        dec += centers.shape[1]    
    color = colors[i%len(colors)]
    ax[2*i-dec, col].bar (x, centers[:nbf,i], width=1.0, color=color)
    ax[2*i-dec, col].set_ylabel("cluster %d - start" % i, color=color)
    ax[2*i+1-dec, col].bar (x, centers[nbf:,i], width=1.0, color=color)
    ax[2*i+1-dec, col].set_ylabel("cluster %d - stop" % i, color=color)
Explanation: Let's draw the clusters.
End of explanation
from sklearn.cluster import DBSCAN
dbs = DBSCAN(eps=0.1)
pred_dbs = dbs.fit_predict(features[names])
set(pred_dbs)
features["cluster_dbs"] = pred_dbs
nbs = features[["cluster_dbs", "weekday", "station_id"]].groupby(["cluster_dbs", "weekday"]).count()
nbs = nbs.reset_index()
nbs[nbs.cluster_dbs.isin([0, 3, 5, 6])].pivot("weekday","cluster_dbs", "station_id").plot(kind="bar");
Explanation: On a réussi à isoler plusieurs usages différents. On voit les départs les matin et les arrivées le soir, le modèle inversé, un autre... Mais les-a-t-on tous trouvés ?
Exercice 1 : petits clusters
Les petits clusters viennent du fait qu'on impose un nombre de clusters à l'algorithme qui s'efforce de les trouver. Quelques points aberrents sont trop éloignés et isolés du reste des autres. Pour les enlever, on peut simplement réduire le nombre de clusters ou éliminer les points aberrants et les garder comme anomalies.
Les petits clusters sont des stations peu utilisées. Le problème quand on calcule une distribution et qu'on la normalise, typiquement celles des arrivées de vélos à une stations, c'est qu'on perd l'information du nombre de vélos ayant servi à l'estimer. 
L'algorithme k-means++ commence à choisir les points les plus éloignés des uns des autres et cela tombe sur ces stations peu utilisées. Vu le nombre de dimension, 288, les distances sont rapidement très grandes.
Une idée consiste à retirer ces stations peu utilisées ou boucler sur un algorithme du type :
Faire tourner les k-means.
Retirer les stations dans des clusters isolées.
Retour à l'étape 1 jusqu'à ce que cela soit interprétable.
Exercice 2 : autres types de clustering
End of explanation
piv = features.pivot_table(index=["station_id", "station_name"], 
                           columns="weekday", values="cluster")
piv.head()
piv["distincts"] = piv.apply(lambda row: len(set(row[i] for i in range(0,7))), axis=1)
pivn = piv.reset_index()
pivn.head()
pivn.columns = [str(_).replace(".0", "") for _ in pivn.columns.values]
pivn.head()
Explanation: L'algorithme dbscan utilise la proximité des points pour classer les observations. On se retrouve dans le cas où il n'y a pas vraiment de frontière entre les clusters et tous les points se retrouvent associés en un unique cluster excepté quelques points aberrants.
L'algorithme DBScan ne fonctionne pas sur ces données. Une des raisons que la distance dans un espace vectoriels avec tant de dimensions n'est pas loin d'une information type binaire : différent partout ou identique presque partout. Qu'un vélo arrive 9h10 ou à 9h20, cela n'a pas beaucoup d'importance pour notre problème, pourtant, la distance choisie lui donnera autant d'importance que si le vélo était arrivé à 10h du soir. Pour éviter cela, il faudrait lisser les distributions avec une moyenne mobile.
Graphes
Un peu de code pour voir la réparition des clusters sur une carte.
End of explanation
from pyensae.datasource import download_data
if False:
    # Provient du site de Chicago
    file = download_data("Divvy_Trips_2016_Q3Q4.zip",
                         url="https://s3.amazonaws.com/divvy-data/tripdata/")
else:
    # Copie au cas où celui-ci tomberait en panne
    file = download_data("Divvy_Trips_2016_Q3.zip")    
stations = pandas.read_csv("Divvy_Stations_2016_Q3.csv")
stations.head()
data = stations.merge(pivn, left_on=["id", "name"],
                      right_on=["station_id", "station_name"], suffixes=('_s', '_c'))
data.sort_values("id").head()
def folium_html_stations_map(stations, html_width=None, html_height=None, **kwargs):
    import folium
    from pyensae.notebookhelper import folium_html_map
    map_osm = None
    for key, value in stations:
        x, y = key
        if map_osm is None:
            if "zoom_start" not in kwargs:
                kwargs["zoom_start"] = 11
            if "location" not in kwargs:
                map_osm = folium.Map(location=[x, y], **kwargs)
            else:
                map_osm = folium.Map(kwargs["location"], **kwargs)
        if isinstance(value, tuple):
            name, value = value
            map_osm.add_child(folium.CircleMarker(
                [x, y], popup=name, radius=15, fill_color=value, color=value))
        else:
            map_osm.add_child(folium.CircleMarker(
                [x, y], radius=15, fill_color=value, color=value))
    return folium_html_map(map_osm, width=html_width, height=html_height)
colors = ["red", "yellow", "gray", "green", "brown", "orange", "blue", "black", "pink", "violet"]
for i, c in enumerate(colors):
    print("Cluster {0} is {1}".format(i, c))
xy = []
for els in data.apply(lambda row: (row["latitude"], row["longitude"], row["1"], row["name"]), axis=1):
    try:
        cl = int(els[2])
    except:
        # NaN
        continue
    name = "%s c%d" % (els[3], cl)
    color = colors[cl]
    xy.append( ( (els[0], els[1]), (name, color)))
folium_html_stations_map(xy, width="80%")
Explanation: Une carte des stations un jour de semaine.
End of explanation
<END_TASK>