Main

Quelques exercices sur des données réelles (Vélib)

Nous faisons l'hypothèse que les étudiants ont les connaissances de base en numpy et matplotlib. Dans le cas contraire, se référer au premier tutoriel lien


Récupération des données

Solution 1: télécharger un fichier

Le fichier suivant regroupe les informations sur les stations Vélib enrichies avec les altitudes (API Google) dataVelib.pkl

fname = "nomDeFichier.pkl"
f= open(fname,'rb')
data = pkl.load(f, encoding='latin1') # option encoding pour le passage py2 => py3
f.close()

Solution 2: les API rest [Pour jouer A LA MAISON]

Nous proposons de tracer quelques courbes de probabilité sur des données velib récupérée directement sur le web.

  1. première étape: récupération des données, création d'un compte sur: https://developer.jcdecaux.com/#/signup
  2. récupération d'une clé (XXX)
  3. Début du code:
import requests
import pickle as pkl
import time

url = "https://api.jcdecaux.com/vls/v1/stations?contract=Paris&apiKey=XXX"
dataStation = requests.get(url)
data = dataStation.json()
  1. enrichissement avec l'altitude à partir de l'API Google:
urlGoogleAPI = "https://maps.googleapis.com/maps/api/elevation/json?locations="

for s in data:
    position = "%f,%f"%(s['position']['lat'],s['position']['lng'])
    alt = requests.get(urlGoogleAPI+position)
    assert(alt.json()['status'] == "OK") # verification de la réussite
    s[u'alt'] = alt.json()['results'][0]['elevation'] # enrichissement
    time.sleep(0.1) # pour ne pas se faire bannir par Google

f= open('coordVelib.pkl','wb')
pkl.dump(data,f) # penser à sauver les données pour éviter de refaire les opérations
f.close()

Réflexe pickle: chaque fois que vous avez effectué des opérations coûteuses, penser à sauver le résultat! Le module pickle offre la possibilité de le faire en très peu de ligne!

f= open('coordVelib.pkl','wb')
pkl.dump(data,f) # penser à sauver les données pour éviter de refaire les opérations
f.close()

Structure des données

Les données sont dans une liste de dictionnaire (ie chaque station est un dictionnaire python). Les champs du dictionnaire sont:

  • 'address': u"57 RUE DU CHATEAU D'EAU - 75010 PARIS",
  • 'alt': 35.79555130004883,
  • 'available_bike_stands': 2,
  • 'available_bikes': 17,
  • 'banking': True,
  • 'bike_stands': 19,
  • 'bonus': False,
  • 'contract_name': u'Paris',
  • 'last_update': 1410442143000,
  • 'name': u"10007 - CHATEAU D'EAU",
  • 'number': 10007,
  • 'position': {u'lat': 48.87242997325711, u'lng': 2.355489390173873},
  • 'status': u'OPEN'}

Pour faire défiler les stations dans la structure data:

for station in data:
    ...

Pour accéder à un champ d'une station:

for station in data:
    nbVeloDispo = station['available_bikes']
    ...

Mise en forme et élimination du bruit

Les données ne sont pas toutes correctes (comme la plupart des données réelles). Nous souhaitons aussi les mettre en forme dans un tableau.

  • Faire défiler les stations
  • Extraire l'arrondissement [Champ 'number' / 1000 en division entière] et vérifier qu'il est compris entre 1 et 20
  • Eliminer les autres stations
  • Définir une matrice du bon nombre de lignes avec les colonnes contenant: 3 coordonnées géographiques, Arrondissement, Places totales, Places dispo.

Distributions de probabilités

Dans notre population, les individus sont des stations, qui ont différents caractères. Nous allons étudier les variables aléatoires suivantes:

  • Arrondissement (Ar)
  • Altitude (Al)

* Places totales (Ps) * Places dispo (Pd)

  • Station pleine (Sp) [variable binaire: valeur 1 si la station est pleine]
  • Au moins 2 vélos disponibles (variable binaire) (Vd) [comme il y a souvent des vélos en panne, on considère qu'il faut 2 vélos dans une stations pour être sûr de pouvoir en prendre un.]

Construire les matrices suivantes:

  • {$P[Ar]$}
  • {$P[Al]$} nous utiliserons une discrétisation de l'altitude en 100 intervalles de mêmes tailles
nItervalles = 30
res = plt.hist(altitudes, nIntervalles)
print res[0] # effectif dans les intervalles
print res[1] # definition des intervalles (ATTENTION: 31 valeurs)
  • {$P[Sp | Al]$}
  • {$P[Vd | Al]$}
  • {$P[Vd | Ar]$}

Tracer un histogramme

Tracer l'histogramme correspondant à la distribution {$P[Al]$}.
Attention: pour faire un tracé correct, il faut que l'aire de l'histogramme somme à 1. Il est donc nécessaire de réfléchir à la hauteur et largeur des différentes barres.

# stocker les altitudes + définir la largeur des intervalles:
alt = res[1]
intervalle = alt[1]-alt[0]
# définir la fréquence de répartition de la population dans les intervalles
pAlt = res[0]/res[0].sum()
# puis diviser la fréquence par la base de l'intervalle pour que ça somme à 1
pAlt /= intervalle
[...] # créer une figure
plt.bar((alt[1:]+alt[:-1])/2,pAlt, alt[1]-alt[0])
# NB: dans bar, on donne: abscisse, ordonnées, largeur des barres

Calcul et tracé d'une probabilité conditionnelle

On s'intéresse à {$E[P[Vd | Al]]$}, c'est à dire, la probabilité d'avoir un vélo disponible en fonction de l'altitude. Voici les étapes à respecter

  1. calculer pour toutes les stations
    1. Vd = 1 si au moins 2 vélos dispos, Vd = 0 sinon
    2. Al = [0,...,29] catégorie d'altitude (cf aide ci-dessous pour ce calcul)
    3. Construire la table à 2 lignes et 30 colonnes de {$P[Vd | Al]$} en cherchant pour chaque altitude:
      1. Chercher les stations concernées [=réduction de l'univers pour les probas conditionnelles]
      2. Calculer la fréquence de Vd=1
      3. Calculer la fréquence de Vd=0
      4. Tracer {$P[Vd=1|Al]$} [={$P[Vd=1|Al]*1 + P[Vd=0|Al]*0$}estimation de l'espérance conditionnelle]
# retrouver les catégories
# première colonne de station = altitude
# intervalle = largeur d'un intervalle
catAlt = np.floor((stations[:,1]-stations[:,1].min()) / intervalle)

# le début du code suivant peut-être
pDispoSAlt = np.zeros(np.unique(catAlt).shape[0])
for a in np.unique(catAlt): # fonction pratique pour isoler les valeurs uniques
    [...]

Tracer la population des stations

Sanity check: affichage des arrondissements pour vérifier que tout est OK:

x1 = stations[:,3] # recuperation des coordonnées
x2 = stations[:,2]
# définition de tous les styles (pour distinguer les arrondissements)
style = [(s,c) for s in "o^+*" for c in "byrmck" ]

# tracé de la figure
plt.figure()
for i in range(1,21):
    ind, = np.where(stations[:,0]==i)
    # scatter c'est plus joli pour ce type d'affichage
    plt.scatter(x1[ind],x2[ind],marker=style[i-1][0],c=style[i-1][1],linewidths=0)

plt.axis('equal') # astuce pour que les axes aient les mêmes espacements
plt.legend(range(1,21), fontsize=10)
plt.savefig("carteArrondissements.pdf")

Tout est OK, on peut passer à l'exercice!

Disponibilité:

Afin de réviser les usages de np.where Projeter les stations sur la carte en mettant en:

  • rouge les stations pleines,
  • jaune les vides,
  • verte les autres.

Moyenne, Médiane...

Sur différentes cartes, afficher les stations suivantes:

  • stations dont l'altitude est inférieure à la moyenne (en conservant le code couleur précédent)
  • stations dont l'altitude est supérieure à la médiane (en conservant le code couleur précédent)

Tests de corrélation

  • Calculer les corrélations entre les variables Altitude et Vélo disponible
  • Calculer les corrélations entre les variables Arrondissement et Vélo disponible

Quel facteur est le plus lié au fait qu'(au moins) un vélo soit disponible dans une station?


Pour s'amuser avec d'autres données: