Skip to content

My words, sometimes technical, sometimes not

Como crear un blog con Blogdown y Netlify

En este post vamos a ver el proceso reusmido para crear un blog donde podemos generar contenido directamente desde RStudio, utilizando el paquete blogdown, Github y Netlify. La idea es que al finalizar la configuración, simplemente creemos el post en Rstudio (un markdown) usando blogdown y que al subirlo a github automaticamente se actualice el blog y se vea reflejado en nuestra página. Es lo que estoy haciendo en este momento.

Lo primero y más importante más allá de este tema en particular es tener una cuenta en github. Si no la tienen se los recomiendo ampliamente para hacer version control - actualizar código de forma segura y con backups constantes en un servidor + compartir proyectos. Es gratis, al menos la versión básica que alcanza y sobra para el uso cotidiano. En nuestro repositorio crearemos un proyecto para el blog y ahi se subirán nuestros posts en formato html. A su vez, estará el theme y otras configuraciones básicas del blog.

Lo segundo es instalar el paquete blogdown en nuestro R. Es una obra maestra de Yihui Xie, ingeniero de RStudio y creador de varios paquetes. En un nuevo proyecto de R, ejecutan el siguiente comando.

blogdown::new_site()

Con eso ya tienen generado la estructura básica de lo que será su blog. Se crearán carpetas y archivos en la ruta del proyecto con contenido de prueba para tener algo funcional. Blogdown es bastante complejo y hay un millón de configuraciones y detalles que uno puede personalizar. No entraremos en eso acá porque se haría super extenso. Para eso está la documentación oficial en ingles.

Eventualmente van a tener que cambiar el archivo config.toml con ciertos pasos de la guía y luego podrán explorar todas las posibilidades que presenta. Entre ellas pueden (y recomiendo) descargar otro theme para cambiar el formato. El que uso actualmente es tranquilpeak.

Para crear un post nuevo simplemente escriben el comando.

blogdown::new_post()

Lo cual genera un script con un YAML, que es la configuración con el título, tags y otros metadatos del post. Simplemente escriben como cualquier markdown debajo. Cuando terminan el post (o mientras para ir visualizando como queda) corren

blogdown::serve_site()
Lo cual generará el archivo html correspondiente que luego será usado en su web y les permite ver el resultado temporal de su post.

Luego lo que deben hacer es pushear la carpeta que se les generó del blog a su repositorio en github. O al menos las carpetas y archivos que se ven en la siguiente imagen. Public no la pusheen.

gitsnap

Llegado a este punto tenemos el contenido inicial del blog, pero no hay sitio web. Ahí es donde entra en juego Netlify. No vamos a entrar en el paso a paso minucioso pero básicamente deben crearse una cuenta, generar una nueva web y linkearla a su repositorio Github. Es bastante lineal. Luego configuran el nombre de la web y otros detalles y en cuestión de minutos ya están publicados! (Y Gratis.) Para ver bien esta etapa les recomiendo la explicación del link de blogdown.

Una vez puesto en marcha solo es cuestión de abrir su proyecto en R (en su pc), crear un nuevo post con blogdown::new_post() y pushear a github! Prueben chusmeando todas las configuraciones para cambiar la estética del blog!

Hola MAP. Chau Apply

Introducción

La idea de este post es introducirlos a la familia de funciones MAP, propias de tidyverse. A grandes rasgos son un remplazo MUY útil a la familia de funciones APPLY, propias de R base. Estas últimas se suelen enseñar en todos los cursos introductorios de R, como la manera correcta de aplicar funciones a listas o columnas de dataframes. No es que no sirvan, pero dado el surgimiento de tantas librerías que facilitan el manejo de la data, no tiene sentido seguir insistiendo con ellas dado que hay nuevas con mayor flexibilidad, muy sencillas de utilizar y mucho más amenas.

  • Lo mejor que tienen las funciones MAP es:
    • Consistencia en los inputs.
    • Flexibilidad del output.
    • Integración con todo el universo tidyverse y prolijidad.

Empecemos.

library(purrr) # MAP está contenida acá
library(dplyr)
## Warning: package 'dplyr' was built under R version 4.0.5

Como regla general, MAP aplica funciones a elementos de una lista o de un vector. Su output es otra lista. Muy similar a lapply().

l1 <- list( a= c(100,200), b = c(8,10))
map(l1, max)

## $a
## [1] 200
## 
## $b
## [1] 10
A cada lista le calcula el máximo y devuelve una lista con cada elemento siendo el resultado de la función.

Tenemos la flexibilidad para pasarle funciones anónimas..

map(l1, function(x) max(x))
## $a
## [1] 200
## 
## $b
## [1] 10

Aplicando funciones a elementos de un vector. Cada numero de 1 a 5 es usado como primer input de la funcion rnorm, sd y n son otros parámetros de rnorm. El resultado de nuevo es una lista.

set.seed(1)
1:5 %>% map(., rnorm,sd =2, n=5)
## [[1]]
## [1] -0.2529076  1.3672866 -0.6712572  4.1905616  1.6590155
## 
## [[2]]
## [1] 0.3590632 2.9748581 3.4766494 3.1515627 1.3892232
## 
## [[3]]
## [1]  6.023562  3.779686  1.757519 -1.429400  5.249862
## 
## [[4]]
## [1] 3.910133 3.967619 5.887672 5.642442 5.187803
## 
## [[5]]
## [1] 6.837955 6.564273 5.149130 1.021297 6.239651

Consistencia entre variantes

Por ahora solo vimos la versión de lapply en MAP, pero esta familia tiene varios integrantes.

map_if

Ejecuta la función solo si el elemento cumple determinada condición. Devuelve una lista.

l2 <- list(a = 213, b = "string", c = c(1,2))
map_if(l2, is.numeric, function(x) x*2)

## $a
## [1] 426
## 
## $b
## [1] "string"
## 
## $c
## [1] 2 4
El output es la lista original con los elementos correspondientes transformados. Vemos que no hubo ningún problema con "string" ya que fue omitido.

map_at

Ejecuta la función solo en los elementos que seleccionemos. No hace falta que cumplan alguna condición. Misma función de antes pero solo aplicada al tercer elemento. Devuelve una lista.

map_at(l2, c(3), function(x) x*2)
## $a
## [1] 213
## 
## $b
## [1] "string"
## 
## $c
## [1] 2 4

Variantes súper útiles que permiten no utilizar loops y que dan mucho control de manera sencilla sobre las funciones a ejecutar. Por otra parte, en términos de consistencia, la estructura es siempre la misma. El primer argumento es x= y luego viene la función a aplicar. En el caso de map_if y map_at entre medio surge el condicionante. Si recuerdan, la familia apply cambia el orden de los inputs según si es apply, lapply, mapply, sapply...

Flexibilidad del output

Por el momento vimos que todos los outputs eran listas. Lo interesante es que podemos controlar eso y cambiar el formato del resultado, ahorrándonos conversiones molestas con unlist y etc.

l3 <- list(c(1,2,4), c(100,200), c(5000,6000))
map_dbl(l3, max)
## [1]    4  200 6000

Nos devuelve un vector con los resultados de aplicar la función max a cada elemento!

De este mismo tipo esta.

  • map_chr # vector caracter
  • map_int # vector de integers
  • map_lgl # vector de booleanos

Cadenas de Markov

Las cadenas de Markov son herramientas muy útiles para modelar transiciones entre estados. Imaginemos un escenario sencillo con dos posibles estados: día de lluvia o día soleado. En el momento t el estado supongamos que es "día soleado". ¿Cuál será el estado en t+1 ? Dadas las probabilidades de transición de un estado a otro podemos simular escenarios tras el paso del tiempo. Mismo en algunos casos puede ser útil entender si en el largo plazo esta simulación converge hacia algún resultado estable en el tiempo.

Mostraremos ejemplos sencillos, pero la metodología es escalable a procesos complejos como pueden usarse en meteorología, aplicaciones financieras, etc. El algoritmo de búsqueda de Google está basado en cadenas de Markov, para que vean la utilidad. 1

Lo que necesitamos para este ejercicio son las probabilidades de transición de un estado a otro y los valores iniciales en cada estado.

Supongamos una clase de estadística con n estudiantes, en la que dos estados son posibles. Estado A : El alumno está atento. Estado B : El alumno está aburrido.

  • Las probablidades son las siguientes:
    • En t+1 el 80% de A sigue en A (y por lo tanto el 20% de A pasa a B)
    • En t+1 el 25% de B pasa a A (y por lo tanto el 75% de B queda en B)

La matriz que representa esa probabilidades la vamos a llamar * Matriz de Transición *.

tmatrix <-  as.matrix(c(0.8,0.25))  
# 80% de A sigue en A, 25% de B pasa a A en t+1

tmatrix <- t(tmatrix) 
# trasponemos porque necesitamos esta información en la primera fila.

tmatrix <- rbind(tmatrix, 1 - tmatrix[1,]) 
# Agregamos la segunda fila, que es 1 menos la primera.

tmatrix # Matriz de transción
##      [,1] [,2]
## [1,]  0.8 0.25
## [2,]  0.2 0.75

La matriz de valores iniciales es la siguiente: En un primero momento (t), el 10% de los alumnos está atento y el 90% aburrido.

smatrix <- as.matrix(c(0.1,0.9)) # Matriz inicial, 10% Atento, 90% aburridos

Ya tenemos toda la información necesaria para hacer nuestras primeras simulaciones. Supongamos que cada período de tiempo son 10 minutos, por lo tanto si t es el momento 0, t+1 es a los 10 de minutos de empezada la clase, t+2 a los 20 y así sucesivamente.

Para evaluar el porcentaje de alumnos concentrados en determinado momento de la clase lo que debemos hacer es multiplicar la matriz de estado inicial por la matriz de transición tantas veces como momentos a futuro querramos simular.

Por ejemplo si queremos ver que pasará con nuestros alumnos luego de 20 minutos de clase debemos multiplicar smatrix * tmatrix 2 veces.

for (i in 1:2){            # Loopeamos 2 veces. %*% es la multiplicacion matricial.
  smatrix = tmatrix %*% smatrix  
# smatrix va tomando nuevos valores en cada iteracion, 
# reflejando el movimiento de un estado a otro
}

smatrix  # Despues de 2 iteraciones -> A 42% , B 58%
##         [,1]
## [1,] 0.41775
## [2,] 0.58225

Vemos que luego de 2 transiciones el estado A está compuesto por casi 42% de alumnos (concentrados) y 58% no atentos. Son movimientos bastante rápidos de un estado a otro.

Veamos qué pasa luego de 10 iteraciones.

smatrix <- as.matrix(c(0.1,0.9)) # Reseteamos smatrix a su estado inicial

for (i in 1:10){           
# Loopeamos 10 veces. %*% es la multiplicacion matricial.
  smatrix = tmatrix %*% smatrix   
# smatrix va tomando nuevos valores en cada iteracion, 
# reflejando el movimiento de un estado a otro
}

smatrix  # Despues de 10 iteraciones -> A 55% , B 45%
##           [,1]
## [1,] 0.5544017
## [2,] 0.4455983

Luego de 10 movimientos, A pasa a tener el 55% de los alumnos dejando a 45% en B (no atentos). En este ejemplo con el paso del tiempo los alumnos se concentran más y más en la clase. Pero es este un proceso continuo e infinito? Llega un momento en que dada la matriz de transción todos los alumnos pasan a estar en el estado A, es decir, atentos?

Para analizar esto podemos ver qué sucede luego de 1000 iteraciones (sería una clase muy muy larga...)

smatrix <- as.matrix(c(0.1,0.9)) # Reseteamos smatrix a su estado inicial

for (i in 1:1000){  # Loopeamos 10 veces. %*% es la multiplicacion matricial.
  smatrix = tmatrix %*% smatrix  
# smatrix va tomando nuevos valores en cada iteracion,
# reflejando el movimiento de un estado a otro
}

smatrix  # Despues de 1000 iteraciones -> A 55% , B 45%
##           [,1]
## [1,] 0.5555556
## [2,] 0.4444444

El resultado es casi idéntico luego de 1000 iteraciones al intento de tan solo 10 iteraciones. Por lo tanto podemos ver que esta cadena de Markov converge rápidamente a 55% en A y 45% en B. Es un estado estacionario del cual no podemos movernos dada la matriz de transición que tenemos.

Este ejemplo sencillo permite ver la intuición detrás de esta potente herramienta en unos pocos pasos. Obviamente como comentamos antes se puede utilizar para procesos mucho más complejos y de muy diversas areas. Queríamos mostrar los fundamentos con una aplicación rápida y que se comprenda que lo que se requiere es una matriz de transición y un estado inicial.


  1. Para una explicación gráfica y muy didáctica dejamos el siguiente link en inglés. 

Simulación de Monty Hall

Vamos a ver en un corto y sencillo ejemplo cómo hacer una simulación en R. El caso a utilizar es el "famoso" problema de Monty Hall, asociado a un programa televisivo de Estados Unidos.

En breve, el problema consiste en que un concursante debe elegir una entre 3 puertas (A,B y C). Detrás de una hay un premio (en general un automóvil) y tras las otras dos no hay nada (o una cabra en algunos ejemplos, lo cual no me parece tan malo en realidad..). Una vez que el concursante eligió una puerta, el organizador del programa, que sabe qué puerta oculta el premio y cuáles no, abre una de las dos puertas restantes, tras la cual no hay premio (recuerden que sabe qué hay detrás de cada puerta). Ante el nuevo escenario, el concursante debe elegir si mantiene su elección original o decide cambiar por la puerta restante, es decir, la que no eligió ni la que abrió el organizador.

  • Qué conviene hacer ante tal incertidumbre?
    • Cambiar?
    • Mantenerse fiel a la decisión original sin caer en los juegos psicológicos del programa?
    • Es indistinto? 50/50 entre las dos puertas.

La simulación deberíaa darnos una respuesta acertada. Comencemos.

Generamos las puertas y un vector donde vamos a guardar el resultado de la simulación.

puertas <- c("A","B","C")
xdata   <- c()

Ahora lo que vamos a hacer es simular 10000 escenarios distintos emulando la lógica del problema. En cada uno vamos a asignarle a una puerta al azar el premio (con el comando "sample"). Luego elegiremos, como si fueramos el concursante, una puerta al azar. Descartaremos una de las puertas sin premio y por último y más importante, vamos a analizar en cada escenario qué hubiera pasado si nos quedabamos con la puerta elegida originalmente y qué hubiera pasado si cambiábamos. Si no hay diferencia entre cambiar y no cambiar, luego de 10000 simulaciones deberíamos haber ganado 5000 veces al cambiar y 5000 al no cambiar (con algún margen de error). En caso contrario, alguna de las dos estrategias es superadora.

set.seed(10)
for (i in 1:10000) {  # 10000 iteraciones
  premio <- sample(puertas)[1] # Asignar al premio una puerta al azar
  eleccion <- sample(puertas)[1] # Concursante elige una puerta al azar
  abrir <- sample(puertas[which(puertas != eleccion 
  & puertas != premio)])[1]
  # "Abren" una que no es la que elegiste ni la que tiene premio
  cambiarsi <- puertas[which(puertas != eleccion 
  & puertas != abrir)] # Situacion si cambiaras. 
  if(eleccion == premio) (xdata = c(xdata,"nocambiargana")) 
  # Caso en que eleccion original ganara y guardas resultado
  if(cambiarsi == premio)(xdata = c(xdata, "cambiargana"))
  # Caso en que cambiar ganara y guardas resultado
}

LLegado este punto tenemos un vector xdata que tiene para cada una de las 10000 iteraciones, qué estategia hubiera ganado. Si cambiar de puerta o no cambiar. Ahora simplemente contamos cuántas hay de cada una y analizamos el resultado.

length(which(xdata == "cambiargana")) # Cantidad que hubieran ganado si cambiabas
## [1] 6623
length(which(xdata == "nocambiargana")) 
## [1] 3377
# Cantidad que hubieran ganado si no cambiabas

table(xdata)
## xdata
##   cambiargana nocambiargana 
##          6623          3377

Para sorpresa o no de ustedes, la elección parece obvia. Cambiar de puerta nos hace elegir el premio un 66% de las veces y no cambiar tan solo un 33%.

Entender por qué es interesante.

Al elegir inicialmente una puerta entre las 3 opciones, la probabilidad de acertarle al premio es ⅓. De ahí que la estrategia de no cambiar de puerta es efectiva solo un 33% de las veces. Es simplemente quedarse con la elección inicial que tenia ⅓ de chances de ser correcta, independientemente de la puerta que abra el organizador.

Ahora, supongamos que en la elección inicial elegimos una puerta que no contiene el premio ( 66% de probabilidades). La estrategia de no cambiar de puerta nos hace perder indiscutidamente.

Si juntamos las dos proposiciones obtenemos que no cambiar de puerta nos hace ganar ⅓ de las veces, cuando elegimos bien por azar la puerta del premio inicialmente, y nos va a hacer perder el resto de las veces - ⅔ de las veces - que es cuando elegimos una puerta sin premio al principio.

Otro razonamiento equivalente es que cambiar de puerta nos hacer perder siempre y cuando hayamos elegido la puerta del premio originalmente (⅓) pero nos va a hacer ganar siempre que hayamos elegido una sin premio (⅔) porque la nueva puerta si o sí tendrá el premio, ya que la tercera es la que abre el organizador y no tiene premio.

En caso de que la rápida intuición nos hiciera creer que la elección entre cambiar y no cambiar era indistinto y ambas tenían 50% de chances de garantizarnos el premio, el ejercicio de simulación nos hubiera hecho elegir correctamente.

En este caso razonar el problema es posible ya que involucra pocas opciones pero en situaciones más complejas la simulación puede ayudar enormemente a la toma de decisiones.

Tipos de Variables

Las características de las variables que podemos encontrar en un dataset son muy diversas, pero general se pueden clasificar bajo alguno de los siguientes formatos.

Categóricas o Cualitativas

Corresponden a variables con etiquetas o valores pero que no siguen un orden específico o no tienen jerarquía. Por ejemplo: color de ojos, género, sabor (dulce, salado, amargo), etc. Para estas variables, que una observación corresponda a un valor u a otro no revela mayor importancia o mejor ponderación sobre otra observación, son simplemente distintas.

df <- as.data.frame(matrix(c("Persona1","Persona2","Persona3","Verde","Marron",
      "Azul",      1.8,1.87,1.65,"Secundario Completo", "Universitario Completo",
      "Sin estudios",3,2,1),nrow = 3, ncol = 5))
names(df) <- c("ID","Ojos","Altura","Estudios","Hijos")

df$Ojos
## [1] "Verde"  "Marron" "Azul"

Simulamos una tabla muy básica con 4 columnas por observación. Como ejemplo de variable categórica tomamos el color de ojos de estas personas. Y vemos que puede tomar los valores azul, marrón o verde. Más allá de los gustos personales, en principio estas etiquetas no revelan ningún orden, simplemente valores distintos según el individuo.

Ordinales

Corresponden a variables con etiquetas pero que en este caso sí tienen un orden establecido o jerarquía. Es decir que una etiqueta es "mejor" o tiene un valor más elevado. Por otra parte, en esta jerarquía o escala no podemos determinar cuánto mejor es una etiqueta por sobre otra, solo sabemos cómo se ordenan.

Volvemos al ejemplo de nuestra tabla. Este caso tomamos la columna Nivel de estudios.

df$Estudios
## [1] "Secundario Completo"    "Universitario Completo" "Sin estudios"

Vemos que en nuestra tabla se pueden encontrar los valores "Sin estudios", "Secundario Completo" y "Universitario Completo". Podemos decir que esta columna presenta un orden lógico entre las distintas etiquetas y que Universitario completo es más deseable que Secundario Completo y este último a su vez más deseable que Sin Estudios. Por otra parte, no podemos definir concretamente cuánto más deseable una categoría sobre la otra. Es universitario Completo 2 veces mejor que Secundario completo? 1? 3?. Y esa "distancia", es la misma entre Secundario completo y Sin Estudios? No es clara amplitud.

Cuantitativas Discretas

Corresponden a variables numéricas que son numerables, es decir que podemos contar cuantos valores intermedios hay entre 2 valores cualquiera. Entre cada valor no hay infinitos posibles. Siguiendo nuestra tabla, el ejemplo a tener en cuenta es el de número de hijos.

df$Hijos
## [1] "3" "2" "1"

Cada persona puede tener entre 0 y digamos... 10 hijos para no ser tan extremistas. A su vez, no se pueden tener fracciones de hijo. Por lo tanto, entre 6 y 8 hijos, solo se pueden tener 7. No 7.4, ni 6.2. En ese sentido decimos que entre dos valores cualquiera podemos numerar los intermedios.

Cuantitativas Continuas

Corresponden a variables numéricas no numerables, es decir que entre 2 valores cualquiera, hay infinitos intermedios. En nuestro ejemplo, podemos tomar la columna Altura para ilustrarlo.

df$Altura
## [1] "1.8"  "1.87" "1.65"

En nuestro caso tenemos valores redondeados pero si tuviéramos valores más precisos, hay inifinitas posibilidades entre 1.7 y 1.8. Este tipo de variables son muy comunes y se encuentran por todos lados. Temperatura, longitudes, etc.