Clustering_UBER / src /app.py
Djohell's picture
Update src/app.py
ec69967 verified
Raw
History Blame Contribute Delete
17.3 kB
import pandas as pd
import streamlit as st
import plotly.express as px
import boto3
import plotly.graph_objects as go
import itertools
from sklearn.cluster import KMeans, DBSCAN
@st.cache_data
def compute_kmeans(df, n_clusters):
coords = df[["Lat", "Lon"]]
kmeans = KMeans(n_clusters=n_clusters, random_state=42)
df = df.copy()
df["clusterKmeans"] = kmeans.fit_predict(coords)
return df
st.set_page_config(page_title="UBER – Analyse des clusters", layout="wide")
st.markdown("""
<style>
.stTabs [role="tablist"] {
justify-content: center;
gap: 25px;
}
.stTabs [role="tab"] {
font-size: 17px;
font-weight: 600;
padding: 10px 20px;
border-radius: 10px;
background-color: #f8f9fa;
color: #444;
transition: all 0.3s ease-in-out;
}
.stTabs [role="tab"]:hover {
background-color: #e0e0e0;
transform: scale(1.05);
}
.stTabs [role="tab"][aria-selected="true"] {
background-color: #4CAF50;
color: white;
font-weight: 700;
box-shadow: 0px 4px 8px rgba(0,0,0,0.2);
}
</style>
""", unsafe_allow_html=True)
st.title("🚕 Bonjour et bienvenue sur le dashboard du projet Clustering d'UBER ! 🚕")
st.subheader(
"Ce projet vise à vérifier la concordance entre l'implantation des taxis UBER et la demande des clients. " \
"Les données utilisées datent d'Avril 2014 et proviennent de la ville de New York.")
tab1, tab2, tab3 = st.tabs(["📊⏱ Implantation des taxis et besoins", "🫧🫧🫧 Analyse par jour de la semaine", " ✍️ Constat et conclusions"])
# --- Import des datasets
taxi = pd.read_csv("https://uber-cdsd-joel.s3.eu-west-3.amazonaws.com/uber-trip-data/taxi-zone-lookup.csv")
apr_14 = pd.read_csv("https://uber-cdsd-joel.s3.eu-west-3.amazonaws.com/uber-trip-data/apr_14_clean.csv")
# --- Préparer la densité des stations taxis
def compute_taxi_density(taxi_df):
density = taxi_df['Borough'].value_counts()
df = density.reset_index()
df.columns = ['Borough', 'Count']
df = df[df['Borough'] != "Unknown"]
# Centres approximatifs
borough_centers = {
'Manhattan': [40.7831, -73.9712],
'Brooklyn': [40.6782, -73.9442],
'Queens': [40.7282, -73.7949],
'Bronx': [40.8448, -73.8648],
'Staten Island': [40.5795, -74.1502],
'EWR': [40.6895, -74.1745]
}
df['Latitude'] = df['Borough'].map(lambda x: borough_centers.get(x, [40.7128, -74.0060])[0])
df['Longitude'] = df['Borough'].map(lambda x: borough_centers.get(x, [40.7128, -74.0060])[1])
return df
df_density = compute_taxi_density(taxi)
with tab1:
st.subheader("1. Regardons dans un premier temps comment Uber a implanté ses différentes bases de taxi.")
# créer le graphique Plotly
fig = px.scatter_mapbox(
df_density,
lat='Latitude',
lon='Longitude',
size='Count',
size_max=50,
color='Borough',
hover_name='Borough',
hover_data={'Count':True, 'Latitude':False, 'Longitude':False},
zoom=10,
height=800,
mapbox_style='carto-positron'
)
# afficher dans Streamlit
st.plotly_chart(fig)
# Conclusions sur l'implantation des taxis
st.markdown("<h4>👉 Manhattan et le Queens sont les principaux foyers de taxis. "
"Suivent Brooklyn, le Bronx, Staten Island et enfin l'aéroport de Newark</h4>", unsafe_allow_html=True)
st.markdown("---")
# Visualisation heatmap et conclusion
st.header("2. Les pics horaires")
st.subheader("- Quels sont les créneaux les plus critiques ?")
image_url = "https://uber-cdsd-joel.s3.eu-west-3.amazonaws.com/heatmap+UBER.png"
st.markdown(
f"""
<div style="display: flex; justify-content: center;">
<img src="{image_url}" width="1000">
</div>
""",
unsafe_allow_html=True
)
st.write("")
st.markdown("""
<h5>👉 Observations sur les horaires des trajets :</h5>
<ul>
<li><strong>Matinée (6h-9h, jours de semaine) :</strong> activité principalement liée aux trajets professionnels.</li>
<li><strong>Après-midi (14h-22h) :</strong> densité accrue le mercredi entre 15h et 21h.</li>
<li><strong>Vendredi :</strong> activité constante en fin de journée jusqu'au samedi 1h du matin.</li>
<li><strong>Samedi :</strong> concentration entre 15h et dimanche 1h du matin, liée aux loisirs.</li>
</ul>
<p>Ces chiffres mettent en avant les besoins des clients pour les trajets professionnels le matin et pour les loisirs le soir et le week-end.</p>
""", unsafe_allow_html=True)
st.markdown("---")
st.subheader("3. Rentrons maintenant dans le détail des clusters d'activité d'Uber sur cette période ! " \
"Pour ce faire, l'algorithme Kmeans sera utilisé comme première approche.")
# KMeans : Préparation du dataset pour visu des clusters du mercredi 17h
apr_14_wed_17 = apr_14[(apr_14['day_of_week'] == 'Wednesday') & (apr_14['hour'] == 17)].copy()
coords = apr_14_wed_17[['Lat', 'Lon']]
kmeans = KMeans(n_clusters=8, random_state=42)
apr_14_wed_17['clusterKmeans'] = kmeans.fit_predict(coords)
fig = px.scatter_mapbox(
apr_14_wed_17,
lat="Lat",
lon="Lon",
color="clusterKmeans",
height=800,
width=600,
zoom=10,
mapbox_style="carto-positron")
st.plotly_chart(fig, use_container_width=True)
st.markdown("""
### Les clusters définis dans Kmeans mettent en avant des zones distinctes :
- Le Sud de Manhattan (cluster 3)
- Le Centre de Manhattan (cluster 1)
- Le Nord de Manhattan avec notamment une concentration dans la moitié sud de Central Park (cluster 7), ainsi que le nord de Central Park (cluster 4)
- Les aéroports de La Guardia (cluster 0), JFK (cluster 2), et Newark (cluster 5)
Cela parait tout à fait pertinent compte-tenu du jour et de l'heure choisis.
En effet, cela met en avant les actifs de Manhattan ainsi que les affluences aux différents aéroports.
Les clusters 1, 3, 4 et 7 sont donc très denses et sont les plus importants.
""")
st.markdown("---")
st.subheader("""
👉 Vérifions maintenant la concordance entre les clusters des courses et les implantations des taxis"""
)
st.write("")
# Visualisation Clusters Kmeans mercredi 17h avec concordance densité taxis
apr_14_wed_17 = apr_14[
(apr_14['day_of_week'] == 'Wednesday') &
(apr_14['hour'] == 17)
].copy()
coords = apr_14_wed_17[['Lat', 'Lon']]
kmeans = KMeans(n_clusters=8, random_state=42)
apr_14_wed_17['clusterKmeans'] = kmeans.fit_predict(coords)
# Figure Mapbox unique
fig = go.Figure()
# --- couche clusters (points)
fig.add_trace(go.Scattermapbox(
lat=apr_14_wed_17['Lat'],
lon=apr_14_wed_17['Lon'],
mode='markers',
marker=dict(
size=6,
color=apr_14_wed_17['clusterKmeans'],
colorscale='Viridis',
opacity=0.6
),
name='Clusters (Mercredi 17h)',
hoverinfo='text'
))
# --- couche densité boroughs
fig.add_trace(go.Scattermapbox(
lat=df_density['Latitude'],
lon=df_density['Longitude'],
mode='markers',
marker=dict(
size=df_density['Count'],
sizemode='area',
sizeref=2.*max(df_density['Count'])/(50.**2),
color='red',
opacity=0.7
),
text=df_density['Borough'] + "<br>Count: " + df_density['Count'].astype(str),
name='Densité par Borough'
))
# Layout commun
fig.update_layout(
mapbox=dict(
style="carto-positron",
center=dict(lat=40.73, lon=-73.94),
zoom=10
),
height=900,
width=600,
margin=dict(l=0, r=0, t=0, b=0)
)
st.plotly_chart(fig)
st.markdown("""
### Compte-tenu des données prises en compte avec notamment un centrage sur les coordonnées de New-York :
- L'implantation dans Manhattan est bien évidemment pertinente, la localisation exacte n'étant pas précisée.
- Le Bronx dispose d'un flotte importante qui reste à proximité de Manhattan.
- L'implantation dans le Queens paraît discutable, mais nous ne disposons pas de la totalité de l'information des courses potentielles du secteur.
- L'aéroport de Newark paraît sous-dimensionné
- Staten Island ne paraît pas justifié, mais encore une fois les données ne concernent que New-York.
""")
st.write("---")
st.subheader("👉 Procédons maintenant avec un second algorithme de clustering : DBScan. " \
"Il a pour propriété notamment de traiter les outliers et de proposer une vision plus concise des clusters.")
# TEST DE DB SCAN
# Sélection des coordonnées
X = apr_14_wed_17[['Lat', 'Lon']]
# DBSCAN clustering avec données définies dans notebook
db = DBSCAN(
eps=0.008,
min_samples=10,
metric="manhattan"
)
db.fit(X)
# Ajout des labels au DataFrame
apr_14_wed_17["clusterDB"] = db.labels_
# Filtrer les outliers (-1)
df_clusters = apr_14_wed_17[apr_14_wed_17["clusterDB"] != -1]
# Carte
fig = px.scatter_mapbox(
df_clusters,
lat="Lat",
lon="Lon",
color="clusterDB",
zoom=10,
height=1000,
mapbox_style="carto-positron"
)
st.plotly_chart(fig, use_container_width=True)
st.markdown("""
### Comme attendu, DBScan nous donne des clusters moins étendus, plus recentrés :
- Un total de 11 clusters.
- Manhattan n'est plus divisé et représente un cluster unique.
- Les 3 aéroports sont bien identifiés.
- Des clusters sont répartis autour de Manhattan (clusters 1, 2, 3, 5, 11 et 9).
""")
st.markdown("---")
st.subheader("👉 Vérifions enfin la concordance entre les clusters DBSCAN et l'implantation des taxis")
st.markdown("")
# Superposition des deux graphs de densité et clusters DBSCAN
# Attribuer une couleur à chaque cluster
colors = ['orange', 'blue', 'green', 'orange', 'purple', 'brown', 'pink', 'cyan', 'yellow', 'black']
cluster_colors = {c: colors[i % len(colors)] for i, c in enumerate(sorted(df_clusters['clusterDB'].unique()))}
# Figure
fig = go.Figure()
# Couche clusters
for c in sorted(df_clusters['clusterDB'].unique()):
dfc = df_clusters[df_clusters['clusterDB'] == c]
fig.add_trace(go.Scattermapbox(
lat=dfc['Lat'],
lon=dfc['Lon'],
mode='markers',
marker=dict(
size=6,
color=cluster_colors[c],
opacity=0.6
),
name=f'Cluster {c}',
hoverinfo='text'
))
# Couche densité boroughs
for i, row in df_density.iterrows():
fig.add_trace(go.Scattermapbox(
lat=[row['Latitude']],
lon=[row['Longitude']],
mode='markers',
marker=dict(
size=row['Count'],
color='red',
opacity=0.7
),
name=row['Borough'],
hoverinfo='text',
text=f"{row['Borough']}<br>Count: {row['Count']}"
))
# Layout
fig.update_layout(
mapbox=dict(
style="carto-positron",
center=dict(lat=40.73, lon=-73.94),
zoom=10
),
height=900,
margin=dict(l=0, r=0, t=0, b=0)
)
# Streamlit
st.plotly_chart(fig, use_container_width=True)
st.write("---")
st.subheader("""
👉 Analyse des clusters :
- On constate une bonne concordance entre les zones d'implantation des taxis et les clusters,
du moins dans le centre de New York.
- Les taxis du Bronx sont justifiés par la proximité de Manhattan.
- L'étude devant isoler cette zone, une concentration de taxis paraît plus éloigné du centre.
Cela est certainement dû à une activité importante dans une zone non concernée par l'étude.
""")
st.write("---")
st.subheader("""
👉 Après cette étude comparative entre KMeans et DBScan, quel est l'algorithme le plus adapté à ce projet ?
Selon moi, KMeans me paraît plus précis dans la définition des clusters et permet d'avoir une approche plus fine dans la définition des clusters.
Le fait de définir soi-même les outliers permet de mieux contrôler les résultats, je l'utiliserais donc pour la suite de l'analyse qui va porter sur les différents jours de la semaine.
""")
with tab2:
st.header("L'analyse des clusters")
st.subheader("Évolution des clusters Uber selon les jours de la semaine et rapprochement avec l'implantation des taxis ")
clusters_by_day = {
"Monday": 7,
"Tuesday": 10,
"Wednesday": 7,
"Thursday": 6,
"Friday": 7,
"Saturday": 7,
"Sunday": 7,
}
days_translation = {
"Monday": "Lundi",
"Tuesday": "Mardi",
"Wednesday": "Mercredi",
"Thursday": "Jeudi",
"Friday": "Vendredi",
"Saturday": "Samedi",
"Sunday": "Dimanche",
}
selected_day_fr = st.selectbox(
"Choisissez le jour souhaité :",
list(days_translation.values())
)
fr_to_en = {v: k for k, v in days_translation.items()}
selected_day_en = fr_to_en[selected_day_fr]
filtered_df = apr_14[apr_14["day_of_week"] == selected_day_en].copy()
filtered_df = compute_kmeans(
filtered_df,
clusters_by_day[selected_day_en]
)
# --- Visualisation clusters + densité taxi ---
fig = go.Figure()
fig.add_trace(go.Scattermapbox(
lat=filtered_df["Lat"],
lon=filtered_df["Lon"],
mode="markers",
marker=dict(
size=6,
color=filtered_df["clusterKmeans"],
colorscale="Viridis",
opacity=0.6
),
name=f"Clusters ({selected_day_fr})"
))
fig.add_trace(go.Scattermapbox(
lat=df_density["Latitude"],
lon=df_density["Longitude"],
mode="markers",
marker=dict(
size=df_density["Count"],
sizemode="area",
sizeref=2.*max(df_density["Count"])/(50.**2),
color="red",
opacity=0.7
),
text=df_density["Borough"] + "<br>Stations: " + df_density["Count"].astype(str),
hoverinfo="text",
name="Densité stations taxi"
))
fig.update_layout(
mapbox=dict(
style="carto-positron",
center=dict(lat=40.73, lon=-73.94),
zoom=10
),
height=900,
title=f"Clusters Uber ({selected_day_fr}) & densité des stations taxi",
margin=dict(l=0, r=0, t=40, b=0)
)
st.plotly_chart(fig, use_container_width=True)
# REPRENDRE ICI !!!!!!
with tab3:
st.subheader("Premier constat sur l'implantation des taxis")
st.markdown("""
La répartition des bases de taxis UBER semble globalement cohérente avec la demande observée :
- Les zones de Manhattan et Brooklyn sont très bien couvertes.
- Les zones du Queens et du Bronx paraissent surdimensionnées, mais cela peut s'expliquer par la proximité de Manhattan.
- Le dimensionnement paraît surévalué dans Staten Island et sous-évalué à Newark, mais le recentrager sur New-York peut expliquer cela.
""")
st.subheader("Second constat sur les pics horaires")
st.markdown("""
Les pics d'activité sont sur les créneaux suivants :
- Une activité certaine les matinées de la semaine entre 6h et 9h
- Les après-midi de 14h à 21h pour les mardi, mercredi, jeudi, vendredi
- Un pic particulièrement intense le mercredi après-midi
- Le vendredi soir jusqu'au samedi 1h du matin
- Le samedi après-midi jusqu'au dimanche 1h du matin également
Ces pics correspondent aux besoins professionnels (matinée en semaine) et de loisirs (fin d'après-midi et soirée, surtout le week-end).
""")
st.markdown("---")
st.subheader("Conclusion et recommandations")
st.markdown(""" L'étude des clusters Uber en avril 2014 à New York révèle une implantation globalement cohérente des taxis par rapport à la demande.
Cependant, quelques ajustements pourraient être envisagés :
- Réévaluer la densité des taxis dans le Queens et le Bronx pour optimiser les ressources, à vérifier avec des données plus complètes.
- Ajuster la couverture à Newark et Staten Island en fonction des besoins réels.
- Surveiller les pics d'activité identifiés pour adapter la flotte en conséquence, notamment les matinées en semaine et les fins d'après-midi/soirées du week-end.
""")
st.markdown(""" Bien évidemment, ces conclusions sont basées sur des données d'avril 2014 et pourraient différer avec des données plus récentes ou complètes.
""")