---
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)
```



