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