--- title: "Tutorial Sparklyr" author: "Ricardo Aler" date: "1 Agosto 2019" output: html_notebook: default pdf_document: default word_document: default html_document: df_print: paged --- Información sobre **sparklyr** aquí: [https://spark.rstudio.com/] ```{r} # Con esto se instala: # install.packages("sparklyr") # Además, hay que instalar Spark. Con esto se instala una copia local. Es necesario Java 8 (con Java 9 no funciona) # spark_install(version = "2.1.0") library(dplyr) library(sparklyr) ``` En primer lugar, establecemos una conexión con Spark (o sea, con el cluster manager). **sc** es una variable que permite usar el cluster. ```{r} sc <- spark_connect(master = "local") ``` Vamos a volver a usar el conjunto de datos de Titanic, para ver si podemos predecir si alguien se va a salvar. ```{r} library(titanic) data("titanic_train") head(titanic_train) ``` La siguiente instrucción copia el data.frame local **titanic_train** al cluster. Eso significa que **titanic_train** está contenido en la memoria de nuestro ordenador (del ordenador en el que se ejecuta el **driver**), mientras que **titanic_tbl** es una variable que representa a un data.frame dividido en dos particiones. En teoría, cada partición debería estar almacenada en un ordenador distinto de la red. En nuestro caso, esas particiones están siendo simuladas (dado que no disponemos de un cluster de ordenadores). El nombre "titanicSpark" es el nombre de la tabla o matriz de datos para Spark. **titanic_tbl** es una **referencia** a esa tabla. Es decir, *apunta* a esa tabla, pero no es la tabla en sí. ```{r} titanic_tbl <- sdf_copy_to(sc, titanic_train, repartition = 2, overwrite = TRUE, name="titanicSpark") class(titanic_train) class(titanic_tbl) ``` Podemos ver las tablas que hay en el cluster hasta el momento: ```{r} src_tbls(sc) ``` Vamos a hacer cierto preprocesamiento sencillo. Primero, vamos a seleccionar sólo las columnas que nos interesan. Segundo, vamos a imputar con **if_else** los valores de **Age**, porque tiene muchos NA's. Tercero, vamos a convertir los dolares en euros y vamos a crear una variable booleana que indique si el pasajero es niño o no. Nótese que ahora todas estas operaciones están ocurriendo en todos los ordenadores del cluster, en paralelo. ```{r} titanic_tbl <- select(titanic_tbl, Survived, Pclass, Sex, Age, Fare) %>% mutate(Age = if_else(is.na(Age), mean(Age), Age)) %>% mutate(Fare = Fare * 0.86, menor = Age<=10) ``` Un concepto básico de **Spark** es que es *perezoso* (*lazy*). Es decir, que algunas operaciones (select, filter, ...) sólo se realizan cuando es necesario tener el resultado. En el caso anterior, aunque parece que se computó **select** y **mutate**, realmente no ocurrió nada. **Spark** simplemente anota que hay que realizar esas operaciones. Una manera de forzar a que se realice el cómputo es solicitar un resultado. Por ejemplo, con **head** pedimos visualizar las 10 primeras líneas, con lo cual **Spark** no tiene más remedio que forzar la realización del cálculo. ```{r} head(titanic_tbl) ``` Otra manera de forzar que se compute el resultado es con **collect**. Pero hay que tener cuidado, porque **collect** computa el resultado y se lo trae al ordenador local (**driver**). Es decir, el resultado **ya no es un data.frame distribuido Spark**. Esto es importante, porque sólo se puede hacer **collect** cuando estemos seguros de que el resultado cabe en el ordenador local. Así que sólo usaremos **collect** cuando necesitemos un resultado para traernos al ordenador local. ```{r} titanic_LOCAL <- select(titanic_tbl, Survived, Pclass, Sex, Age, Fare) %>% mutate(Age = if_else(is.na(Age), mean(Age, na.rm=TRUE), Age)) %>% mutate(Fare = Fare * 0.86, menor = Age<=10) %>% collect() class(titanic_LOCAL) ``` La manera de forzar a que se realice el cálculo y que el resultado siga siendo un data.frame Spark (o sea, distribuido), es con **sdf_register** y el nombre de la tabla donde queremos que se guarde el resultado. ```{r} head(titanic_LOCAL) ``` ```{r} titanic_tbl <- select(titanic_tbl, Survived, Pclass, Sex, Age, Fare) %>% mutate(Age = if_else(is.na(Age), mean(Age), Age)) %>% mutate(Fare = Fare * 0.86, menor = Age<=10) %>% sdf_register("titanicSpark") ``` Pero en este caso, no necesitamos el resultado inmediatamente, así que podemos dejar que Spark lo calcule cuando lo necesite, así que volvemos a la secuencia inicial ```{r} titanic_tbl <- select(titanic_tbl, Survived, Pclass, Sex, Age, Fare) %>% mutate(Age = if_else(is.na(Age), mean(Age), Age)) %>% mutate(Fare = Fare * 0.86, menor = Age<=10) ``` Ahora partimos los datos en entrenamiento y test: ```{r} partitions <- titanic_tbl %>% sdf_partition(training = 2/3, test = 1/3, seed = 0) ``` Podemos ver que ambas particiones son data.frames spark. Ambas están distribuidas en el cluster. ```{r} class(partitions$training) class(partitions$test) ``` Ahora, entrenamos un árbol de decisión. Hay que tener en cuenta que los métodos están programados para Spark, con lo que se ejecutarán en paralelo, en la medida de lo posible. Primero, tenemos que saber los nombres de los atributos de entrada y de la variable de respuesta. En este caso la clase va en primer lugar. ```{r} nombres <- colnames(partitions$training) print(nombres) features <- nombres[-1] response <- nombres[1] features response ``` Ahora, entrenamos un árbol de decisión (se ejecutará en paralelo en todas las particiones de los datos): ```{r} model_tree <- ml_decision_tree(partitions$training, features=features, response = response, type="classification") ``` Hacemos predicciones con el conjunto de test. Lo que hace Spark es añadir una columna más con la predicción ("prediction"). Nótese que **predictions_tree_tbl** es un dataframe Spark, o sea, está distribuido. ```{r} predictions_tree_tbl <- ml_predict(model_tree, partitions$test) ``` Podemos intentar verlas, pero vemos que nos sobran columnas ... ```{r} head(predictions_tree_tbl) ``` Vamos a seleccionar las columnas que nos interesan, calculamos después una columna con el error para cada instancia (0 significa acierto, 1 significa error). Por último, calculamos la media de la columna Error, y esa es la tasa de error. ```{r} tasa_error <- predictions_tree_tbl %>% select(Survived, prediction) %>% mutate(Error = abs(Survived-prediction)) %>% summarise(tasa_error = mean(Error)) ``` ```{r} tasa_error ``` Realmente Spark tiene funciones para calcular la tasa de error, con lo que hubieramos podido utilizar esto: ```{r} # Por alguna razón, la clase tiene que tasa_aciertos <- predictions_tree_tbl %>% mutate(Survived = as.double(Survived)) %>% ml_multiclass_classification_evaluator("prediction", "Survived", "accuracy") tasa_error <- 1 - tasa_aciertos tasa_error ``` Por último, nos desconectamos del cluster. ```{r} spark_disconnect(sc) ```