import mermaid from 'https://cdnjs.cloudflare.com/ajax/libs/mermaid/10.2.3/mermaid.esm.min.mjs'; mermaid.initialize({ startOnLoad: true });
Manipulation de fichiers sur un cluster Hadoop HDInsight, premieer job map/reduce avec PIG. Lire aussi From Pig to Spark: An Easy Journey to Spark for Apache Pig Developers.
from jyquickhelper import add_notebook_menu
add_notebook_menu()
Dans le cadre du partenariat avec Microsoft, l'ENSAE dispose de ressources Azure. Azure est une plateforme qui propose de nombreux fonctionnalités et services. Les élèves n'auront pas directement accès au portail Azure mais recevront les identifiants et mots de passe pour les services mis en place à leur intention. Ils seront différents selon les services mais ce sera le même identifiant pour chaque élève.
Cette séance de travaux pratique s'appuiera sur le jeu de données suivant : Localization Data for Person Activity Data Set qu'on récupère comme suit :
import pyensae.datasource
import urllib.error
try:
# ce soir ça ne marche pas...
pyensae.datasource.download_data("ConfLongDemo_JSI.txt",
website="https://archive.ics.uci.edu/ml/machine-learning-databases/00196/")
except urllib.error.URLError:
# donc je récupère une vieille copie
pyensae.datasource.download_data("ConfLongDemo_JSI.zip")
On l'insère dans une base de données SQL.
columns = "sequence tag timestamp dateformat x y z activity".split()
import pandas, sqlite3, os
df = pandas.read_csv("ConfLongDemo_JSI.txt", sep=",", names=columns)
if os.path.exists("ConfLongDemo_JSI.db3"):
os.remove("ConfLongDemo_JSI.db3")
con = sqlite3.connect("ConfLongDemo_JSI.db3")
df.to_sql("person", con)
con.close()
df.head()
sequence | tag | timestamp | dateformat | x | y | z | activity | |
---|---|---|---|---|---|---|---|---|
0 | A01 | 010-000-024-033 | 633790226051280329 | 27.05.2009 14:03:25:127 | 4.062931 | 1.892434 | 0.507425 | walking |
1 | A01 | 020-000-033-111 | 633790226051820913 | 27.05.2009 14:03:25:183 | 4.291954 | 1.781140 | 1.344495 | walking |
2 | A01 | 020-000-032-221 | 633790226052091205 | 27.05.2009 14:03:25:210 | 4.359101 | 1.826456 | 0.968821 | walking |
3 | A01 | 010-000-024-033 | 633790226052361498 | 27.05.2009 14:03:25:237 | 4.087835 | 1.879999 | 0.466983 | walking |
4 | A01 | 010-000-030-096 | 633790226052631792 | 27.05.2009 14:03:25:263 | 4.324462 | 2.072460 | 0.488065 | walking |
On crée un petit exemple pour plus tard qu'on enregistre également dans la base de données.
columns = "sequence tag timestamp dateformat x y z activity".split()
import pandas
df = pandas.read_csv("ConfLongDemo_JSI.txt", sep=",", names=columns)
dfs = df[:1000]
dfs.to_csv("ConfLongDemo_JSI.small.txt", header=False)
import sqlite3
con = sqlite3.connect("ConfLongDemo_JSI.db3")
dfs.to_sql("person_small", con)
con.close()
Il faut se connecter au cluster avec l'url : https://....datascience.fr/....&login=<login>
.
from pyquickhelper.helpgen import NbImage
NbImage("clhdins.png", width=600)
On peut accéder à ce cluster via une adresse web qui s'ouvre sur la fenêtre suivante d'où on peut voir le contenu des fichiers et
NbImage("clhdins2.png", width=600)
Le fenêtre suivante permet d'effectuer des requêtes distribuées avec le langage Hive très proche du SQL.
NbImage("clhdins3.png", width=600)
Avant de commencer à déplacer des fichiers, il faut comprendre qu'il y a trois entités :
from IPython.core.display import Image
NbImage("azure3.png")
Pour exécuter une tâche Map/Reduce, il faut suivre le processus suivant :
Pour manipuler les données sur un cluster, il faut d'abord les uploader sur ce cluster. Pour les récupérer, il faut les downloader. Pour faciliter les choses, on va utiliser des commandes magiques implémentées dans le module pyensae (>= 0.9). La première tâche est d'enregistrer dans l'espace de travail le nom du blob storage, l'alias et le mot de passe dans le workspace du notebook. Ces commandes utilise le module azure qu'il faut installer.
Le code est avant tout destiné à ne pas laisser votre mot de passe en clair dans le notebook. S'il est en clair, tôt ou tard, vous oublierez de l'effacer avant de partager votre notebook. Dans ce cas, il faut changer de mot de passe sans tarder.
from pyquickhelper.ipythonhelper import open_html_form
params={"blob_storage":"", "password":""}
open_html_form(params=params,title="server + credentials", key_save="blob")
blobstorage = blob["blob_storage"]
blobpassword = blob["password"]
On ouvre la connection au blob storage.
cl, bs = %blob_open
cl, bs
(<pyensae.remote.azure_connection.AzureClient at 0xaa63f60>, <azure.storage.blob.blobservice.BlobService at 0xb46c908>)
On regarde la liste des blob storage disponibles.
l = %blob_containers
l
['clusterensaeazure1', 'clusterensaeazure1-1', 'clusterensaeazure2', 'clusterensaeazure2-1', 'hdblobstorage', 'petittest', 'sparkclus', 'sparkclus2', 'testhadoopensae']
On regarde les listes des fichiers dans un répertoire du blob storage :
df = %blob_ls clusterensaeazure1
df.head()
name | last_modified | content_type | content_length | blob_type | |
---|---|---|---|---|---|
0 | DonnotLaugel/mv_0000001.txt | Sun, 01 Feb 2015 11:10:47 GMT | application/octet-stream | 11233 | BlockBlob |
1 | HdiSamples/SensorSampleData/building/building.csv | Sun, 16 Nov 2014 21:24:42 GMT | application/octet-stream | 544 | BlockBlob |
2 | HdiSamples/SensorSampleData/hvac/HVAC.csv | Sun, 16 Nov 2014 21:24:42 GMT | application/octet-stream | 240591 | BlockBlob |
3 | HdiSamples/StorageAnalytics/hive-serde-microso... | Sun, 16 Nov 2014 21:24:42 GMT | application/octet-stream | 9562 | BlockBlob |
4 | HdiSamples/StorageAnalytics/hive-serde-microso... | Sun, 16 Nov 2014 21:24:42 GMT | application/octet-stream | 10290 | BlockBlob |
Et plus particulièrement :
df=%blob_ls clusterensaeazure1/testensae
df
name | last_modified | content_type | content_length | blob_type | |
---|---|---|---|---|---|
0 | testensae/CASP.csv | Thu, 05 Feb 2015 13:40:12 GMT | application/octet-stream | 3528710 | BlockBlob |
On supprime tous les fichiers de répertoire :
for name in df["name"]:
cl.delete_blob(bs, "clusterensaeazure1", name)
On vérifie qu'il ne reste plus rien :
%blob_ls clusterensaeazure1/testensae
name | url |
---|
On uploade un fichier dans le blob storage :
%blob_up ConfLongDemo_JSI.small.txt clusterensaeazure1/testensae/ConfLongDemo_JSI.small.txt
'testensae/ConfLongDemo_JSI.small.txt'
On vérifie qu'il a bien été uploadé :
%blob_ls clusterensaeazure1/testensae
name | last_modified | content_type | content_length | blob_type | |
---|---|---|---|---|---|
0 | testensae/ConfLongDemo_JSI.small.txt | Thu, 29 Oct 2015 00:18:55 GMT | application/octet-stream | 132727 | BlockBlob |
On récupère le fichier uploadé :
if os.path.exists("ConfLongDemo_JSI.small2.txt") : os.remove("ConfLongDemo_JSI.small2.txt")
%blob_down clusterensaeazure1/testensae/ConfLongDemo_JSI.small.txt ConfLongDemo_JSI.small2.txt
'ConfLongDemo_JSI.small2.txt'
On vérifie que le fichier a été téléchargé :
%lsr -f Conf.*[.]txt
directory | last_modified | name | size | |
---|---|---|---|---|
0 | False | 2015-10-29 00:39:34.239955 | .\ConfLongDemo_JSI.small.example.is_back_local... | 129.62 Kb |
1 | False | 2015-10-29 01:16:41.403741 | .\ConfLongDemo_JSI.small.txt | 129.62 Kb |
2 | False | 2015-10-29 01:19:03.751954 | .\ConfLongDemo_JSI.small2.txt | 129.62 Kb |
3 | False | 2015-10-29 00:25:23.606115 | .\ConfLongDemo_JSI.txt | 20.55 Mb |
On peut également faire une copie d'un blob :
%blob_copy clusterensaeazure1/testensae/ConfLongDemo_JSI.small.txt clusterensaeazure1/testensae/ConfLongDemo_JSI.small_copy.txt
True
Et supprimer le blob copié :
%blob_delete clusterensaeazure1/testensae/ConfLongDemo_JSI.small_copy.txt
True
On termine la session.
%blob_close
True
Pour cette partie, l'idée d'exécuter des jobs Map/Reduce sur le fichier ConfLongDemo_JSI.small.example.txt
puis de vérifier qu'on obtient bien les même résultats sur le même fichier en utilisant une requête SQL. Le code pour créer la connexion à la passerelle est recopié ci-dessous mais il n'est pas nécessaire de l'exécuter si la connexion n'a pas été interrompue.
from pyquickhelper.ipythonhelper import open_html_form
params={"blob_storage":"", "password1":"", "hadoop_server":"", "password2":"", "username":"xavierdupre"}
open_html_form(params=params,title="server + hadoop + credentials", key_save="blobhp")
import pyensae
%load_ext pyensae
%load_ext pyenbc
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
(<pyensae.remote.azure_connection.AzureClient at 0xa293b00>, <azure.storage.blob.blobservice.BlobService at 0xa293ba8>)
Le cluster est lié à un autre container hdblobstorage
(différent de celui utiliser pour la première partie. Il faut uploader les données à cet endroit. On supprime d'abord les fichiers existant :
df = %blob_ls hdblobstorage/testensae/
for name in df["name"]:
client.delete_blob(bs, "hdblobstorage", name)
%blob_up ConfLongDemo_JSI.small.txt hdblobstorage/testensae/ConfLongDemo_JSI.small.txt
'testensae/ConfLongDemo_JSI.small.txt'
%blob_ls hdblobstorage/testensae
name | last_modified | content_type | content_length | blob_type | |
---|---|---|---|---|---|
0 | testensae/ConfLongDemo_JSI.small.txt | Thu, 29 Oct 2015 00:23:00 GMT | application/octet-stream | 132727 | BlockBlob |
Un job définit l'ensemble des traitements que vous souhaitez effectuer sur un ou plusieurs fichiers. Le langage PIG permet de décrire ces traitements. Le programme est ensuite interprété puis soumis à Hadoop qui s'occupe de répartir de traitements sur l'ensemble des resources dont il dispose. La commande suivante permet d'obtenir l'ensemble des tâches associés aux jobs :
import sqlite3
con = sqlite3.connect("ConfLongDemo_JSI.db3")
import pandas.io.sql as psql
sql = 'SELECT * FROM person_small WHERE activity == "walking"'
df = psql.read_sql(sql, con)
con.close()
df.tail()
index | sequence | tag | timestamp | dateformat | x | y | z | activity | |
---|---|---|---|---|---|---|---|---|---|
165 | 698 | A01 | 010-000-030-096 | 633790226277429870 | 27.05.2009 14:03:47:743 | 2.965434 | 1.782434 | 0.228563 | walking |
166 | 699 | A01 | 020-000-032-221 | 633790226277970452 | 27.05.2009 14:03:47:797 | 4.322405 | 1.571452 | 1.400499 | walking |
167 | 700 | A01 | 010-000-024-033 | 633790226278240749 | 27.05.2009 14:03:47:823 | 3.133065 | 1.769329 | -0.022590 | walking |
168 | 701 | A01 | 020-000-033-111 | 633790226278781331 | 27.05.2009 14:03:47:877 | 3.121254 | 1.549842 | 1.048139 | walking |
169 | 702 | A01 | 020-000-032-221 | 633790226279051629 | 27.05.2009 14:03:47:907 | 3.281498 | 1.498734 | 0.620412 | walking |
Avec PIG (syntaxe), le programme inclut trois étapes :
ConfLongDemo_JSI.small.example.txt
) (voir LOAD)ConfLongDemo_JSI.small.example.walking.txt
) (voir STORE)La commande magique permet d'écrire un fichier select1.pig
avec l'encoding utf-8
(les caractères accentuées sont possibles).
%%PIG_azure select_azure_2015.pig
myinput = LOAD '$CONTAINER/testensae/ConfLongDemo_JSI.small.txt'
using PigStorage(',')
AS (index:long, sequence, tag, timestamp:long, dateformat, x:double,y:double, z:double, activity) ;
filt = FILTER myinput BY activity == 'walking' ;
STORE filt INTO '$CONTAINER/$PSEUDO/testensae/ConfLongDemo_JSI.small.walking_2015.txt' USING PigStorage() ;
La commande suivante fait deux choses : elle uploade le job sur le blob storage et soumet une requête au cluster afin de l'exécuter :
jid = %hd_pig_submit select_azure_2015.pig
jid
{'id': 'job_1445989166328_0007'}
Après quelques essais infructueux, on s'aperçoit parfois que les numéros de job vont de deux en deux (sauf si le job ne compile pas). Le protocole utilisé pour communiquer avec Azure HD Insight est WebHCat. Lorsque le service reçoit l'instruction, il lance un autre job et façon à soumettre le job au cluster. WebHCat sert d'intermédiaire. On vérifie que le script a bien été uploadé :
df = %blob_ls /$PSEUDO/scripts/pig/
df[df.name.apply(lambda x : "select" in x)]
name | last_modified | content_type | content_length | blob_type | |
---|---|---|---|---|---|
36 | xavierdupre/scripts/pig/select_azure.pig | Thu, 29 Oct 2015 00:30:39 GMT | application/octet-stream | 374 | BlockBlob |
37 | xavierdupre/scripts/pig/select_azure.pig.log | Thu, 29 Oct 2015 00:31:19 GMT | 0 | BlockBlob | |
38 | xavierdupre/scripts/pig/select_azure.pig.log/exit | Thu, 29 Oct 2015 00:31:19 GMT | application/octet-stream | 3 | BlockBlob |
39 | xavierdupre/scripts/pig/select_azure.pig.log/s... | Thu, 29 Oct 2015 00:31:09 GMT | application/octet-stream | 4808 | BlockBlob |
40 | xavierdupre/scripts/pig/select_azure.pig.log/s... | Thu, 29 Oct 2015 00:31:09 GMT | application/octet-stream | 0 | BlockBlob |
41 | xavierdupre/scripts/pig/select_azure_2015.pig | Thu, 29 Oct 2015 00:45:05 GMT | application/octet-stream | 375 | BlockBlob |
42 | xavierdupre/scripts/pig/select_azure_2015.pig.log | Thu, 29 Oct 2015 00:45:28 GMT | 0 | BlockBlob | |
43 | xavierdupre/scripts/pig/select_azure_2015.pig.... | Thu, 29 Oct 2015 00:46:15 GMT | application/octet-stream | 15812 | BlockBlob |
44 | xavierdupre/scripts/pig/select_azure_2015.pig.... | Thu, 29 Oct 2015 00:46:15 GMT | application/octet-stream | 0 | BlockBlob |
On exécute la commande suivante pour connaître le status de l'exécution. On l'execute à nouveau de temps en temps pour vérifier que le job s'est bien déroulé.
st = %hd_job_status jid["id"]
st["id"],st["percentComplete"],st["completed"],st["status"]["jobComplete"],st["status"]["state"]
('job_1445989166328_0007', '100% complete', 'done', True, 'SUCCEEDED')
On vérifie le contenu du cluster :
df=%blob_ls /$PSEUDO/testensae
df[df.last_modified.apply(lambda x : "2015" in x)]
name | last_modified | content_type | content_length | blob_type | |
---|---|---|---|---|---|
10 | xavierdupre/testensae/ConfLongDemo_JSI.small.w... | Thu, 29 Oct 2015 00:28:30 GMT | 0 | BlockBlob | |
11 | xavierdupre/testensae/ConfLongDemo_JSI.small.w... | Thu, 29 Oct 2015 00:28:30 GMT | application/octet-stream | 0 | BlockBlob |
12 | xavierdupre/testensae/ConfLongDemo_JSI.small.w... | Thu, 29 Oct 2015 00:28:30 GMT | application/octet-stream | 22166 | BlockBlob |
13 | xavierdupre/testensae/ConfLongDemo_JSI.small.w... | Thu, 29 Oct 2015 00:46:05 GMT | 0 | BlockBlob | |
14 | xavierdupre/testensae/ConfLongDemo_JSI.small.w... | Thu, 29 Oct 2015 00:46:05 GMT | application/octet-stream | 0 | BlockBlob |
15 | xavierdupre/testensae/ConfLongDemo_JSI.small.w... | Thu, 29 Oct 2015 00:46:04 GMT | application/octet-stream | 22166 | BlockBlob |
set(df[df.last_modified.apply(lambda x : "2015" in x)]["name"])
{'xavierdupre/testensae/ConfLongDemo_JSI.small.walking2015.txt', 'xavierdupre/testensae/ConfLongDemo_JSI.small.walking2015.txt/_SUCCESS', 'xavierdupre/testensae/ConfLongDemo_JSI.small.walking2015.txt/part-m-00000', 'xavierdupre/testensae/ConfLongDemo_JSI.small.walking_2015.txt', 'xavierdupre/testensae/ConfLongDemo_JSI.small.walking_2015.txt/_SUCCESS', 'xavierdupre/testensae/ConfLongDemo_JSI.small.walking_2015.txt/part-m-00000'}
Le job s'est bien exécuté. On downloade le contenu sur l'ordinateur :
import os
if os.path.exists("results.txt"):os.remove("results.txt")
%blob_down /$PSEUDO/testensae/ConfLongDemo_JSI.small.keep_walking.txt/part-m-00000 results.txt
'results.txt'
La plupart du temps, il y aura plus d'un fichier en sortie. C'est pour cela qu'on utilise la commande magique %blob_downmerge
qui va télécharger tous les fichiers et les regrouper localement en un seul :
if os.path.exists("results_allfiles.txt"):os.remove("results_allfiles.txt")
%blob_downmerge /$PSEUDO/testensae/ConfLongDemo_JSI.small.keep_walking.txt results_allfiles.txt
'results_allfiles.txt'
%lsr r.*s[.]txt
directory | last_modified | name | size | |
---|---|---|---|---|
0 | False | 2015-10-29 01:46:45.425028 | .\results.txt | 21.65 Kb |
1 | False | 2015-10-29 01:46:46.705466 | .\results_allfiles.txt | 21.65 Kb |
On regarde les logs de l'exécution. Pour le moment, les logs sont stockés dans le même fichier quelque soit le job.
if os.path.exists("stderr.txt"): os.remove("stderr.txt")
%blob_ls /$SCRIPTPIG/select_azure.pig.log
name | last_modified | content_type | content_length | blob_type | |
---|---|---|---|---|---|
0 | xavierdupre/scripts/pig/select_azure.pig.log | Thu, 29 Oct 2015 00:31:19 GMT | 0 | BlockBlob | |
1 | xavierdupre/scripts/pig/select_azure.pig.log/exit | Thu, 29 Oct 2015 00:31:19 GMT | application/octet-stream | 3 | BlockBlob |
2 | xavierdupre/scripts/pig/select_azure.pig.log/s... | Thu, 29 Oct 2015 00:31:09 GMT | application/octet-stream | 4808 | BlockBlob |
3 | xavierdupre/scripts/pig/select_azure.pig.log/s... | Thu, 29 Oct 2015 00:31:09 GMT | application/octet-stream | 0 | BlockBlob |
df = %blob_ls /$SCRIPTPIG/select_azure_2015.pig.log
list(df["name"])
['xavierdupre/scripts/pig/select_azure_2015.pig.log', 'xavierdupre/scripts/pig/select_azure_2015.pig.log/exit', 'xavierdupre/scripts/pig/select_azure_2015.pig.log/stderr', 'xavierdupre/scripts/pig/select_azure_2015.pig.log/stdout']
L'instruction récupère l'erreur standard pour un job donné :
%hd_tail_stderr jid["id"]
Job DAG: job_1445989166328_0008 2015-10-29 00:46:13,803 [main] INFO org.apache.hadoop.yarn.client.api.impl.TimelineClientImpl - Timeline service address: http://headnodehost:8188/ws/v1/timeline/ 2015-10-29 00:46:13,803 [main] INFO org.apache.hadoop.yarn.client.RMProxy - Connecting to ResourceManager at headnodehost/100.89.100.164:9010 2015-10-29 00:46:13,803 [main] INFO org.apache.hadoop.yarn.client.AHSProxy - Connecting to Application History server at headnodehost/100.89.100.164:10200 2015-10-29 00:46:13,881 [main] INFO org.apache.hadoop.mapred.ClientServiceDelegate - Application state is completed. FinalApplicationStatus=SUCCEEDED. Redirecting to job history server 2015-10-29 00:46:14,068 [main] INFO org.apache.hadoop.yarn.client.api.impl.TimelineClientImpl - Timeline service address: http://headnodehost:8188/ws/v1/timeline/ 2015-10-29 00:46:14,068 [main] INFO org.apache.hadoop.yarn.client.RMProxy - Connecting to ResourceManager at headnodehost/100.89.100.164:9010 2015-10-29 00:46:14,068 [main] INFO org.apache.hadoop.yarn.client.AHSProxy - Connecting to Application History server at headnodehost/100.89.100.164:10200 2015-10-29 00:46:14,146 [main] INFO org.apache.hadoop.mapred.ClientServiceDelegate - Application state is completed. FinalApplicationStatus=SUCCEEDED. Redirecting to job history server 2015-10-29 00:46:14,334 [main] INFO org.apache.hadoop.yarn.client.api.impl.TimelineClientImpl - Timeline service address: http://headnodehost:8188/ws/v1/timeline/ 2015-10-29 00:46:14,334 [main] INFO org.apache.hadoop.yarn.client.RMProxy - Connecting to ResourceManager at headnodehost/100.89.100.164:9010 2015-10-29 00:46:14,334 [main] INFO org.apache.hadoop.yarn.client.AHSProxy - Connecting to Application History server at headnodehost/100.89.100.164:10200 2015-10-29 00:46:14,412 [main] INFO org.apache.hadoop.mapred.ClientServiceDelegate - Application state is completed. FinalApplicationStatus=SUCCEEDED. Redirecting to job history server 2015-10-29 00:46:14,490 [main] INFO org.apache.pig.backend.hadoop.executionengine.mapReduceLayer.MapReduceLauncher - Success! 2015-10-29 00:46:14,522 [main] INFO org.apache.pig.Main - Pig script completed in 44 seconds and 962 milliseconds (44962 ms)
Et pour tuer un job, il sufft d'appeler :
%hd_job_kill <job_id>
Dans cette partie, l'objectif est de transcrire un GROUP BY
en PIG, un JOIN
et de combiner toutes ces opérations en un seul job au cours du second exercice. Ces exemples utilisent de petits fichiers. Utiliser un job Map/Reduce n'a pas beaucoup d'intérêt à moins que la taille de ces fichiers n'atteigne un giga-octets. Les instructions sont à chercher dans cette page : Pig Latin Basics.
import pandas, sqlite3
con = sqlite3.connect("ConfLongDemo_JSI.db3")
df = pandas.read_sql("""SELECT activity, count(*) as nb FROM person GROUP BY activity""", con)
con.close()
df.head()
activity | nb | |
---|---|---|
0 | falling | 2973 |
1 | lying | 54480 |
2 | lying down | 6168 |
3 | on all fours | 5210 |
4 | sitting | 27244 |
Il faut maintenant le faire avec PIG.
con = sqlite3.connect("ConfLongDemo_JSI.db3")
df = pandas.read_sql("""SELECT person.*, A.nb FROM person INNER JOIN (
SELECT activity, count(*) as nb FROM person GROUP BY activity) AS A
ON person.activity == A.activity""", con)
con.close()
df.head()
index | sequence | tag | timestamp | dateformat | x | y | z | activity | nb | |
---|---|---|---|---|---|---|---|---|---|---|
0 | 0 | A01 | 010-000-024-033 | 633790226051280329 | 27.05.2009 14:03:25:127 | 4.062931 | 1.892434 | 0.507425 | walking | 32710 |
1 | 1 | A01 | 020-000-033-111 | 633790226051820913 | 27.05.2009 14:03:25:183 | 4.291954 | 1.781140 | 1.344495 | walking | 32710 |
2 | 2 | A01 | 020-000-032-221 | 633790226052091205 | 27.05.2009 14:03:25:210 | 4.359101 | 1.826456 | 0.968821 | walking | 32710 |
3 | 3 | A01 | 010-000-024-033 | 633790226052361498 | 27.05.2009 14:03:25:237 | 4.087835 | 1.879999 | 0.466983 | walking | 32710 |
4 | 4 | A01 | 010-000-030-096 | 633790226052631792 | 27.05.2009 14:03:25:263 | 4.324462 | 2.072460 | 0.488065 | walking | 32710 |
Idem, maintenant il faut le faire avec PIG.