Skip to content

Commit

Permalink
Ejemplo netflix
Browse files Browse the repository at this point in the history
  • Loading branch information
felipegonzalez committed Nov 15, 2023
1 parent 0313a9f commit 49b26e3
Showing 1 changed file with 206 additions and 0 deletions.
206 changes: 206 additions & 0 deletions ejemplos/recomendacion-netflix.qmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
---
title: "Recomendaciones (Netflix)"
format: html
---

```{r, message=FALSE, warning=FALSE}
library(tidyverse)
library(sparklyr)
# configuración para spark
spark_install(version = "3.1.2")
config <- spark_config()
config$`spark.env.SPARK_LOCAL_IP.local` <- "0.0.0.0"
config$`sparklyr.shell.driver-memory` <- "8G"
config$spark.executor.memory <- "2G"
```

```{r}
# conectar con "cluster" local
sc <- spark_connect(master = "local", config = config)
```


```{r}
spark_set_checkpoint_dir(sc, './checkpoint')
```


```{r leertabla}
dat_tbl <- spark_read_csv(sc, name="netflix", "../datos/netflix/dat_muestra_nflix.csv")
dat_tbl <- dat_tbl |> select(-fecha)
peliculas_num <- dat_tbl |> group_by(peli_id) |>
count() |> collect()
#dat_tbl <- dat_tbl |> left_join(peliculas_num) |>
# filter(n > 2000) |> select(-n)
```



```{r}
usuario_val <- dat_tbl |> select(usuario_id) |>
sdf_distinct() |>
sdf_sample(fraction = 0.1) |>
compute("usuario_val")
pelicula_val <- dat_tbl |> select(peli_id) |>
sdf_distinct() |>
sdf_sample(fraction = 0.1) |>
compute("pelicula_val")
valida_tbl <- dat_tbl |>
inner_join(usuario_val) |>
inner_join(pelicula_val) |>
compute("valida_tbl")
```

```{r}
entrena_tbl <- dat_tbl |> anti_join(valida_tbl) |>
compute("entrena_tbl")
entrena_tbl |> tally()
valida_tbl |> tally()
```

Vamos a hacer primero una descomposición en pocos factores,
con regularización relativamente alta:

```{r als-spark}
modelo <- ml_als(entrena_tbl,
rating_col = 'calif',
user_col = 'usuario_id',
item_col = 'peli_id',
rank = 20, reg_param = 0.05,
checkpoint_interval = 10,
max_iter = 100)
# Nota: checkpoint evita que la gráfica de cálculo
# sea demasiado grande. Cada 10 iteraciones hace una
# nueva gráfica con los resultados de la última iteración.
```

```{r}
modelo
```

Hacemos predicciones para el conjunto de validación:

```{r, warning = FALSE, message = FALSE, fig.width=5, fig.asp=0.7}
preds <- ml_predict(modelo, valida_tbl) |>
mutate(prediction = ifelse(isnan(prediction), 3.5, prediction))
ml_regression_evaluator(preds, label_col = "calif", prediction_col = "prediction",
metric_name = "rmse")
```

Y podemos traer a R los datos de validación (que son chicos) para examinar:

```{r}
preds_df <- preds |> collect() #traemos a R con collect
ggplot(preds_df, aes(x = prediction)) + geom_histogram()
```


Examinamos ahora las dimensiones asociadas con películas:


```{r}
modelo$item_factors
```

```{r}
V_df <- modelo$item_factors |>
select(id, features) |> collect() |>
unnest_wider(features, names_sep = "_")
```

Nota: La columna *features* contiene la misma información de *feature_1,feature_2,...*, pero en forma de lista.

Examinemos la interpretación de los factores latentes de las
películas.

```{r}
pelis_nombres <- read_csv('../datos/netflix/movies_title_fix.csv', col_names = FALSE, na = c("", "NA", "NULL"))
names(pelis_nombres) <- c('peli_id','año','nombre')
medias_peliculas <- entrena_tbl |> group_by(peli_id) |>
summarise(num_calif_peli = n(), media_peli = mean(calif)) |>
collect()
latentes_pelis <- V_df |>
rename(peli_id = id) |>
left_join(pelis_nombres |> left_join(medias_peliculas))
latentes_pelis <- latentes_pelis |>
mutate(num_grupo = ntile(num_calif_peli, 10))
```

Podemos examinar las dimensiones latentes:

```{r}
top_tail <- function(latentes_pelis, feature){
top_df <- arrange(latentes_pelis, {{ feature }} ) |>
select(nombre, {{ feature }}, media_peli, num_calif_peli) |>
filter(num_calif_peli > 2000) |>
head(100)
tail_df <- arrange(latentes_pelis, desc({{ feature }}) ) |>
select(nombre, {{ feature }}, media_peli, num_calif_peli) |>
filter(num_calif_peli > 2000) |>
head(100)
print(top_df)
print(tail_df)
bind_rows(top_df, tail_df)
}
res <- top_tail(latentes_pelis, features_1)
```

Otra dimensión latente:

```{r}
res <- top_tail(latentes_pelis, features_3)
```


```{r}
res <- top_tail(latentes_pelis, features_10)
```

**Nota**: Podemos usar **ml_recommend** para producir recomendaciones de películas para
usuarios, o para cada película los usuarios más afines.

```{r}
pelis_nombres_sdf <- pelis_nombres |> select(peli_id, nombre) |>
sdf_import(sc = sc, overwrite = TRUE)
top_recom <- ml_recommend(modelo, type = "items", n = 40) |>
left_join(pelis_nombres_sdf)
top_recom
```

A partir de los candidatos producidos, podemos usar otras reglas
para decidir qué recomendar. En este caso filtramos películas con pocas
vistas, pero otros criterios son posibles.

```{r}
#num_usuario <- 22
num_usuario <- 771
peliculas_tbl <- peliculas_num |> collect() |> select(peli_id, n)
# recomendaciones
recoms <- top_recom |> filter(usuario_id==num_usuario) |>
collect() |>
left_join(peliculas_tbl) |> filter(n > 150) |>
select(nombre)
recoms
# qué le gustó?
vistas <- entrena_tbl |> filter(usuario_id==num_usuario) |> collect() |>
left_join(pelis_nombres |> collect() |> select(peli_id, nombre)) |>
arrange(desc(calif)) |> filter(calif > 4) |>
select(nombre, calif)
vistas
```


```{r}
recoms |> anti_join(vistas)
```


## Similitud de películas





```{r}
sparklyr::spark_disconnect_all()
```

0 comments on commit 49b26e3

Please sign in to comment.