{"paragraphs": [{"config": {"enabled": true, "editorHide": true}, "text": "%md\n\n", "results": {"code": "SUCCESS", "msg": [{"type": "HTML", "data": "
"}]}}, {"config": {"enabled": true, "editorHide": true}, "text": "%md\n# Travaux pratiques - Manipulation de donn\u00e9es num\u00e9riques. Ex\u00e9cution d\u2019applications\n\nR\u00e9f\u00e9rences externes utiles :\n\n> - [Documentation de Spark](https://spark.apache.org/docs/2.4.7/) \n- [Documentation de l\u2019API Spark en Scala](https://spark.apache.org/docs/latest/api/scala/org/apache/spark/index.html) \n- [Documentation du langage Scala](http://docs.scala-lang.org) \n\n\n\n**L\u2019objectif** de cette s\u00e9ance de TP est d\u2019introduire les structures de donn\u00e9es locales et distribu\u00e9es employ\u00e9es pour des donn\u00e9es num\u00e9riques, ainsi que la m\u00e9thode permettant d\u2019\u00e9crire des programmes pour Spark en langage Scala et de les lancer en ex\u00e9cution.\n\nLes exemples ci-dessous reprennent, en partie, ceux des documentations en ligne.\n\nNous vous recommandons d\u2019effectuer cette s\u00e9ance de travaux pratiques avec Apache Zeppelin afin de vous familiariser avec cet environnement de programmation pour Spark. Pour ce faire, t\u00e9l\u00e9chargez le cahier Zeppelin en cliquant sur le bandeau en haut du cours (*clic droit > Enregistrer la cible du lien sous*) puis importez ce fichier (*Import note*) dans Zeppelin.\n\nServeur JupyterHub\n\nSi vous \u00eates inscrit au Cnam, vous avez acc\u00e8s au serveur JupyterHub en passant par votre [espace num\u00e9rique de formation](https://lecnam.net). Une fois sur la page du cours, cliquez sur le lien \u00ab\u00a0Acc\u00e8s \u00e0 JupyterHub\u00a0\u00bb. Vous serez automatiquement authentifi\u00e9 sur votre espace Jupyter personnel h\u00e9berg\u00e9 sur les serveurs du Cnam. Votre r\u00e9pertoire personnel est pr\u00e9serv\u00e9 tant que vous \u00eates inscrit \u00e0 au moins un cours.\n\nUne fois sur cette interface, vous pouvez y importer la version Jupyter du TP que vous aurez pr\u00e9alblement t\u00e9l\u00e9charg\u00e9e en cliquant sur le bandeau en haut de cette page. Lorsque cela vous est demand\u00e9, choisissez le noyau \u00ab\u00a0Apache Toree - Scala\u00a0\u00bb.\n\nServeur Jupyter local\n\nIl est possible d\u2019installer Spark et Jupyter sur votre machine personnelle en suivant [ces instructions d\u201dinstallation](http://cedric.cnam.fr/vertigo/Cours/RCP216/installationSpark.html). Vous pouvez nous signaler les \u00e9ventuelles difficult\u00e9s rencontr\u00e9es (apr\u00e8s avoir quand m\u00eame cherch\u00e9 vous-m\u00eame des solutions dans des forums sur le web). Une fois install\u00e9, vous pouvez d\u00e9marrer Jupyter puis y acc\u00e9der \u00e0 l\u2019URL [http://localhost:8888/](http://localhost:8888/).\n\nServeur Zeppelin local\n\nIl est possible d\u2019installer Spark et Zeppelin sur votre machine personnelle en suivant [ces instructions d\u201dinstallation](http://cedric.cnam.fr/vertigo/Cours/RCP216/installationSpark.html). Vous pouvez nous signaler les \u00e9ventuelles difficult\u00e9s rencontr\u00e9es (apr\u00e8s avoir quand m\u00eame cherch\u00e9 vous-m\u00eame des solutions dans des forums sur le web). L\u2019utilisation d\u2019un syst\u00e8me d\u2019exploitation Linux ou MacOS est recommand\u00e9e (mais il est possible d\u2019effectuer l\u2019installation sous Windows). Une fois install\u00e9, Zeppelin est accessible \u00e0 l\u2019URL [http://localhost:8080/](http://localhost:8080/).\n\nServeur Zeppelin distant\n\nSi vous \u00eates inscrit au Cnam, vous avez acc\u00e8s au serveur Zeppelin RCP216 du d\u00e9partement informatique: [https://zeppelin.kali-service.cnam.fr](https://zeppelin.kali-service.cnam.fr). Vous pouvez vous y connecter \u00e0 l\u2019aide de vos identifiants Siscol. Vos identifiants sont form\u00e9s de votre nom de famille (max. 6 caract\u00e8res) et de la premi\u00e8re lettre de votre pr\u00e9nom, s\u00e9par\u00e9s par un underscore _. Par exemple, pour Jeanne Dupont, l\u2019identifiant Siscol associ\u00e9 serait : `dupont_j`.\n\nLorsque vous importez le cahier dans Zeppelin, indiquez comme nom `votreidenfiant/lenomduTp`, par exemple `dupont_j/tpDonneesNumeriques`. Cela permettra de regrouper vos TP dans un dossier \u00e0 votre nom.", "results": {"code": "SUCCESS", "msg": [{"type": "HTML", "data": "R\u00e9f\u00e9rences externes utiles :
\n\n\n \n\n\n
L\u2019objectif de cette s\u00e9ance de TP est d\u2019introduire les structures de donn\u00e9es locales et distribu\u00e9es employ\u00e9es pour des donn\u00e9es num\u00e9riques, ainsi que la m\u00e9thode permettant d\u2019\u00e9crire des programmes pour Spark en langage Scala et de les lancer en ex\u00e9cution.
\n\nLes exemples ci-dessous reprennent, en partie, ceux des documentations en ligne.
\n\nNous vous recommandons d\u2019effectuer cette s\u00e9ance de travaux pratiques avec Apache Zeppelin afin de vous familiariser avec cet environnement de programmation pour Spark. Pour ce faire, t\u00e9l\u00e9chargez le cahier Zeppelin en cliquant sur le bandeau en haut du cours (clic droit > Enregistrer la cible du lien sous) puis importez ce fichier (Import note) dans Zeppelin.
\n\nServeur JupyterHub
\n\nSi vous \u00eates inscrit au Cnam, vous avez acc\u00e8s au serveur JupyterHub en passant par votre espace num\u00e9rique de formation. Une fois sur la page du cours, cliquez sur le lien \u00ab\u00a0Acc\u00e8s \u00e0 JupyterHub\u00a0\u00bb. Vous serez automatiquement authentifi\u00e9 sur votre espace Jupyter personnel h\u00e9berg\u00e9 sur les serveurs du Cnam. Votre r\u00e9pertoire personnel est pr\u00e9serv\u00e9 tant que vous \u00eates inscrit \u00e0 au moins un cours.
\n\nUne fois sur cette interface, vous pouvez y importer la version Jupyter du TP que vous aurez pr\u00e9alblement t\u00e9l\u00e9charg\u00e9e en cliquant sur le bandeau en haut de cette page. Lorsque cela vous est demand\u00e9, choisissez le noyau \u00ab\u00a0Apache Toree - Scala\u00a0\u00bb.
\n\nServeur Jupyter local
\n\nIl est possible d\u2019installer Spark et Jupyter sur votre machine personnelle en suivant ces instructions d\u201dinstallation. Vous pouvez nous signaler les \u00e9ventuelles difficult\u00e9s rencontr\u00e9es (apr\u00e8s avoir quand m\u00eame cherch\u00e9 vous-m\u00eame des solutions dans des forums sur le web). Une fois install\u00e9, vous pouvez d\u00e9marrer Jupyter puis y acc\u00e9der \u00e0 l\u2019URL http://localhost:8888/.
\n\nServeur Zeppelin local
\n\nIl est possible d\u2019installer Spark et Zeppelin sur votre machine personnelle en suivant ces instructions d\u201dinstallation. Vous pouvez nous signaler les \u00e9ventuelles difficult\u00e9s rencontr\u00e9es (apr\u00e8s avoir quand m\u00eame cherch\u00e9 vous-m\u00eame des solutions dans des forums sur le web). L\u2019utilisation d\u2019un syst\u00e8me d\u2019exploitation Linux ou MacOS est recommand\u00e9e (mais il est possible d\u2019effectuer l\u2019installation sous Windows). Une fois install\u00e9, Zeppelin est accessible \u00e0 l\u2019URL http://localhost:8080/.
\n\nServeur Zeppelin distant
\n\nSi vous \u00eates inscrit au Cnam, vous avez acc\u00e8s au serveur Zeppelin RCP216 du d\u00e9partement informatique: https://zeppelin.kali-service.cnam.fr. Vous pouvez vous y connecter \u00e0 l\u2019aide de vos identifiants Siscol. Vos identifiants sont form\u00e9s de votre nom de famille (max. 6 caract\u00e8res) et de la premi\u00e8re lettre de votre pr\u00e9nom, s\u00e9par\u00e9s par un underscore _. Par exemple, pour Jeanne Dupont, l\u2019identifiant Siscol associ\u00e9 serait : dupont_j
.
Lorsque vous importez le cahier dans Zeppelin, indiquez comme nom votreidenfiant/lenomduTp
, par exemple dupont_j/tpDonneesNumeriques
. Cela permettra de regrouper vos TP dans un dossier \u00e0 votre nom.
MLlib est la biblioth\u00e8que de Spark pour l\u2019apprentissage automatique (machine learning). MLlib comporte deux interfaces de programmation (API) diff\u00e9rentes, une plus ancienne (org.apache.spark.mllib
) \u00e9crite pour travailler avec des RDD et une plus r\u00e9cente (org.apache.spark.ml
) qui travaille avec les Dataset / DataFrame. Nous nous servirons principalement de l\u2019API la plus r\u00e9cente, c\u2019est-\u00e0-dire org.apache.spark.ml
.
Les deux API proposent des vecteurs et des matrices locales (stock\u00e9s sur un seul n\u0153ud de calcul). L\u2019API bas\u00e9e sur les RDD propose des matrices distribu\u00e9es sous forme de RDD. Dans l\u2019API bas\u00e9e sur les Dataset / DataFrame, ce sont des DataFrame ou des (groupes de) colonnes de DataFrame qui constituent les matrices distribu\u00e9es. M\u00e9langer les deux API doit \u00eatre fait avec pr\u00e9caution.
\n\nIl faut noter que les op\u00e9rations d\u2019alg\u00e8bre lin\u00e9aire ne sont pas propos\u00e9es explicitement mais sont obtenues en se basant sur les librairies Breeze
et jblas
. Vous pouvez trouver ici un bref exemple d\u2019utilisation explicite.
Un vecteur local a des indices de type Int
(dont les valeurs commencent \u00e0 0) et des valeurs de type Double
. MLlib propose des repr\u00e9sentations denses ou creuses (sparse) pour les vecteurs. Un vecteur dense emploie un seul tableau (array) de valeurs. Pour les vecteurs creux, deux tableaux sont mis en correspondance, un d\u2019indices et l\u2019autre de valeurs. Nous examinons ici quelques d\u00e9finitions de vecteurs denses et de vecteurs creux. Pour les essayer, entrez
```scala\n// Importe la biblioth\u00e8que de calcul vectoriel de Spark\nimport org.apache.spark.ml.linalg.Vectors
\n\n// Cr\u00e9er un vecteur dense (1.5, 0.0, 3.5)\nval vectDense = Vectors.dense(1.5, 0.0, 3.5)
\n\n// Cr\u00e9er un vecteur dense de dimension 3 avec toutes les composantes 0.0\nval vectZeros = Vectors.zeros(3)
\n\n// Cr\u00e9er un vecteur creux (1.5, 0.0, 3.5) en indiquant les indices et valeurs\n// correspondant aux composantes non nulles\nval vectCreux1 = Vectors.sparse(3, Array(0, 2), Array(1.5, 3.5))\n// Cr\u00e9er un vecteur creux (1.5, 0.0, 3.5) en indiquant la s\u00e9quence des\n// paires (indice, valeur) pour les composantes non nulles\nval vectCreux2 = Vectors.sparse(3, Seq((0, 1.5), (2, 3.5)))\n```
\nLes vecteurs denses stockent l\u2019int\u00e9gralit\u00e9 des valeurs dans un tableau. Si ce vecteur contient beaucoup de z\u00e9ros, ce format est assez inefficace (il faut stocker n valeurs en m\u00e9moire pour un vecteur de longueur n).
\n\nLes vecteurs creux ne stockent en m\u00e9moire que les valeurs non-nulles et leurs indices. Par exemple le vecteur creux (0., 1.5, 3.0, 0., 0., 0., 0., 1.0, 0., 0., 0., 0.)
est repr\u00e9sent\u00e9 par deux listes :
\n\n\n\n
\n- la liste des valeurs non-nulles
\n(1.5, 3.0, 1.0)
,- la liste des positions des valeurs non-nulles
\n(1, 2, 7)
(les indices o\u00f9 se trouvent les valeurs).
Cela permet de ne stocker que 2k valeurs en m\u00e9moire (les \u00e9l\u00e9ments non-nuls et leurs indices) pour un vecteur de longueur n. Si un vecteur ou une matrice a plus de 50% d\u2019\u00e9l\u00e9ments nuls, il est int\u00e9ressant de les stocker au format creux.
\n\nOn peut acc\u00e9der aux \u00e9l\u00e9ments du vecteur en utilisant les parenth\u00e8ses :
\nscala\n// Acc\u00e8s \u00e0 une valeur\nprintln(vectCreux1(1))\nprintln(vectCreux1(2))\n
La m\u00e9thode .equals()
permet de tester l\u2019\u00e9galit\u00e9 entre vecteurs, y compris repr\u00e9sent\u00e9s sous des formats diff\u00e9rents.
scala\n// Test d'\u00e9galit\u00e9 entre (contenus des) vecteurs\nprintln(vectCreux1.equals(vectCreux2))\nprintln(vectCreux1.equals(vectDense))\nprintln(vectCreux1.equals(vectZeros))\n
L\u2019attribut .size
permet d\u2019acc\u00e9der \u00e0 la taille du vecteur.
scala\n// Taille des vecteurs\nprintln(vectDense.size) // la m\u00e9thode size n'a pas d'arguments\nprintln(vectZeros.size)\nprintln(vectCreux1.size)\nprintln(vectCreux2.size)\n
L\u2019attribut .copy
permet de copier un vecteur.
scala\n// Copie profonde d'un vecteur\nval vectCreux3 = vectCreux1.copy // la m\u00e9thode copy n'a pas d'arguments\n
Il est n\u00e9cessaire d\u2019importer explicitement org.apache.spark.ml.linalg.Vectors
car Scala importe par d\u00e9faut scala.collection.immutable.Vector
.
Il est possible de calculer directement une norme pour un vecteur local, ainsi que le carr\u00e9 de la distance L2 entre deux vecteurs :
\nscala\n// Norme L1\nVectors.norm(vectDense, 1)\nVectors.norm(vectCreux1, 1)\n
scala\n// Norme L2\nVectors.norm(vectDense, 2)\nVectors.norm(vectCreux1, 2)\n
scala\n// Carr\u00e9 de la distance L2 entre deux vecteurs\nVectors.sqdist(vectDense, vectZeros)\nVectors.sqdist(vectDense, vectCreux1)\n
Calculer avec Spark la distance euclidienne (L2) entre les points (0, 1, 2)
et (-2, -1, 0)
.
```scala
\n\n```
\nUne matrice locale a des indices de type Int
dont la valeur commence \u00e0 0 pour les lignes et pour les colonnes, et des valeurs de type Double
. MLlib propose des matrices locales denses, classe DenseMatrix
, et creuses, classe SparseMatrix
. Les valeurs des \u00e9l\u00e9ments d\u2019une matrice dense sont gard\u00e9es dans un tableau unidimensionnel en concat\u00e9nant les colonnes successives de la matrice. Un exemple de cr\u00e9ation d\u2019une matrice dense \u00e0 3 lignes et 2 colonnes :
```scala\nimport org.apache.spark.ml.linalg.{Matrix, DenseMatrix, SparseMatrix}
\n\n// Cr\u00e9er une matrice dense ((1.2, 2.3), (3.4, 4.5), (5.6, 6.7))\nval matDense = new DenseMatrix(3, 2, Array(1.2, 3.4, 5.6, 2.3, 4.5, 6.7))\n```
\nLes matrices creuses sont repr\u00e9sent\u00e9es dans un format Compressed Sparse Column (CSC).
\nCalculez dans un vecteur vectProduit
le produit entre matDense
et un vecteur dense de composantes (1.0, 2.0). Pour cela, cherchez la m\u00e9thode \u00e0 utiliser dans la documentation en ligne de l\u2019API en Scala (entrez DenseMatrix
dans l\u2019espace de recherche, ensuite choisissez la bonne classe et examinez ses m\u00e9thodes)).
```scala
\n\n```
\nIl est souvent utile de construire explicitement un Dataset / DataFrame de petite taille pour tester une m\u00e9thode de traitement. L\u2019exemple ci-dessous montre cette possibilit\u00e9 :
\n```scala\nimport org.apache.spark.ml.linalg.Vectors // si ce n'est d\u00e9j\u00e0 fait
\n\nval donnees = Array(Vectors.sparse(3, Seq((0, 1.0))), Vectors.dense(0.0, 1.0, 0.0), Vectors.dense(0.0, 0.0, 1.0))\nval donneesdf = spark.createDataFrame(donnees.map(Tuple1.apply)).toDF(\"vecteurs\")\ndonneesdf.show()\n```
\nLa cr\u00e9ation de Dataset / DataFrame \u00e0 partir de fichiers contenant des donn\u00e9es num\u00e9riques peut \u00eatre faite suivant plusieurs approches. Pour pouvoir examiner ces diff\u00e9rentes approches nous chercherons d\u2019abord quelques fichiers de donn\u00e9es.
\n```bash\nimport sys.process._\n// Cr\u00e9\u00e9 un r\u00e9pertoire \"data\"\n\"mkdir -p data\" !
\n\n// T\u00e9l\u00e9charge les fichiers de test\n\"wget -nc http://cedric.cnam.fr/vertigo/Cours/RCP216/docs/geysers.txt -P data/\" !
\n\n\"wget -nc http://cedric.cnam.fr/vertigo/Cours/RCP216/docs/geyser.txt -P data/\" !
\n\n\"wget -nc http://cedric.cnam.fr/vertigo/Cours/RCP216/docs/geyser.csv -P data/\" !
\n\n\"wget -nc https://raw.githubusercontent.com/apache/spark/master/data/mllib/samplelibsvmdata.txt -P data/\" !\n```
\nLa premi\u00e8re approche consiste \u00e0 cr\u00e9er directement le Dataset / DataFrame \u00e0 partir du fichier. Examinons d\u2019abord cette approche pour un fichier de format CSV (comma separated values), geyser.csv
, qui pr\u00e9sente deux valeurs double
par ligne, s\u00e9par\u00e9es par une virgule :
```scala\n// Cr\u00e9ation directe de DataFrame \u00e0 partir d'un fichier de donn\u00e9es en format CSV\nval geysercsvdf = spark.read.format(\"csv\").load(\"data/geyser.csv\")
\n\n// examinons le DataFrame geysercsvdf obtenu\ngeysercsvdf.printSchema()\ngeysercsvdf.show(5)\n```
\nCe sont les m\u00e9thodes format
et load
de DataFrameReader , accessibles \u00e0 partir de SparkSession.read
, qui sont employ\u00e9es ici.
Pourquoi les colonnes sont-elles de type string
? Avec les options de la fonction read
, indiquez lors de la lecture via Spark que l\u2019en-t\u00eate du fichier CSV doit \u00eatre pris en compte pour nommer les colonnes du DataFrame.
Indice : se r\u00e9f\u00e9rer aux exemples de la documentation.
\nToujours \u00e0 l\u2019aide des options, cr\u00e9ez un DataFrame \u00e0 partir du fichier \u00ab data/geyser.txt \u00bb dans lequel les s\u00e9parateurs sont des espaces (\u00ab \u00bb).
\n```scala
\n\n```
\nLa m\u00e9thode .describe()
permet de produire des statistiques sur les colonnes num\u00e9riques du DataFrame :
scala\ngeysercsvdf.describe().show()\n
Un deuxi\u00e8me exemple suivant cette m\u00eame approche concerne la lecture de vecteurs \u00e9tiquett\u00e9s (labeled point) dans un format popularis\u00e9 par LIBSVM (le m\u00eame format est utilis\u00e9 par LIBLINEAR). C\u2019est un format texte dans lequel chaque ligne repr\u00e9sente un vecteur \u00e9tiquett\u00e9 creux dans le format suivant label index1:value1 index2:value2 ...
, o\u00f9 les indices (>= 1) sont en ordre croissant. Apr\u00e8s chargement, les indices sont convertis pour \u00eatre >= 0. SparkSession.read.format(\"libsvm\").load()
permet la lecture de fichiers suivant ce format et la cr\u00e9ation d\u2019un DataFrame contenant ces donn\u00e9es :
```scala\n// Cr\u00e9ation directe de DataFrame \u00e0 partir d'un fichier de donn\u00e9es en format LIBSVM\nval libsvmdf = spark.read.format(\"libsvm\").load(\"data/samplelibsvmdata.txt\")
\n\n// examinons le DataFrame libsvmdf obtenu\nlibsvmdf.printSchema()\nlibsvmdf.show(5)
\n\n// Calcul de statistiques pour la colonne \u00ab label \u00bb\nlibsvmdf.describe(\"label\").show()\n```
\nPourquoi il n\u2019est pas possible d\u2019obtenir avec .describe()
des statistiques pour la colonne \u00ab features \u00bb ?
Il faut noter que parmi les autres formats directement accessibles avec SparkSession.read.format()
on peut trouver json
, text
ou parquet
.
Une seconde approche pour la cr\u00e9ation de Dataset / DataFrame \u00e0 partir de fichiers contenant des donn\u00e9es num\u00e9riques consiste \u00e0 passer par l\u2019interm\u00e9diaire d\u2019un RDD. D\u2019abord, un RDD est obtenu \u00e0 partir du fichier texte geysers.txt
repr\u00e9sentant, dans un format adapt\u00e9 \u00e0 la fonction de lecture .loadVectors()
, 272 observations (1 observation par ligne) pour 2 variables (chaque variable est repr\u00e9sent\u00e9e par une colonne) :
```scala\nimport org.apache.spark.mllib.util.MLUtils\nimport org.apache.spark.rdd.RDD
\n\nval lignes = MLUtils.loadVectors(sc, \"data/geysers.txt\")\nlignes.count()\n```
\nCe RDD est ensuite utilis\u00e9 pour obtenir un DataFrame :
\n```scala\nimport org.apache.spark.sql.Row\nimport org.apache.spark.sql.types._
\n\n// D\u00e9finition du sch\u00e9ma du DataFrame comme une cha\u00eene de caract\u00e8res\nval chaineSchema = \"duration interval\"
\n\n// Construction du sch\u00e9ma \u00e0 partir de la cha\u00eene de caract\u00e8res\nval champs = chaineSchema.split(\" \").map(nomChamp => StructField(nomChamp, DoubleType, true))\nval schema = StructType(champs)
\n\n// Construction d'un RDD[Row] \u00e0 partir du RDD[Vector]\nval rowRDD = lignes.map(ligne => Row(ligne(0),ligne(1)))
\n\n// Construction du DataFrame \u00e0 partir du RDD[Row]\nval lignesdf = spark.createDataFrame(rowRDD, schema)
\n\n// Examen du DataFrame obtenu\nlignesdf.printSchema()\nlignesdf.show(5)
\n\n// Calcul de statistiques pour les deux colonnes du DataFrame\nlignesdf.describe(\"duration\").show()\nlignesdf.describe(\"interval\").show()\n```
\nPour obtenir un DataFrame (le m\u00eame) via un RDD \u00e0 partir du fichier geyser.txt
dont le format n\u2019est pas adapt\u00e9 \u00e0 la fonction de lecture loadVectors
nous proc\u00e9dons de la mani\u00e8re suivante :
```scala\nimport org.apache.spark.ml.linalg.Vectors
\n\nval donnees = sc.textFile(\"data/geyser.txt\")\nval lignes2 = donnees.map(s => Vectors.dense(s.split(' ').map(_.toDouble)))\nlignes2.count()\n```
\nExpliquer les op\u00e9rations r\u00e9alis\u00e9es pour obtenir le RDD lignes2
.
La d\u00e9finition pr\u00e9alable du sch\u00e9ma du DataFrame permet de sp\u00e9cifier les formats des diff\u00e9rentes colonnes gr\u00e2ce \u00e0 la m\u00e9thode DataFrameReader.schema()
. Par exemple, avec le schema
d\u00e9fini plus haut :
```scala\nval geysercsvdfd = spark.read.format(\"csv\").schema(schema).load(\"data/geyser.csv\")\ngeysercsvdfd.printSchema()\ngeysercsvdfd.show(5)
\n\n// Calcul de statistiques pour toutes les colonnes num\u00e9riques\ngeysercsvdfd.describe().show()\n```
\nLe m\u00eame r\u00e9sultat peut \u00eatre obtenu dans cet exemple \u00e0 l\u2019aide de DataFrameReader.option(\"inferSchema\",true)
, au prix d\u2019une passe suppl\u00e9mentaire sur les donn\u00e9es lues (ce qui est co\u00fbteux lorsque le volume de donn\u00e9es est \u00e9lev\u00e9).
scala\nval testdfd = spark.read.format(\"csv\").option(\"inferSchema\",true).load(\"data/geyser.csv\")\ntestdfd.printSchema()\ntestdfd.show(5)\n
Jusqu\u2019ici nous avons utilis\u00e9 Spark en transmettant une par une des instructions \u00e9crites en Scala \u00e0 travers spark-shell
ou Jupyter. Ce mode de fonctionnement est adapt\u00e9 \u00e0 l\u2019ex\u00e9cution d\u2019op\u00e9rations simple et lors de l\u2019\u00e9tape de mise au point de programmes. Il est toutefois n\u00e9cessaire de pouvoir \u00e9crire des programmes pour Spark (en Scala, dans le cadre de nos TP), de les corriger ou modifier \u00e9ventuellement et de les lancer en ex\u00e9cution.
\n\nNote
\n \nCette partie se fait hors de Jupyter/Zeppelin. Les commandes ci-dessous sont \u00e0 ex\u00e9cuter dans une invite de commandes Windows ou un terminal Linux/MacOS, par exemple sur les machines de TP.
\n
Nous ferons d\u2019abord une copie locale d\u2019un fichier HTML qui servira de source de donn\u00e9es et pr\u00e9parerons la structure des r\u00e9pertoires n\u00e9cessaires. Pour cela, dans une nouvelle fen\u00eatre terminal entrez les commandes suivantes :
\nbash\nmkdir -p tpnum && cd tpnum/\nmkdir -p data/ && cd data/\nwget http://cedric.cnam.fr/vertigo/Cours/RCP216/tpSparkScala.html\ncd ..\nmkdir -p src/main/scala\n
Sous Windows, cr\u00e9ez le r\u00e9pertoire data
et t\u00e9l\u00e9chargez-y la page HTML sp\u00e9cifi\u00e9e (par exemple en la sauvegardant depuis un navigateur), puis cr\u00e9ez l\u2019arborescence src/main/scala
dans le r\u00e9pertoire tpnum
.
Lancez un \u00e9diteur de texte (KWrite
par exemple, dans le menu Applications_pedagogiques
, sous-menu Editeurs
) et copiez dans cet \u00e9diteur le programme Scala suivant :
```scala\n/* SimpleProg.scala */\nimport org.apache.spark.sql.SparkSession
\n\nobject SimpleProg {\n def main(args: Array[String]) {\n val myFile = \"data/tpSparkScala.html\"\n val spark = SparkSession.builder.appName(\"Application Simple\").getOrCreate()\n val myData = spark.read.textFile(myFile).cache()\n val nbclass = myData.filter(line => line.contains(\"class\")).count()\n val nbjavascript = myData.filter(line => line.contains(\"javascript\")).count()\n println(\"Lignes avec javascript : %s, lignes avec class : %s\".format(nbjavascript, nbclass))\n spark.stop()\n }\n}\n```
\nEnregistrez-le dans un fichier SimpleProg.scala
dans le r\u00e9pertoire ~/tpnum/src/main/scala
. Ici ~/
indique votre r\u00e9pertoire utilisateur (\\$HOME
, que vous pouvez conna\u00eetre avec la commande Linux echo \\$HOME
).
Ce programme tr\u00e8s simple compte le nombre de lignes du fichier tpSparkScala.html
qui contiennent \u00ab javascript \u00bb et le nombre de lignes qui contiennent \u00ab class \u00bb. Lors de l\u2019utilisation de spark-shell
un objet SparkSession
\u00e9tait cr\u00e9\u00e9 automatiquement. Lorsqu\u2019on \u00e9crit une application autonome il est n\u00e9cessaire d\u2019initialiser un objet SparkSession
explicitement. Pour cela, il est n\u00e9cessaire d\u2019appeler SparkSession.builder
.
Quel est l\u2019int\u00e9r\u00eat de l\u2019utilisation de .cache()
pour le Dataset myData
?
Le programme d\u00e9pendant de l\u2019API Spark, nous pr\u00e9parerons aussi un programme de configuration pour SBT (Simple build Tool), simpleprog.sbt
, qui indique les d\u00e9pendances n\u00e9cessaires. Pour cela, lancez l\u2019\u00e9diteur de texte et copiez dans l\u2019\u00e9diteur les lignes suivantes (attention, les lignes vides de s\u00e9paration sont n\u00e9cessaires !) :
```bash\n/* Le nom de votre choix pour votre programme */\nname := \"Simple Programme\"
\n\n/* Le num\u00e9ro de version de votre choix pour votre programme */\nversion := \"1.0\"
\n\n/* La version de Scala install\u00e9e sur la machine */\nscalaVersion := \"2.11.8\"
\n\n/* Les d\u00e9pendences \u00e0 utiliser (ici, Apache Spark dans la version install\u00e9e sur la machine) */\nlibraryDependencies += \"org.apache.spark\" %% \"spark-sql\" % \"2.2.0\"\n```
\nIl faut indiquer les versions de Scala et de Spark qui sont install\u00e9es sur votre syst\u00e8me (ici 2.11.8 et respectivement 2.2.0). Enregistrez ce texte dans un fichier simpleprog.sbt
dans le r\u00e9pertoire ~/tpnum
. Le nom \u00ab\u00a0Simple Programme\u00a0\u00bb et la version de votre programme, ainsi que la version de Scala utlis\u00e9e sont employ\u00e9s par SBT pour g\u00e9n\u00e9rer le nom du fichier .jar
qui sera, dans le cas pr\u00e9sent, simple-programme_2.11-1.0.jar
.
Si SBT n\u2019est pas pr\u00e9sent sur votre syst\u00e8me, il est n\u00e9cessaire de l\u2019installer (voir http://www.scala-sbt.org). SBT est d\u00e9j\u00e0 install\u00e9 dans les salles de travaux pratiques au Cnam.
\n\nIl est maintenant possible de cr\u00e9er avec SBT le .jar
contenant le programme. Dans une fen\u00eatre terminal (shell), entrez les commandes suivantes :
bash\ncd ~/tpnum\nsbt package\n
Si la commande sbt
n\u2019est pas trouv\u00e9e, sur les ordinateurs de la salle de TP essayez alors plut\u00f4t /opt/sbt/bin/sbt package
.
Durant les env. 5 minutes n\u00e9cessaires \u00e0 SBT pour la cr\u00e9ation du premier .jar
(ce sera plus rapide pour les suivants) vous pouvez poursuivre la lecture du support de TP.
Si jamais vous obtenez une erreur du type ResolveException: unresolved dependency
, c\u2019est que vous n\u2019avez pas sp\u00e9cifi\u00e9 la bonne version pour Scala ou pour Apache Spark dans le fichier de configuration simpleprog.sbt
.
D\u00e8s que SBT a termin\u00e9 la cr\u00e9ation de simple-programme_2.11-1.0.jar
(affichage de [info] Packaging {..}/{..}/target/scala-2.10/simple-programme_2.11-1.0.jar
) il est possible de lancer en ex\u00e9cution l\u2019application avec spark-submit
. Le script spark-submit
permet d\u2019utiliser tous les cluster managers que Spark sait g\u00e9rer, de fa\u00e7on transparente \u00e0 l\u2019application. Dans une fen\u00eatre terminal (bash
), dans le r\u00e9pertoire ~/tpnum
, entrez :
bash\n$ spark-submit --class \"SimpleProg\" --master local target/scala-2.11/simple-programme_2.11-1.0.jar\n
Si tout se passe bien, vous devriez obtenir la r\u00e9ponse suivante :
\nbash\n...\nLignes avec javascript : 1, lignes avec class : 248\n...\n
Les options transmises \u00e0 spark-submit
sont :
\n\n\n\n
\n- \n
--class
: le point d\u2019entr\u00e9e pour l\u2019application, iciSimpleProg
;- \n
--master
: l\u2019URL dumaster
sur le cluster, icilocal
qui demande l\u2019ex\u00e9cution de l\u2019application en local sur un seul c\u0153ur (local[2]
: sur 2 c\u0153urs,local[*]
: sur tous les c\u0153urs disponibles).
D\u2019autres options sont souvent utilis\u00e9es :
\navec
\n\n\n\n\n\n
\n- \n
--deploy-mode
: s\u2019il faut faire tourner le driver sur un des n\u0153uds du cluster (cluster
) ou localement sur un client externe (client
) (par d\u00e9fautclient
) ;- \n
--conf
: propri\u00e9t\u00e9s de configuration de Spark en format \u00ab\u00a0cl\u00e9=valeur\u00a0\u00bb ;- \n
application-jar
: chemin vers un.jar
\u00ab\u00a0empaquet\u00e9\u00a0\u00bb (bundled) contenant l\u2019application et toutes les d\u00e9pendances et obtenu avec SBT ; l\u2019URL doit \u00eatre visible \u00e0 partir de chaque n\u0153ud du cluster ;- \n
application-arguments
: si n\u00e9cessaire, arguments \u00e0 passer \u00e0 la m\u00e9thodemain
de l\u2019application.
La totalit\u00e9 des options, dont certaines sp\u00e9cifiques au cluster manager employ\u00e9, peuvent \u00eatre obtenues avec spark-submit --help
. Aussi, des informations d\u00e9taill\u00e9es sur le processus de lancement et sur l\u2019ex\u00e9cution peuvent \u00eatre obtenues en ajoutant l\u2019option --verbose
lors du lancement de spark-submit
.
Des informations de configuration sont \u00e9galement pr\u00e9sentes dans SPARK_HOME/conf/spark-defaults.conf
(si le fichier spark-defaults.conf
n\u2019existe pas, il faut le cr\u00e9er \u00e0 partir de spark-defaults.conf.template
), mais sont moins prioritaires que les options indiqu\u00e9es en ligne de commande lors du lancement de spark-submit
.