Ci-dessous, les différences entre deux révisions de la page.
Les deux révisions précédentes Révision précédente Prochaine révision | Révision précédente | ||
site:enseignement:master:bdle:tmes:tme3-prise-main-spark [27/10/2016 14:43] hubert |
site:enseignement:master:bdle:tmes:tme3-prise-main-spark [15/10/2018 09:09] (Version actuelle) amine [Interrogation des données] |
||
---|---|---|---|
Ligne 1: | Ligne 1: | ||
- | {{indexmenu_n>2}} | + | {{indexmenu_n>30}} |
- | ====== Prise en main de Spark ====== | + | ====== [TME II-1] Introduction à Spark (Algèbre RDD) ====== |
+ | |||
+ | **Remarque générale :** Le cours ne peut être //self-contained// --> consulter la documentation en ligne de [[https://spark.apache.org/docs/2.1.1/api/scala/index.html#package|Spark]]. | ||
Pour l'aide sur l'utilisation de Spark voir [[spark|ici]] | Pour l'aide sur l'utilisation de Spark voir [[spark|ici]] | ||
- | |||
===== Exercice 1 ===== | ===== Exercice 1 ===== | ||
- | Commencer par copier et décompresser dans votre espace de travail le fichier | ||
- | |||
- | <code>/Infos/bd/spark/bdle/2015/data/wordcount.txt.bz2</code> | ||
- | |||
- | Le fichier obtenu, wordcount.txt (45Mo), contient des statistiques d’accès aux pages Wikimdia en différentes langue. | ||
- | Une ligne de la forme En.d updates 3 24145 indique dans une page écrite en anglais (symbole ‘En’), intitulée ‘updates’ | ||
- | qui a été cliqué 3 fois et qui fait 24145 octets. | ||
+ | Copier le fichier | ||
+ | <code> /Infos/bd/spark/bdle/2015/data/wordcount.txt.bz2</code> sur votre espace personnel. | ||
Lancer le spark-shell en mode local ([[spark|voir Doc]]) en suivant les instructions fournies | Lancer le spark-shell en mode local ([[spark|voir Doc]]) en suivant les instructions fournies | ||
puis charger le fichier | puis charger le fichier | ||
Ligne 22: | Ligne 18: | ||
<code> val data = sc.textFile("<le_chemin_dans_votre_espace_perso>/wordcount.txt")</code> | <code> val data = sc.textFile("<le_chemin_dans_votre_espace_perso>/wordcount.txt")</code> | ||
- | Ici, la variable data est une RDD, et donc, de type Array tel que vous pouvez le constater sur le shell. | ||
- | L’invocation de la méthode take(n) sur cette variable affiche les n premiers éléments sur une seule ligne. Pour un affichage sur plusieurs lignes (tel un head -n sous linux), utiliser plutôt take(n).foreach(println) qui itère sur les éléments de la variable d’où elle est invoquée et affiche chaque élément sur une ligne séparément. | ||
- | Noter également que la méthode count retourne la cardinalité (nombre d’éléments) de la RDD d’où elle est invoquée. | ||
- | Par soucis de lisibilité de votre code, stocker le résultat de chaque question dans des variables nommées q1, q2, q3 etc. | ||
- | Penser à tester au fur et à mesure le résultat des instructions lorsque celles-ci ne contiennent que des transformations. | ||
- | |||
- | ==== Manipulation de RDDs Simples ==== | ||
- | |||
- | - Extraire le 4e champ de chaque élément de data et le convertir en type double | ||
- | - Construire à partir de q1 une liste contenant les nombres compris strictement entre 1000 et 1300 puis convertir en type entier. | ||
- | - Construire à partir de q2 une liste contenant les multiples de 3 et l’appeler q33. Faire de même pour les multiples de 4 et l’appeler q34. | ||
- | - Construire une liste obtenue en divisant par 10 chaque élément de q33. | ||
- | - Construire à partir de q4 un ensemble d’éléments (liste sans doublons).Construire une liste contenant les éléments de q2 qui sont multiples de 3 et de 4 à la fois. Utiliser impérativement q33 et q34. | ||
- | - Construire une liste contenant les éléments de q2 qui sont multiples de 3 mais pas de 4. Utiliser impérativement q33 et q34. | ||
- | - Construire à partir de q2 une liste contenant les éléments de q2 multiples de 3 ou de 10. Mettre les résultats dans la variable q8. | ||
- | - Convertir les éléments de q8 en type Double puis calculer la somme (q9sum), le minium (q9min) ainsi que le maximum (q9max) des éléments de q8. Calculer la moyenne (q9avg) en utilisant un operateur parmi reduce, fold et aggregate. | ||
- | - Retourner pour chaque élément distinct de q2 le nombre de fois qu’il apparaît dans cette liste. | ||
==== Manipulation de RDDs sous forme de paires clé-valeur ==== | ==== Manipulation de RDDs sous forme de paires clé-valeur ==== | ||
Ligne 47: | Ligne 26: | ||
- Grouper les paires par ‘mot’ et additionner leur nombre nb. | - Grouper les paires par ‘mot’ et additionner leur nombre nb. | ||
- Reprendre les questions 3 et 4 en calculant ‘mot’ différemment : désormais, ‘mot’ doit correspondre au préfixe du premier sous-élément de chaque élément de list, çad, pour en.d, mot doit être en, pour fr.d, mot doit être fr, etc. Comparer les résultats avec ceux obtenus précédemment. | - Reprendre les questions 3 et 4 en calculant ‘mot’ différemment : désormais, ‘mot’ doit correspondre au préfixe du premier sous-élément de chaque élément de list, çad, pour en.d, mot doit être en, pour fr.d, mot doit être fr, etc. Comparer les résultats avec ceux obtenus précédemment. | ||
- | **Remarque** pour partitionner une chaîne de caractères en utilisant le point (.) comme délimiteur à l'aide de la méthode split(), il faut protéger le point avec \, i.e split("\.") | + | **Remarque** pour partitionner une chaîne de caractères en utilisant le point (.) comme délimiteur à l'aide de la méthode split(), il faut protéger le point avec \, i.e split("\\.") |
+ | |||
+ | |||
+ | <showif isloggedin> | ||
+ | **Réponse** | ||
+ | <code scala> | ||
+ | //1. Structurer le contenu de data de sorte à obtenir un tableau de tableaux de chaines de caractères. Ce dernier devra être stocké dans une nouvelle variable nommée list. | ||
+ | |||
+ | val list = data.map(_.split(" ")) | ||
+ | |||
+ | |||
+ | //2. Afficher ensuite les 100 premiers éléments de la 3e colonne de list. | ||
+ | |||
+ | val q12 = list.map(x=>x(2)) | ||
+ | |||
+ | //3. Transformer le contenu de list en une liste de paires (‘mot’, nb) où mot correspond à la première colonne de list et nb sa troisième colonne. | ||
+ | |||
+ | val q13 = list.map(x=>(x(0),x(2).toInt)) | ||
+ | |||
+ | //4. Grouper les paires par ‘mot’ et additionner leur nombre nb. | ||
+ | |||
+ | val q14 = q13.reduceByKey((x,y)=>x+y) | ||
+ | //ou bien q13.reduceByKey(_+_) | ||
+ | |||
+ | //5. Reprendre les questions 3 et 4 en calculant ‘mot’ différemment : désormais, ‘mot’ doit correspondre au préfixe du premier sous-élément de chaque élément de list, çad, pour en.d, mot doit être en, pour fr.d, mot doit être fr, etc. | ||
+ | |||
+ | val q13bis = list.map(x=>(x(0),x(2).toInt)).map(x=>(x._1.split("\\.")(0), x._2)) | ||
+ | |||
+ | |||
+ | |||
+ | |||
+ | |||
+ | </code> | ||
+ | </showif> | ||
===== Exercice 2 ===== | ===== Exercice 2 ===== | ||
Cet exercice s’intéresse à la formulation de jointures simples en Scala. | Cet exercice s’intéresse à la formulation de jointures simples en Scala. | ||
Ligne 64: | Ligne 76: | ||
Pour plus d'informations, voir [[http://files.grouplens.org/datasets/movielens/ml-1m-README.txt|movielens]]. | Pour plus d'informations, voir [[http://files.grouplens.org/datasets/movielens/ml-1m-README.txt|movielens]]. | ||
+ | |||
==== Préparation des données ==== | ==== Préparation des données ==== | ||
Ligne 70: | Ligne 83: | ||
* utils (UserID, Gender, Age, Occupation, Zip-code) | * utils (UserID, Gender, Age, Occupation, Zip-code) | ||
* films (MovieID, Title, Genres) | * films (MovieID, Title, Genres) | ||
+ | |||
+ | |||
Ligne 86: | Ligne 101: | ||
Par exemple, pour le nuplet (1,Toy Story (1995),Animation|Children's|Comedy) de films, il existe trois nuplets dans films_bis : (1,Toy Story (1995),Animation), (1,Toy Story (1995), Children's) et (1,Toy Story (1995), Comedy). | Par exemple, pour le nuplet (1,Toy Story (1995),Animation|Children's|Comedy) de films, il existe trois nuplets dans films_bis : (1,Toy Story (1995),Animation), (1,Toy Story (1995), Children's) et (1,Toy Story (1995), Comedy). | ||
Indice: pour construire films_bis, il est possible d’imbriquer une fonction map à l’intérieur d’une autre (cf. question 2 de l’exercice 3). | Indice: pour construire films_bis, il est possible d’imbriquer une fonction map à l’intérieur d’une autre (cf. question 2 de l’exercice 3). | ||
- | ===== Exercice subsidiaire ===== | ||
- | ==== Illustration du flatMap ==== | ||
- | - Expliquer le résultat de ys dans les deux cas suivants : val ys = Map("a" -> List(1 -> 11,1 -> 111), "b" -> List(2 -> 222,2 -> 22)).flatMap(_._1) et val ys = Map("a" -> List(1 -> 11,1 -> 111), "b" -> List(2 -> 222,2 -> 22)).flatMap(_._2) | + | <showif isloggedin> |
- | - Faire de même pour les instructions suivantes : var pers = Array( (1,"pierre"), (5,"alice"), (4, "paul")) et var v=pers.flatMap(x=>x._2) | + | **Réponse** |
+ | <code scala> | ||
+ | //a. le nombre de notes (ratings) réalisées par chaque utilisateur identifié par son UserID | ||
+ | val q2a = notes.map{case(userId,movieId,rating,ts)=>(userId,1)}.reduceByKey(_+_) | ||
+ | |||
+ | //b. le nombre de notes (ratings) réalisées par chaque localisation donnée par le Zip-code | ||
+ | val q2b = utilis.map{case(userId,gender,age,occup,zipcode)=>(userId,zipcode)} join(notes.map{case(userId,movieId,rating,ts)=>(userId,1)}) map{case (userId,(zipcode, nb))=>(zipcode, 1)} reduceByKey(_+_) | ||
+ | |||
+ | |||
+ | //c. le nombre de notes (ratings) réalisées par chaque genre de film | ||
+ | //jointure en notes et films | ||
+ | val q2c = notes.map(x=>(x(1),1)).join(films.map(x=>(x(0),x(2)))).map{case (movieID, (genre,nb))=>(genre, 1)}.reduceByKey(_+_) | ||
+ | |||
+ | |||
+ | //d. les 10 utilisateurs ayant noté le plus de films. | ||
+ | val q2d = notes.map(x=>(x(0),1)).reduceByKey(_+_).takeOrdered(10) | ||
+ | |||
+ | //e. Les films ayant reçu le moins de notes | ||
+ | //f. Les utilisateurs n’ayant noté aucun film | ||
+ | |||
+ | |||
+ | |||
+ | //genre de films | ||
+ | val films_bis = films.map(x=>(x._1,x._2,x._3.split("\\|"))).flatMap{case(a,b,l)=>l.map(x=>(a,b,x))} | ||
+ | |||
+ | </code> | ||
+ | </showif> | ||
- | ==== Composition de Map ==== | + | === Exercice Subsidiaire : reprendre les questions précédentes en utilisant l'API Dataset === |
- | En considérant la variable pers de la question précédente, expliquer le résultat des instructions suivantes | + | Pour utiliser les Dataset, récupérer le fichier suivant: |
- | * pers.flatMap(x=>(x._2) map(y=>y)) | + | <code bash> |
- | * pers.flatMap(x=>(x._2) map(y=>x._1)) | + | cp /Infos/bd/spark/tme-dataset-etudiant.scala <votre repertoire de travail> |
- | * pers.flatMap(x=>(x._2) map(y=>x._2)) | + | emacs tme-dataset-etudiant.scala & |
- | et expliquer pourquoi les deux instructions suivantes sont erronées | + | </code> |
- | * pers.flatMap(x=>(x._1) map(y=>y)) | + | |
- | * pers.flatMap(x=>(x._2) map(y=>y._1))) | + | |