td3a correction session7az August 19, 2015 1 Séance 7 : PIG et JSON et streaming avec les données vélib (correction avec Azure) Plan Récupération des données Connexion au cluster et import des données Exercice 1 : convertir les valeurs numériques Exercice 2 : stations fermées Exercice 3 : stations fermées, journée complète Exercice 4 : astuces Récupération Connexion Exo 1 Exo2 Exo3 Astuces 1.1 Récupération des données In [1]: import pyensae import os, datetime In [2]: if not os.path.exists("velib") : os.mkdir("velib") files=pyensae.download_data("data_velib_paris_2014-11-11_22-23.zip", website="xdtd", whereto="ve files[:2] Out[2]: [ velib\\paris.2014-11-11 22-00-18.331391.txt, velib\\paris.2014-11-11 22-01-17.859194.txt ] 1.2 Connexion au cluster et import des données In [4]: import pyquickhelper, pyensae params={"blob_storage":"", "password1":"", "hadoop_server":"", "password2":"", "username":"alias pyquickhelper.open_html_form(params=params,title="server + hadoop + credentials", key_save="blob Out[4]: <IPython.core.display.HTML at 0xb28f810> In [1]: import pyensae blobstorage = blobhp["blob_storage"] blobpassword = blobhp["password1"] hadoop_server = blobhp["hadoop_server"] hadoop_password = blobhp["password2"] username = blobhp["username"] client, bs = %hd_open client, bs 1
Out[1]: (<pyensae.remote.azure connection.azureclient at 0x7f82430>, <azure.storage.blobservice.blobservice at 0xaad3a50>) On uploade les données (sauf si vous l avez déjà fait une fois) : In [3]: files = [ os.path.join("velib",_) for _ in os.listdir("velib") if ".txt" in _] In [4]: #client.upload(bs, "hdblobstorage", "velib_1h1", files) In [5]: df=%blob_ls hdblobstorage/velib_1h1 df.head() Out[5]: name \ 0 velib 1h1/paris.2014-11-11 22-00-18.331391.txt 1 velib 1h1/paris.2014-11-11 22-01-17.859194.txt 2 velib 1h1/paris.2014-11-11 22-02-17.544368.txt 3 velib 1h1/paris.2014-11-11 22-03-17.229557.txt 4 velib 1h1/paris.2014-11-11 22-04-18.204200.txt last modified content type content length \ 0 Tue, 25 Nov 2014 21:28:03 GMT application/octet-stream 523646 1 Tue, 25 Nov 2014 21:28:10 GMT application/octet-stream 523470 2 Tue, 25 Nov 2014 21:28:18 GMT application/octet-stream 522057 3 Tue, 25 Nov 2014 21:28:25 GMT application/octet-stream 523165 4 Tue, 25 Nov 2014 21:28:32 GMT application/octet-stream 523039 blob type 0 BlockBlob 1 BlockBlob 2 BlockBlob 3 BlockBlob 4 BlockBlob 1.3 Exercice 1 : convertir les valeurs numériques Le programme suivant prend comme argument les colonnes à extraire des fichiers textes qui sont enregistrés au format python. Le streaming sur Azure est sensiblement différent du streaming présenté avec Cloudera. Cette version fonctionne également avec Cloudera. La réciproque n est pas vraie. Les scripts python sont interprétés avec la machine virtuelle java tout comme pig. La solution suivante s inspire de Utilisation de Python avec Hive et Pig dans HDInsight. Voir également Writing Jython UDFs. Cette écriture impose de comprendre la façon dont PIG décrit les données et l utilisation de schema. Le nom du script doit être jython.py pour ce notebook sinon le compilateur PIG ne sait pas dans quel langage interpréter ce script. La version de jython utilisée sur le cluster est 2.5.3 (2.5:c56500f08d34+, Aug 13 2012, 14:54:35) [OpenJDK 64-Bit Server VM (Azul Systems, Inc.)]. La correction inclut encore un bug mais cela devrait être bientôt corrigé. Cela est dû aux différences Python/Jython. In [6]: import pyensae In [87]: %%PYTHON jython.py import datetime, sys, re @outputschema("brow: {(available_bike_stands:int, available_bikes:int, lat:double, lng:double, def extract_columns_from_js(row): 2
# pour un programmeur python, les schéma sont contre plut^ot intuitifs, # { } veut dire une liste, # ( ) un tuple dont chaque colonne est nommé # j écrirai peut-^etre une fonction détermine le schéma en fonction des données # il faut utiliser des expressions régulières pour découper la ligne # car cette expression ne fonctionne pas sur des lignes trop longues # eval ( row ) --> revient à évaluer une ligne de 500 Ko # Hadoop s arr^ete sans réellement proposer de message d erreurs qui mettent sur la bonne vo # dans ces cas-là, il faut relancer le job après avoir commenter des lignes # jusqu à trouver celle qui provoque l arr^et brutal du programme # arr^et qui ne se vérifie pas en local cols = ["available_bike_stands","available_bikes","lat","lng","name","status"] exp = re.compile ("(\\{.*?\\})") rec = exp.findall(row) res = [] for r in rec : station = eval(r) vals = [ str(station[c]) for c in cols ] res.append(tuple(vals)) return res La vérification qui suit ne fonctionne que si la fonction à tester prend comme entrée une chaîne de caractères mais rien n empêche de de créer une telle fonction de façon temporaire juste pour vérifier que le script fonctionne (avec Jython 2.5.3) : In [88]: %%jython jython.py extract_columns_from_js [{ address : RUE DES CHAMPEAUX (PRES DE LA GARE ROUTIERE) - 93170 BAGNOLET, collect_date : d [{ address : RUE DES CHAMPEAUX (PRES DE LA GARE ROUTIERE) - 93170 BAGNOLET, collect_date : d Out[88]: <IPython.core.display.HTML at 0xc1abcb0> On écrit le script PIG qui utilise plus de colonnes : In [4]: %%PIG json_velib_python.pig REGISTER $CONTAINER/$SCRIPTPIG/jython.py using jython as myfuncs; jspy = LOAD $CONTAINER/velib_1h1/*.txt USING PigStorage( \t ) AS (arow:chararray); DESCRIBE jspy ; matrice = FOREACH jspy GENERATE myfuncs.extract_columns_from_js(arow); DESCRIBE matrice ; multiply = FOREACH matrice GENERATE FLATTEN(brow) ; DESCRIBE multiply ; STORE multiply INTO $CONTAINER/$PSEUDO/velibpy_results/firstjob USING PigStorage( \t ) ; On supprime la précédente exécution si besoin puis on vérifie que le répertoire contenant les résultats est vide : In [5]: if client.exists(bs, client.account_name, "$PSEUDO/velibpy_results/firstjob"): r = client.delete_folder (bs, client.account_name, "$PSEUDO/velibpy_results/firstjob") print(r) 3
[ xavierdupre/velibpy results/firstjob, xavierdupre/velibpy results/firstjob/ SUCCESS, xavierdupre/ve In [6]: %hd_pig_submit json_velib_python.pig jython.py -stop_on_failure Out[6]: { id : job 1416874839254 0095 } In [9]: st = %hd_job_status job_1416874839254_0095 st["id"],st["percentcomplete"],st["status"]["jobcomplete"] Out[9]: ( job 1416874839254 0095, 100% complete, True) On récupère l erreur : In [10]: %tail_stderr job_1416874839254_0095 10 Out[10]: <IPython.core.display.HTML at 0xaa9fff0> In [11]: %blob_ls /$PSEUDO/velibpy_results Out[11]: name \ 0 xavierdupre/velibpy results 1 xavierdupre/velibpy results/firstjob 2 xavierdupre/velibpy results/firstjob/ SUCCESS 3 xavierdupre/velibpy results/firstjob/part-m-00000 last modified content type content length \ 0 Thu, 27 Nov 2014 08:56:03 GMT application/octet-stream 0 1 Thu, 27 Nov 2014 22:39:39 GMT 0 2 Thu, 27 Nov 2014 22:39:39 GMT application/octet-stream 0 3 Thu, 27 Nov 2014 22:39:39 GMT application/octet-stream 4693699 blob type 0 BlockBlob 1 BlockBlob 2 BlockBlob 3 BlockBlob On récupère les informations qu on affiche sous forme de dataframe : In [12]: if os.path.exists("velib_exo1.txt") : os.remove("velib_exo1.txt") %blob_downmerge /$PSEUDO/velibpy_results/firstjob velib_exo1.txt Out[12]: velib exo1.txt In [13]: %head velib_exo1.txt Out[13]: <IPython.core.display.HTML at 0xaa9fb70> In [14]: import pandas df = pandas.read_csv("velib_hd.txt", sep="\t",names=["available_bike_stands","available_bikes", df.head() Out[14]: available bike stands available bikes lat lng \ 0 47 3 48.864528 2.416171 1 5 28 48.872420 2.348395 2 42 1 48.882149 2.319860 3 5 31 48.868217 2.330494 4 20 5 48.893269 2.412716 4
name status 0 31705 - CHAMPEAUX (BAGNOLET) OPEN 1 10042 - POISSONNIÈRE - ENGHIEN OPEN 2 08020 - METRO ROME OPEN 3 01022 - RUE DE LA PAIX OPEN 4 35014 - DE GAULLE (PANTIN) OPEN 1.4 Exercice 2 : stations fermées Les stations fermées ne le sont pas tout le temps. On veut calculer le ratio minutes/stations fermées / total des minutes/stations. Le script python permettant de lire les données ne change pas, il suffit juste de déclarer les sorties numériques comme telles. Quelques détails sur les tables : jspy : contient les données brutes dans une liste de fichiers matrice : d après le job qui précède, la table contient une ligne par stations et par minute, chaque ligne décrit le status de la station grstation : table matrice groupée par status fermees : pour chaque groupe, on aggrégé le nombre de minutes multipliés par le nombre de vélos gr*,dist* : distribution du nombre de stations (Y) en fonction du nombre de vélos ou places disponibles En cas d exécution précédentes : In [122]: for sub in [ "multiply.txt"]: if client.exists(bs, client.account_name, "$PSEUDO/velibpy_results/" + sub): r = client.delete_folder (bs, client.account_name, "$PSEUDO/velibpy_results/" + sub) print(r) [ xavierdupre/velibpy results/fermees.txt ] [ xavierdupre/velibpy results/distribution bikes.txt ] [ xavierdupre/velibpy results/distribution stands.txt ] On va exécuter le job en deux fois. Le premier job met tout à plat. Le second calcule les aggrégations. La plupart du temps, le travaille de recherche concerne la seconde partie. Mais si le job n est pas scindé, la première partie est toujours exécutée à chaque itération. Dans ce cas-ci, on scinde le job en deux. La première partie forme une table à partir des données initiales. La seconde les agrègre. In [155]: %%PIG json_velib_python2.pig REGISTER $CONTAINER/$SCRIPTPIG/jython.py using jython as myfuncs; jspy = LOAD $CONTAINER/velib_1h1/*.txt USING PigStorage( \t ) AS (arow:chararray); DESCRIBE jspy ; matrice = FOREACH jspy GENERATE myfuncs.extract_columns_from_js(arow); DESCRIBE matrice ; multiply = FOREACH matrice GENERATE FLATTEN(brow) ; DESCRIBE multiply ; STORE multiply INTO $CONTAINER/$PSEUDO/velibpy_results/multiply.txt USING PigStorage( \t ) ; In [156]: %hd_pig_submit json_velib_python2.pig jython.py -stop_on_failure 5
Out[156]: { id : job 1416874839254 0125 } In [163]: st = %hd_job_status job_1416874839254_0125 st["id"],st["percentcomplete"],st["status"]["jobcomplete"] Out[163]: ( job 1416874839254 0125, 100% complete, True) In [165]: %blob_ls /$PSEUDO/velibpy_results/multiply.txt Out[165]: name \ 0 xavierdupre/velibpy results/multiply.txt 1 xavierdupre/velibpy results/multiply.txt/ SUCCESS 2 xavierdupre/velibpy results/multiply.txt/part-... last modified content type content length \ 0 Fri, 28 Nov 2014 01:46:41 GMT 0 1 Fri, 28 Nov 2014 01:46:41 GMT application/octet-stream 0 2 Fri, 28 Nov 2014 01:46:41 GMT application/octet-stream 4693699 blob type 0 BlockBlob 1 BlockBlob 2 BlockBlob In [167]: for sub in ["fermees.txt", "distribution_bikes.txt", "distribution_stands.txt"]: if client.exists(bs, client.account_name, "$PSEUDO/velibpy_results/" + sub): r = client.delete_folder (bs, client.account_name, "$PSEUDO/velibpy_results/" + sub) print(r) [ xavierdupre/velibpy results/fermees.txt, xavierdupre/velibpy results/fermees.txt/ temporary, xavier [ xavierdupre/velibpy results/distribution bikes.txt, xavierdupre/velibpy results/distribution bikes.tx [ xavierdupre/velibpy results/distribution stands.txt, xavierdupre/velibpy results/distribution stands. In [168]: %%PIG json_velib_python3.pig multiply = LOAD $CONTAINER/$PSEUDO/velibpy_results/multiply.txt USING PigStorage( \t ) AS (available_bike_stands:int, available_bikes:int, lat:double, lng:double, name:chararra DESCRIBE multiply ; grstation = GROUP multiply BY status ; DESCRIBE grstation ; fermees = FOREACH grstation GENERATE group,sum(multiply.available_bikes) AS available_bikes,sum(multiply.available_bike_stands) AS available_bike_stands ; DESCRIBE fermees ; gr_av = GROUP multiply BY available_bikes ; DESCRIBE gr_av; dist_av = FOREACH gr_av GENERATE group, COUNT(multiply) ; DESCRIBE dist_av; gr_pl = GROUP multiply BY available_bike_stands ; 6
DESCRIBE gr_pl; dist_pl = FOREACH gr_pl GENERATE group, COUNT(multiply) ; DESCRIBE dist_pl; STORE fermees INTO $CONTAINER/$PSEUDO/velibpy_results/fermees.txt USING PigStorage( \t ) ; STORE dist_av INTO $CONTAINER/$PSEUDO/velibpy_results/distribution_bikes.txt USING PigStorag STORE dist_pl INTO $CONTAINER/$PSEUDO/velibpy_results/distribution_stands.txt USING PigStora In [169]: %hd_pig_submit json_velib_python3.pig -stop_on_failure Out[169]: { id : job 1416874839254 0127 } In [173]: st = %hd_job_status job_1416874839254_0127 st["id"],st["percentcomplete"],st["status"]["jobcomplete"] Out[173]: ( job 1416874839254 0127, 100% complete, True) In [177]: %tail_stderr job_1416874839254_0127 10 Out[177]: <IPython.core.display.HTML at 0xc285b50> In [178]: %blob_ls /$PSEUDO/velibpy_results Out[178]: name \ 0 xavierdupre/velibpy results 1 xavierdupre/velibpy results/distribution bikes... 2 xavierdupre/velibpy results/distribution bikes... 3 xavierdupre/velibpy results/distribution bikes... 4 xavierdupre/velibpy results/distribution stand... 5 xavierdupre/velibpy results/distribution stand... 6 xavierdupre/velibpy results/distribution stand... 7 xavierdupre/velibpy results/fermees.txt 8 xavierdupre/velibpy results/fermees.txt/ SUCCESS 9 xavierdupre/velibpy results/fermees.txt/part-r... 10 xavierdupre/velibpy results/firstjob 11 xavierdupre/velibpy results/firstjob/ SUCCESS 12 xavierdupre/velibpy results/firstjob/part-m-00000 13 xavierdupre/velibpy results/multiply.txt 14 xavierdupre/velibpy results/multiply.txt/ SUCCESS 15 xavierdupre/velibpy results/multiply.txt/part-... last modified content type content length \ 0 Thu, 27 Nov 2014 08:56:03 GMT application/octet-stream 0 1 Fri, 28 Nov 2014 01:50:54 GMT 0 2 Fri, 28 Nov 2014 01:50:54 GMT application/octet-stream 0 3 Fri, 28 Nov 2014 01:50:54 GMT application/octet-stream 497 4 Fri, 28 Nov 2014 01:50:55 GMT 0 5 Fri, 28 Nov 2014 01:50:55 GMT application/octet-stream 0 6 Fri, 28 Nov 2014 01:50:55 GMT application/octet-stream 477 7 Fri, 28 Nov 2014 01:50:54 GMT 0 8 Fri, 28 Nov 2014 01:50:54 GMT application/octet-stream 0 9 Fri, 28 Nov 2014 01:50:53 GMT application/octet-stream 37 10 Thu, 27 Nov 2014 22:39:39 GMT 0 11 Thu, 27 Nov 2014 22:39:39 GMT application/octet-stream 0 12 Thu, 27 Nov 2014 22:39:39 GMT application/octet-stream 4693699 7
13 Fri, 28 Nov 2014 01:46:41 GMT 0 14 Fri, 28 Nov 2014 01:46:41 GMT application/octet-stream 0 15 Fri, 28 Nov 2014 01:46:41 GMT application/octet-stream 4693699 blob type 0 BlockBlob 1 BlockBlob 2 BlockBlob 3 BlockBlob 4 BlockBlob 5 BlockBlob 6 BlockBlob 7 BlockBlob 8 BlockBlob 9 BlockBlob 10 BlockBlob 11 BlockBlob 12 BlockBlob 13 BlockBlob 14 BlockBlob 15 BlockBlob In [179]: if os.path.exists("distribution_bikes.txt") : os.remove("distribution_bikes.txt") %blob_downmerge /$PSEUDO/velibpy_results/distribution_bikes.txt distribution_bikes.txt Out[179]: distribution bikes.txt In [180]: import pandas df = pandas.read_csv("distribution_bikes.txt", sep="\t", names=["nb_velos", "nb_stations_minut df.head() Out[180]: nb velos nb stations minutes 0 0 8586 1 1 6698 2 2 4904 3 3 3863 4 4 2887 In [182]: import matplotlib.pyplot as plt plt.style.use( ggplot ) df.plot(x="nb_velos",y="nb_stations_minutes",kind="bar",figsize=(16,4)) Out[182]: <matplotlib.axes. subplots.axessubplot at 0xae39f90> 8
In [183]: if os.path.exists("distribution_stands.txt") : os.remove("distribution_stands.txt") %blob_downmerge /$PSEUDO/velibpy_results/distribution_stands.txt distribution_stands.txt Out[183]: distribution stands.txt In [184]: df = pandas.read_csv("distribution_stands.txt", sep="\t", names=["nb_places", "nb_stations_min df.plot(x="nb_places",y="nb_stations_minutes",kind="bar",figsize=(16,4)) Out[184]: <matplotlib.axes. subplots.axessubplot at 0xab50d50> In [185]: if os.path.exists("fermees.txt") : os.remove("fermees.txt") %blob_downmerge /$PSEUDO/velibpy_results/fermees.txt fermees.txt Out[185]: fermees.txt In [186]: df = pandas.read_csv("fermees.txt", sep="\t", names=["status", "nb_velos_stations_minutes", "n df=df.set_index("status") df = df.t df["%close"] = df.closed / (df.closed + df.open) df Out[186]: status OPEN CLOSED %close nb velos stations minutes 1066055 3111 0.002910 nb places stations minutes 1276138 122 0.000096 Ce dernier tableau n est vrai que dans la mesure où les nombres reportées sont fiables lorsque les stations sont fermées. 1.5 Exercice 3 : stations fermées, journée complète Appliquer cela à une journée complète revient à lancer le même job sur des données plus grandes. On verra bientôt commencer éviter de recopier le job une seconde fois (introduisant une source potentielle d erreur). In [40]: 9
1.6 Exercice 4 : astuces Les erreurs de PIG ne sont pas très explicite surtout si elles se produisent dans le script python. Un moyen simple de débugger est d attraper les exceptions produites par python et de les récupérer sous PIG (le reste du job est enlevé). On peut tout-à-fait imaginer ajouter la version de python installée sur le cluster ainsi que la liste de modules In [70]: %%PYTHON jython.py import sys @outputschema("brow:chararray") def information(row): return (";".join([str(sys.version), str(sys.executable)])).replace("\n"," ") On vérifie que le script fonctionne avec jython : In [71]: %%jython jython.py information n importe quoi Out[71]: <IPython.core.display.HTML at 0xb066630> In [78]: %%PIG info.pig REGISTER $CONTAINER/$SCRIPTPIG/jython.py using jython as myfuncs; jspy = LOAD $CONTAINER/velib_1h1/*.txt USING PigStorage( \t ) AS (arow:chararray); one = LIMIT jspy 1 ; infos = FOREACH one GENERATE myfuncs.information(arow); STORE infos INTO $CONTAINER/$PSEUDO/results/infos USING PigStorage( \t ) ; In [79]: if client.exists(bs, client.account_name, "$PSEUDO/results/infos"): r = client.delete_folder (bs, client.account_name, "$PSEUDO/results/infos") print(r) In [80]: %hd_pig_submit info.pig jython.py -stop_on_failure Out[80]: { id : job 1416874839254 0107 } In [83]: st = %hd_job_status job_1416874839254_0107 st["id"],st["percentcomplete"],st["status"]["jobcomplete"] Out[83]: ( job 1416874839254 0107, 100% complete, True) In [84]: %tail_stderr job_1416874839254_0107 10 Out[84]: <IPython.core.display.HTML at 0xaa9f770> In [85]: if os.path.exists("infos.txt") : os.remove("infos.txt") %blob_downmerge /$PSEUDO/results/infos infos.txt Out[85]: infos.txt In [86]: %head infos.txt Out[86]: <IPython.core.display.HTML at 0xc1ab5f0> In [ ]: 10