NÚMEROS ALEATORIOS, BOOTSTRAPPING, DISTRIBUCIONES, SIMULACIÓN, INTERVALOS DE CONFIANZA, ESCENARIOS

Empezaremos viendo el caso de uso de la estructura iterativa while, dado que es más general que for.

While

while es similar a if en cuanto a que también revisa una condición, pero en este caso tenemos un ciclo dado que el código se repite mientras la condición sea TRUE.

counter <- 1
while (counter <= 5) {
 print(counter)
 counter <- counter + 1
}
## [1] 1
## [1] 2
## [1] 3
## [1] 4
## [1] 5

OJO: while tiene el riesgo particular de poder generar ciclos infinitos.¿Cómo pudiéramos tener un ciclo infinito en el siguiente ejemplo?

v2 <- c("a", "b", "c", "d", "e")
counter <- 1
while (counter <= length(v2)) {
 print(counter)
 print(v2[counter])
 counter <- counter + 1 # ¿Qué pasa si quitas esta instrucción?
}
## [1] 1
## [1] "a"
## [1] 2
## [1] "b"
## [1] 3
## [1] "c"
## [1] 4
## [1] "d"
## [1] 5
## [1] "e"

¿Cómo nos salimos del siguiente código? ¿Cuántas iteraciones tendrá?

loop <- "yes"
#while (loop != "no" & loop != "n") {
# loop <- readline("Shall we continue? ")
#}

En el caso anterior, no sabemos de antemano cuántas iteraciones se requerirán para salir del ciclo, todo depende de las respuestas que vayamos obteniendo. Este es el caso típico para utilizar while. Aunque también podemos utilizar while para casos donde sí sabemos de antemano cuántas iteraciones requeriremos, ese caso de uso es más para for.

For

for también genera ciclos, pero en este caso las iteraciones corresponderán con los elementos de un rango o vector que le proporcionemos. Observa estos ejemplos:

Proporcionamos un vector numérico a i:

for (i in 1:5) {
 print(i)
}
## [1] 1
## [1] 2
## [1] 3
## [1] 4
## [1] 5

Proporcionamos un vector numérico a x y lo utilizamos para seleccionar elementos de otro vector teams:

teams <- c("Bulls", "Lakers", "Pistons", "Heat", "Spurs")
for (x in 5:1) {
 print(teams[x])
}
## [1] "Spurs"
## [1] "Heat"
## [1] "Pistons"
## [1] "Lakers"
## [1] "Bulls"

Nota que tanto la i y la x que utilizamos son arbitrarias, lo importante es que lo que utilicemos en los () del for es lo que tomará diferentes valores en cada iteración dentro de su bloque de código.

Aquí proporcionamos a x un vector con valores de texto teams directamente, en vez de un rango numérico:

teams <- c("Bulls", "Lakers", "Pistons", "Heat", "Spurs")
for (x in teams) {
 print(x)
}
## [1] "Bulls"
## [1] "Lakers"
## [1] "Pistons"
## [1] "Heat"
## [1] "Spurs"

Aquí proporcionamos un vector numérico a n, construido de otra manera:

for (n in seq(10, 1, -3)) {
 print(n)
}
## [1] 10
## [1] 7
## [1] 4
## [1] 1

Veamos ahora una función personalizada utilizando for.

Esta función calcula la sumatoria (∑) de todos los números entre 1 y n:

sumatoria <- function(n) {
 total <- 0
 for (i in 1:n) {
 total <- total + i
 }
 return(total)
}

Observa como inicializamos la variable total dentro de la función, con total <- 0 y luego vamos acumulando la sumatoria dentro del ciclo en ese mismo objeto, con la instrucción total <- total + i.

Probemos la función:

sumatoria(10)
## [1] 55

Ahora veamos un ejemplo más complicado: función que calcula la serie de Fibonacci

La serie de Fibonacci es una secuencia de números donde cada uno es la suma de los dos anteriores. Si consideramos 0** como el primer elemento, entonces la secuencia empieza así: 0, 1, 1, 2, 3, 5, 8, 13…

Construiremos un algoritmo que genere los primeros n elementos de la secuencia de Fibonacci, donde n es proveída por el usuario.

En las pruebas que hagamos, considera que los valores de esta serie crecen rápidamente, por lo que evita pedir más de 40 elementos de la serie, para evitar “overflow”. Nuestra función generará solo 10 números por default.

fibo <- function(n = 10) {
 f <- 0:1
 for (i in 3:n) {
 f[i] <- f[i-1] + f[i-2]
 }
 return(f)
}

Observa como inicializamos f con los primeros dos valores en f <- 0:1. En algunos casos, se ocupan valores iniciales de una serie para seguir construyéndola. Dado que ya tenemos los primeros dos valores, las iteraciones del ciclo for solo van de 3 a n en for (i in 3:n). Por último, nota cómo en cada iteración vamos guardando en el valor f[i], la suma de los dos valores anteriores que tenemos en el mismo vector f. Si quisiéramos hacer ese ciclo for sin tener dos valores iniciales y comenzando desde la posición 1, estaríamos tratando de acceder posiciones inexistentes del vector f en las primeras dos iteraciones.

Probemos la función:

x = {}

x = fibo()

str(x)
##  int [1:10] 0 1 1 2 3 5 8 13 21 34

Mini-reto 1: Validación de calificaciones

Intenta hacer este código por tu cuenta, antes de ver el resultado.

Queremos hacer una función que valide las calificaciones de los estudiantes que ingresa un profesor a la plataforma escolar. Calificaciones negativas y calificaciones superiores a 100 deben ser rechazadas, hasta que se ingrese alguna calificación entre 0 y 100.

¿Qué ciclo utilizarías para este caso: while o for?

¿Cuál es la condición para salir del ciclo, cómo se debe plantear?

Tip: En la vida diaria es común visualizar ciclos como actividades que tenemos que hacer “hasta” que se cumpla cierta condición, sin embargo, el ciclo while opera mientras se cumpla cierta condición. Si planteas las condiciones pensando en hasta, debes utilizar sus opuestos para que funcionen en el caso de mientras. Por ejemplo: == en vez de !=, > en vez de <=, & (and) en vez de | (or), etc.

Solución: Se implementa la solución según lo requerido. Se utilizará un ciclo while y la condición para salir del ciclo será cuando la variable status se le asigne el valor “aceptada”.

valida_calif = function(calificacion) {
  status_calif <- {}
  i = 1
  status = "rechazada"
  while (status == "rechazada") {
    calificacion = as.numeric(readline("Ingrese calificación entre 0 y 100:"))
    if (calificacion < 0 | calificacion > 100) {
      status = "rechazada"
      status_calif[i] = "rechazada"
      i = i + 1
    } else {
      status = "aceptada"
      status_calif[i] = "aceptada"
    }
  }
  return(status_calif)
}

hi <- function() {
 nombre <- readline("Nombre? ")
 print(paste0("Hola ", nombre, "!"))
}

Probando la función creada

#valida_calif(-4)

#hi()

Solución del curso

calif <- function(grade) {
 while (grade < 0 | grade > 100) {
 grade <- as.numeric(readline("Calificación inválida, ingresa u
na entre 0 y 100: "))
 }
 print("¡Calificación válida!")
}

Mini-reto 2: Factorial

Intenta hacer este código por tu cuenta, antes de ver el resultado.

Ahora construye una función que calcule el factorial de un número n. El factorial de un número se define como el producto o la multiplicación de todos los números entre 1 y n.

En matemáticas, el factorial de un número n se denota como n!, por ejemplo el factorial de 3 es: 3! = 3 ∗ 2 ∗ 1 = 6

¿Qué ciclo utilizarías para este problema?

¿Cómo irías acumulando la multiplicatoria en cada iteración?

Solución:

Setearemos a 1 dos variables a utilizar dentro de la función (variables locales)

Aplicaremos un ciclo while

factorialn = function(n) {
  n = as.integer(n)
  facto = 1
  i = 1
  while (i <= n) {
    facto = facto * i
    i = i + 1
  }
  return(facto)
}

Prueba: Probando la función realizada junto a la función preinstalada del paquete R

x = factorialn(5) # Función implementada
x
## [1] 120
y = factorial(5) # Función de R
y
## [1] 120

Solución del curso

fact <- function(n) {
 total <- 1
 for (i in 1:n) {
 total <- total * i
 }
 return(total)
}

Este ejemplo es similar al que hicimos de la sumatoria (∑), sin embargo tiene algunas diferencias importantes. Observa como inicializamos la variable total dentro de la función, con total <- 1 y luego vamos acumulando la multiplicatoria (∏) dentro del ciclo en ese mismo objeto, con la instrucción total <- total * i. Aunque en la sumatoria el valor neutro es el 0 (no le afecta a lo que se sume), en el caso de multiplicatoria el valor neutro es el 1 (no afecta a lo que se multiplique).Probemos la función:

fact(5)
## [1] 120

Lectura y consolidación automatizada de API

Vimos en el módulo anterior cómo leer archivos externos y también cómo leer información mediante una API. En ese entonces tanto la lectura y la consolidación fue “de uno en uno”, repitiendo las líneas de código explícitamente.

Ahora que ya sabes utilizar ciclos, utilizaremos uno para poder escalar esta tarea a cualquier número acciones de cuyas cuales queramos la información.

Veremos en este ejemplo el caso de conexión a una API. El proyecto final será muy similar a esto, pero aplicado a archivos externos, por lo que tendrás que tener que hacer las adecuaciones necesarias al código.

Definimos una lista de tickers que queremos leer:

# puedes quitar tickers o agregar

tickers <- c("AAPL", "AMZN", "NFLX", "TSLA", "WMT", "SBUX", 
 "MCD", "DIS", "GOOG", "JPM", "SPY", "^GSPC")
tickers
##  [1] "AAPL"  "AMZN"  "NFLX"  "TSLA"  "WMT"   "SBUX"  "MCD"   "DIS"   "GOOG" 
## [10] "JPM"   "SPY"   "^GSPC"

Para tener la flexibilidad de quitar o agregar tickers a la lista anterior, leeremos la longitud del vector tickers y la guardaremos en n. Esto nos servirá para saber cuántas iteraciones tenemos que hacer con nuestro ciclo. Por tanto, ¿qué ciclo nos conviene utilizar, un while o un for?

n <- length(tickers)
n
## [1] 12

Cuando leímos los archivos externos y los consolidamos “a mano”, leímos solo las dos columnas que nos interesaban: Date y Adj.Close.

Para este caso de lectura mediante la API de Yahoo Finance, especificaremos que queremos solo la columna 6 por las siguientes dos razones:

1. En el caso de los xts, las fechas están en los “rownames”, por tanto “ya viene integrada” y no se cuentan como columna.

2. getSymbols() le pone el ticker como prefijo a cada nombre de columna, lo cual es bueno para seguir diferenciando cada columna al fusionar la información de diferentes acciones.

Sin embargo, el nombre de columna Adj.Close que solíamos usar de los archivos, ahora va cambiando en cada acción y es más fácil referirnos a esa columna por su posición que por su nombre.

p <- getSymbols(Symbols = tickers[1], src = "yahoo",
 from = "2016-12-31", to = "2020-12-31",
 periodicity = "monthly", auto.assign = F)[, 6] # Adj.Close

names(p) <- tickers[1]
head(p)
##                AAPL
## 2017-01-01 28.43845
## 2017-02-01 32.10370
## 2017-03-01 33.81279
## 2017-04-01 33.81043
## 2017-05-01 35.95462
## 2017-06-01 34.03743

Dado que los nombres de columna de las acciones que tenemos en p y los de la columna de la nueva consulta que acabamos de hacer son diferentes, hay varias formas de hacer la consolidación, algunas más eficientes que la mostrada a continuación. Pero para propósitos didácticos y que les sea de utilidad el ejemplo para el proyecto, seguimos una lógica similar que cuando consolidamos los archivos “uno por uno”. Solo recuerda que en el caso de archivos externos tenemos tanto en p como en temp la columna Date y en este caso no, dado que getSymbols( ) nos regresa un xts.

for (i in 2:n) {
 temp <- getSymbols(Symbols = tickers[i], src = "yahoo",
 from = "2016-12-31", to = "2020-12-31",
 periodicity = "monthly", auto.assign = F)[, 6]
 names(temp) <- tickers[i]
 p <- merge(p, temp)
}
head(p)
##                AAPL    AMZN   NFLX     TSLA      WMT     SBUX      MCD      DIS
## 2017-01-01 28.43845 41.1740 140.71 16.79533 59.59708 49.24669 106.7585 105.9607
## 2017-02-01 32.10370 42.2520 142.13 16.66600 63.33863 50.71820 111.1832 105.4244
## 2017-03-01 33.81279 44.3270 147.81 18.55333 64.36556 52.30843 113.7213 108.5846
## 2017-04-01 33.81043 46.2495 152.20 20.93800 67.62741 53.80450 122.7761 110.7009
## 2017-05-01 35.95462 49.7310 163.07 22.73400 70.70384 56.98475 132.3926 103.3655
## 2017-06-01 34.03743 48.4000 149.41 24.10733 68.53278 52.45195 134.3843 101.7472
##               GOOG      JPM      SPY  X.GSPC
## 2017-01-01 39.8395 71.57764 205.8599 2278.87
## 2017-02-01 41.1605 77.06790 213.9485 2363.64
## 2017-03-01 41.4780 74.70363 213.2880 2362.72
## 2017-04-01 45.2980 73.98927 216.3423 2384.20
## 2017-05-01 48.2430 70.26601 219.3955 2411.80
## 2017-06-01 45.4365 78.17788 219.7226 2423.41

La tabla anterior nos sirve para explorar un poco los datos. Visualizarlos también es útil para tener una mejor idea de ellos.

plot(p[, c("AAPL","NFLX")], col = c("blue", "red"), main = "Time-series")

Cálculo de retornos logarítmicos

La función lag() se encuentra en los paquetes stats y dplyr y cuál usar depende de la estructura de datos con la que estemos trabajando:

• Para dataframes usar dplyr::lag()

• Para xts usar stats::lag()

Utilizaremos stats::lag() dado que en este ejemplo el consolidado es un xts, pero al leer archivos externos el consolidado será un data frame por lo que lo apropiado será usar dplyr::lag().

r <- log(p/stats::lag(p))

head(r)
##                     AAPL        AMZN        NFLX         TSLA         WMT
## 2017-01-01            NA          NA          NA           NA          NA
## 2017-02-01  1.212292e-01  0.02584468  0.01004108 -0.007730394  0.06088890
## 2017-03-01  5.186763e-02  0.04794231  0.03918548  0.107278734  0.01608316
## 2017-04-01 -6.962106e-05  0.04245668  0.02926777  0.120916240  0.04943476
## 2017-05-01  6.148819e-02  0.07257781  0.06898417  0.082295867  0.04448652
## 2017-06-01 -5.479679e-02 -0.02712861 -0.08748537  0.058654469 -0.03118780
##                   SBUX        MCD         DIS         GOOG          JPM
## 2017-01-01          NA         NA          NA           NA           NA
## 2017-02-01  0.02944268 0.04060999 -0.00507402  0.032620176  0.073904087
## 2017-03-01  0.03087272 0.02257110  0.02953501  0.007684132 -0.031158205
## 2017-04-01  0.02819956 0.07661233  0.01930282  0.088099691 -0.009608549
## 2017-05-01  0.05742649 0.07540907 -0.06856038  0.062987858 -0.051631884
## 2017-06-01 -0.08288623 0.01493174 -0.01578078 -0.059934971  0.106698491
##                     SPY        X.GSPC
## 2017-01-01           NA            NA
## 2017-02-01  0.038539307  0.0365230010
## 2017-03-01 -0.003091929 -0.0003892729
## 2017-04-01  0.014218256  0.0090501323
## 2017-05-01  0.014014196  0.0115097593
## 2017-06-01  0.001489955  0.0048022259

Como puedes ver, obtuvimos NAs en todo el primer renglón de los retornos, dado que no es posible calcular ese valor: no tenemos el precio anterior a ese día.

Podemos eliminar esos retornos de las siguientes maneras:

Eliminar el primer renglón de r. Esto es lo que recomiendo en este caso, dado que estamos seguros que son puros NAs.

• Utilizar la función na.omit() que sirve para eliminar todos los renglones de la tabla que tengan NAs. En este caso coincide que solo hay NAs en el primer renglón y por tanto tiene el mismo resultado que lo anterior, pero si hubiera NAs en otros renglones también eliminaríamos esos. Este resultado puede ser preferible o no, respecto a lo anterior. En cualquier caso, solo hay que estar seguros de que lo que está sucediendo es de manera intencionada.

Para eliminar el primer renglón, podemos hacer un subsetting pero con números negativos. En este caso el −1 en la coordenada de renglones indica que queremos “todos los renglones menos el 1”. Al no indicar nada en la coordenada de las columnas, indicamos que queremos “todas las columnas”.

r <- r[-1, ]

head(r)
##                     AAPL        AMZN        NFLX         TSLA         WMT
## 2017-02-01  1.212292e-01  0.02584468  0.01004108 -0.007730394  0.06088890
## 2017-03-01  5.186763e-02  0.04794231  0.03918548  0.107278734  0.01608316
## 2017-04-01 -6.962106e-05  0.04245668  0.02926777  0.120916240  0.04943476
## 2017-05-01  6.148819e-02  0.07257781  0.06898417  0.082295867  0.04448652
## 2017-06-01 -5.479679e-02 -0.02712861 -0.08748537  0.058654469 -0.03118780
## 2017-07-01  3.218003e-02  0.02022787  0.19544260 -0.111459838  0.05538757
##                   SBUX        MCD         DIS         GOOG          JPM
## 2017-02-01  0.02944268 0.04060999 -0.00507402  0.032620176  0.073904087
## 2017-03-01  0.03087272 0.02257110  0.02953501  0.007684132 -0.031158205
## 2017-04-01  0.02819956 0.07661233  0.01930282  0.088099691 -0.009608549
## 2017-05-01  0.05742649 0.07540907 -0.06856038  0.062987858 -0.051631884
## 2017-06-01 -0.08288623 0.01493174 -0.01578078 -0.059934971  0.106698491
## 2017-07-01 -0.07715996 0.01909393  0.03404897  0.023674077  0.004367085
##                     SPY        X.GSPC
## 2017-02-01  0.038539307  0.0365230010
## 2017-03-01 -0.003091929 -0.0003892729
## 2017-04-01  0.014218256  0.0090501323
## 2017-05-01  0.014014196  0.0115097593
## 2017-06-01  0.001489955  0.0048022259
## 2017-07-01  0.025210680  0.0191640177

El uso de la función na.omit() sería así:

r <- na.omit(r)

head(r)
##                     AAPL        AMZN        NFLX         TSLA         WMT
## 2017-02-01  1.212292e-01  0.02584468  0.01004108 -0.007730394  0.06088890
## 2017-03-01  5.186763e-02  0.04794231  0.03918548  0.107278734  0.01608316
## 2017-04-01 -6.962106e-05  0.04245668  0.02926777  0.120916240  0.04943476
## 2017-05-01  6.148819e-02  0.07257781  0.06898417  0.082295867  0.04448652
## 2017-06-01 -5.479679e-02 -0.02712861 -0.08748537  0.058654469 -0.03118780
## 2017-07-01  3.218003e-02  0.02022787  0.19544260 -0.111459838  0.05538757
##                   SBUX        MCD         DIS         GOOG          JPM
## 2017-02-01  0.02944268 0.04060999 -0.00507402  0.032620176  0.073904087
## 2017-03-01  0.03087272 0.02257110  0.02953501  0.007684132 -0.031158205
## 2017-04-01  0.02819956 0.07661233  0.01930282  0.088099691 -0.009608549
## 2017-05-01  0.05742649 0.07540907 -0.06856038  0.062987858 -0.051631884
## 2017-06-01 -0.08288623 0.01493174 -0.01578078 -0.059934971  0.106698491
## 2017-07-01 -0.07715996 0.01909393  0.03404897  0.023674077  0.004367085
##                     SPY        X.GSPC
## 2017-02-01  0.038539307  0.0365230010
## 2017-03-01 -0.003091929 -0.0003892729
## 2017-04-01  0.014218256  0.0090501323
## 2017-05-01  0.014014196  0.0115097593
## 2017-06-01  0.001489955  0.0048022259
## 2017-07-01  0.025210680  0.0191640177

Ahora calcularemos los retornos promedio anualizados y las volatilidades anualizadas, dado que así se identifican más en la industria.

El argumento na.rm = T no es necesario en estos dos bloques de código siguientes, dado que ya eliminamos los NAs con la función na.omit(), pero lo dejo especificado ya que puede ser útil en otros casos donde obtengamos NAs por tener algún NA en la columna correspondiente.

ann_r <- apply(r, 2, mean, na.rm = T)*12

print(round(ann_r, 4))
##   AAPL   AMZN   NFLX   TSLA    WMT   SBUX    MCD    DIS   GOOG    JPM    SPY 
## 0.3906 0.3511 0.3437 0.6739 0.2175 0.1894 0.1682 0.1370 0.2012 0.1323 0.1450 
## X.GSPC 
## 0.1276

Dado que tenemos datos mensuales, multiplicamos los retornos: ∗ 12 y las desviaciones estándar ∗ √12 (si fueran varianzas, sí serían ∗ 12). Si fueran datos trimestrales el factor sería 4 en vez de 12 y si fueran datos diarios el factor sería 252 en vez de 12 (los días de cotización del año o “trading days”), etc.

ann_sd <- apply(r, 2, sd, na.rm = T)*sqrt(12)

print(round(ann_sd, 4))
##   AAPL   AMZN   NFLX   TSLA    WMT   SBUX    MCD    DIS   GOOG    JPM    SPY 
## 0.3082 0.2851 0.3374 0.6334 0.1869 0.2323 0.1795 0.2803 0.2181 0.2549 0.1691 
## X.GSPC 
## 0.1658

¿Te hacen sentido los retornos y las volatilidades que ves, de acuerdo a lo que sabes de las empresas que estamos analizando?

Veamos ahora una gráfica para ver la relación histórica riesgo-retorno de estas acciones.

plot(ann_r ~ ann_sd, ylim = c(min(ann_r), max(ann_r)*1.05))

text(ann_r ~ ann_sd, labels = names(ann_r), pos = 3, cex = 0.6, offset = 0.3)

La misma gráfica anterior, pero utilizando el paquete ggplot2:

library(ggplot2) # solo es necesario activarlo una vez por sesión

rsd <- data.frame(ann_sd, ann_r)

ggplot(rsd, aes(x = ann_sd, y = ann_r)) +
 geom_point() +
 geom_text(label = rownames(rsd), size = 3, nudge_y = -0.01)