Intro
Este el primero de una serie de post mostrando y desmitificando como utilizar R, en lenguaje de programación favorito de los estadísticos y académicos, para analizar información en los mercados financieros. Para quién empieza de 0, dentro de poco publicaré un cuadernillo llamado “Investing: We Are Friends” donde en el apéndice se enseñará las bases con aplicaciones financieras.
Simple Screener
Para hacer una demostración aplicada crearemos un Escáner de Acciones, que sea fácil de codificar y escalable. El objetivo de este será calcular el valor de una acción mediante el ingreso neto como medida de rentabilidad, también conocido como Price-to-Earnings:
\(\frac{Price}{Net Income * shares}\)
Set Up
Primero lo primero. R, como también Python y Julia, tienen una serie de ventajas por sobre software propietario como Matlab, Stata o el gran Excel, que es su plataforma Open Source. Esto permite que sus funcionalidades estén siempre actualizadas y por sobre todas las cosas se ven potenciadas gracias al desarrollo de Paquetes (packages) que son escritos y publicados gratuitamente.
Para este ejemplo usaremos los siguientes:
tidyverse: famoso paradigma para manipular datos.
tidyquant: paquete para manipular series de tiempo financieras.
simfinR: sitio de información financiera estandarizada.httr: set de fórmulas para hablar con webs sencillamente.
jsonlite: paquete para convertir data frames de formatoJSONaR.
Los primeros dos paquetes serán los principales. Lo importante es siempre tener en cuenta que existen diversos paquetes o el mismo Base R que pueden hacer las mismas tareas. Como consejo es elegir aquellos que son populares para poder obtener ayuda de la comunindad (o StackOverflow) y en el largo plazo no correr riesgos de quedar huéfanos. Otro consejo también es saber que limitaciones tienen, por ejemplo el tidyverse es super conocido pero su Syntax alla Python fácil de aprender y leer pierde eficiencia cuando se trabaja con muchos datos.
Ahora cargamos los datos (previamente instalados):
Simfin
Esta es mi joya de la corona, ya que cuenta con una gran cantidad de información financiera estandarizada que otros paquetes como quantmod ya no pueden descargar de Yahoo! Finance desde que cambió las reglas de uso de su API.
Lo primero es hacerse un usuario para conseguir una api_key que servirá para descargar los datos. Una vez conseguida
Ahora si podemos ojear lo que hay adentro de Simfin. Para empezar vamos a descargar las compañias disponibles, luego para nuestro análisis usaremos el caso de AAPL.
# Descargar listado de empresas disponibles ne SimFin
info_companies <- simfinR_get_available_companies(my_api_key)
head(info_companies)## simId ticker name
## 1 171401 ZYXI ZYNEX INC
## 2 901704 ZYNE Zynerba Pharmaceuticals, Inc.
## 3 901866 ZVO Zovio Inc
## 4 994625 ZUO ZUORA INC
## 5 45730 ZUMZ Zumiez Inc
## 6 378251 ZTS Zoetis
# Buscamos el SimFin ID para Apple
stock <- info_companies %>%
filter(ticker == "AAPL") %>%
select(simId) %>%
as.numeric(.)
head(stock)## [1] 111052
Importar Datos
En mi opinión este fue el principal motivo por el cúal migré, sin darme cuenta, hacia R. La facilidad con la que uno puede descargar - importar - limpar - manipular los datos y automatizarlo me permitió concentrarme en los análisis en vez de cada vez que quería estudiar algo o simplemente actualizarlo perder horas recontruyendo el proceso.
Parametrizar
Desde la programación hay que entender y ser honestos de cuando algo va a ser one shot y cuando una tarea recurrente. Suponiendo lo segundo, crearemos un super sencillo escáner de acciones. El primer paso es Parametrizar los argumentos de algunas funciones. Este apartado suele ser reiterativo, uno siempre encuentra algo que puede ser parametrizado a medida que conoce mejor los paquetes, las funciones y la tarea que está llevando a cabo.
En nuestro caso de hoy los parametros serán los siguientes, sumados a stock (donde guardamos el código Simfin de Apple):
## [1] 111052
## Tipo de Informe financiero "P/L"
statements = c("pl")
## Periodos
period = c("Q1", "Q2", "Q3", "Q4")
## Horizontes
yrs = 2008:2019Entonces la próxima vez que querás correr todo el análisis solo debemos modificar estos parámetros sin leer todo el código corriendo el riesgo de romper algo.
Descargar / Improtar los Datos
Para nuestro Escáner Simple elegiremos:
- Ingreso Neto
- Acciones
- Precio
Usamos simfinR_get_fin_statements() para descargar el P/L Statement.
# Descargar Datos de Informes Financieros
stock_pl <- simfinR_get_fin_statements(
id_companies = stock,
api_key = my_api_key,
type_statements = statements,
periods = period,
years = yrs)
head(stock_pl)## # A tibble: 6 x 13
## company_name company_sector type_statement period year ref_date acc_name
## <chr> <chr> <fct> <fct> <int> <date> <chr>
## 1 APPLE INC Computer Hard~ pl Q3 2008 2008-09-30 Revenue
## 2 APPLE INC Computer Hard~ pl Q3 2008 2008-09-30 Sales &~
## 3 APPLE INC Computer Hard~ pl Q3 2008 2008-09-30 Financi~
## 4 APPLE INC Computer Hard~ pl Q3 2008 2008-09-30 Other R~
## 5 APPLE INC Computer Hard~ pl Q3 2008 2008-09-30 Cost of~
## 6 APPLE INC Computer Hard~ pl Q3 2008 2008-09-30 Cost of~
## # ... with 6 more variables: acc_value <dbl>, tid <chr>, uid <chr>,
## # parent_tid <chr>, display_level <chr>, check_possible <lgl>
## # A tibble: 6 x 13
## company_name company_sector type_statement period year ref_date acc_name
## <chr> <chr> <fct> <fct> <int> <date> <chr>
## 1 APPLE INC Computer Hard~ pl Q4 2019 2019-12-31 Income ~
## 2 APPLE INC Computer Hard~ pl Q4 2019 2019-12-31 Minorit~
## 3 APPLE INC Computer Hard~ pl Q4 2019 2019-12-31 Net Inc~
## 4 APPLE INC Computer Hard~ pl Q4 2019 2019-12-31 Preferr~
## 5 APPLE INC Computer Hard~ pl Q4 2019 2019-12-31 Other A~
## 6 APPLE INC Computer Hard~ pl Q4 2019 2019-12-31 Net Inc~
## # ... with 6 more variables: acc_value <dbl>, tid <chr>, uid <chr>,
## # parent_tid <chr>, display_level <chr>, check_possible <lgl>
Para buscar la cantidad de acciones sueltas en el mercado usaremos los paquetes httr y jsonlite. Si bien son temas “avanzados” quería mostrar que no es tan difícil y segundo que R puede trabajar con la web (contrario a lo que los fans de Python suelen creer).
# Descargar Datos de la cantidad de acciones
url_stock <- paste("https://simfin.com/api/v1/companies/id/", stock, "/shares/aggregated", sep = "")
url <- paste(url_stock, "?api-key=",
my_api_key,
"&type=","common",
"$measure", "period",
"&ptype=", "TTM",
"&fyear=", 2019,
sep = "")
get_data <- httr::GET(url)
stock_shares <- jsonlite::fromJSON(content(get_data, "text"), flatten = TRUE)
head(stock_shares)## figure type measure date period fyear value
## 1 common-outstanding common point-in-time 2020-07-17 <NA> <NA> 17102536000
## 2 common-outstanding common point-in-time 2020-06-27 <NA> <NA> 17135756000
## 3 common-outstanding common point-in-time 2020-04-17 <NA> <NA> 17337340000
## 4 common-outstanding common point-in-time 2020-03-28 <NA> <NA> 17295948000
## 5 common-outstanding common point-in-time 2020-01-17 <NA> <NA> 17501920000
## 6 common-outstanding common point-in-time 2019-12-28 <NA> <NA> 17539836000
Por último descargamos el precio de la acción:
# Descargar Datos Precio
stock_price <- simfinR_get_price_data(id_companies = stock,
api_key = my_api_key)
head(stock_price)## ref_date close_adj split_coef share_class_id share_class_name
## 1 2020-09-11 112.00 <NA> 824449 Common Shares
## 2 2020-09-10 113.49 <NA> 824449 Common Shares
## 3 2020-09-09 117.32 <NA> 824449 Common Shares
## 4 2020-09-08 112.82 <NA> 824449 Common Shares
## 5 2020-09-04 120.96 <NA> 824449 Common Shares
## 6 2020-09-03 120.88 <NA> 824449 Common Shares
## share_class_type currency company_name
## 1 common USD APPLE INC
## 2 common USD APPLE INC
## 3 common USD APPLE INC
## 4 common USD APPLE INC
## 5 common USD APPLE INC
## 6 common USD APPLE INC
Limpiar y ordenar los Datos
Como mencionamos antes, todavía no estamos listos para realizar ningún análisis. Ahora estructuraremos los datos en modo que podamos trabajar con ellos, sea filtrando aquello que nos sirve como dandole la forma más adecuada para realizar cálculos.
Corroboramos que nuestra cuenta, Net Income este presente en los datos. Para ahorrar espacio usamos tail() pero para ver el elenco completo basta solo quitarla.
## Verificar que cuentas contiene el informe financiero
tail(unique(stock_pl$acc_name)) # 55 es Net Income## [1] "Income (Loss) Including Minority Interest"
## [2] "Minority Interest"
## [3] "Net Income"
## [4] "Preferred Dividends"
## [5] "Other Adjustments"
## [6] "Net Income Available to Common Shareholders"
Para acotar nuestro data frame:
- filtramos la cuenta “Net Income” de la columna acc_name y omitimos los valores nulos.
- elegimos las columnas ref_date, acc_name y acc_value
- renombramos las columnas
# Preparar df para Net Income
net_income <- stock_pl %>% filter(acc_name == "Net Income",
acc_value != 0) %>%
select(ref_date, acc_name, acc_value) %>%
spread(acc_name, acc_value) %>%
rename(date = ref_date,
net_income = "Net Income")
head(net_income)## # A tibble: 6 x 2
## date net_income
## <date> <dbl>
## 1 2008-09-30 1072000000
## 2 2008-12-31 2421000000
## 3 2009-03-31 2255000000
## 4 2009-06-30 1620000000
## 5 2009-09-30 1828000000
## 6 2009-12-31 2532000000
Nuestra segundo elección para el Escáner es:
- tomar los últimos 12 meses (Last Twelve Months = ltm)
- anualizar los retornos de cada trimestre
Para ello hacemos los calculos utilizando lag() para tomar los periodos anteriores y mutate() para guardar los resultados en nuevas columnas.
# Calcular el retorno anual y el retorno anualizado de cada trimestre
net_income %<>% mutate(ltm_net = net_income +
lag(net_income, 1) +
lag(net_income, 2) +
lag(net_income, 3),
annual_net = net_income * 4) %>%
mutate(ltm_net = ifelse(is.na(ltm_net), net_income * 4, ltm_net))
head(net_income)## # A tibble: 6 x 4
## date net_income ltm_net annual_net
## <date> <dbl> <dbl> <dbl>
## 1 2008-09-30 1072000000 4288000000 4288000000
## 2 2008-12-31 2421000000 9684000000 9684000000
## 3 2009-03-31 2255000000 9020000000 9020000000
## 4 2009-06-30 1620000000 7368000000 6480000000
## 5 2009-09-30 1828000000 8124000000 7312000000
## 6 2009-12-31 2532000000 8235000000 10128000000
Luego hacemos lo mismo para las acciones y el precio:
# Cantidad de Acciones
dil_shares <- stock_shares %>%
filter(figure == "common-outstanding-diluted",
period %in% c("Q1", "Q2", "Q3", "Q4")) %>%
select(date, figure, value) %>%
spread(figure, value) %>%
rename(shares = "common-outstanding-diluted") %>%
mutate(date = ymd(date),
shares = as.numeric(shares))
head(dil_shares)## date shares
## 1 2008-06-30 25288676000
## 2 2008-09-30 25353216000
## 3 2008-12-31 25241832000
## 4 2009-03-31 25283804000
## 5 2009-06-30 25456480000
## 6 2009-09-30 25602444000
# Precio
price <- stock_price %>%
select(ref_date, close_adj) %>%
rename(date = "ref_date",
price = "close_adj") %>%
arrange(date) %>%
filter(date >= "2009-03-31")
head(price)## date price
## 1 2009-03-31 3.24
## 2 2009-04-01 3.35
## 3 2009-04-02 3.48
## 4 2009-04-03 3.58
## 5 2009-04-06 3.66
## 6 2009-04-07 3.55
Se puede observar como redujimos el raw dataset y formatamos las tres variables de la misma manera, con la columna date en común para utilizar como nexo entre los tres data frames. Dado el descalabro reciente en el la bolsa y sobre todo en las grandes empresas TECH, focalizaremos el análisis hasta fines de 2019.
# Creamos el data frame unico
df <- net_income %>%
left_join(dil_shares, by = "date")
df <- price %>%
left_join(df, by = "date") %>%
na.locf() %>% # elimina los valarores faltantes (NA)
# realizamos los cálculos
mutate(ltm_eps = ltm_net/shares,
annl_eps = annual_net/shares,
pe_ltm = price/ltm_eps,
pe_annl = price/annl_eps)
df %<>% filter(date < "2020-01-01")
head(df)## date price net_income ltm_net annual_net shares ltm_eps
## 1 2009-03-31 3.24 2.255e+09 9.02e+09 9.02e+09 25283804000 0.3567501
## 2 2009-04-01 3.35 2.255e+09 9.02e+09 9.02e+09 25283804000 0.3567501
## 3 2009-04-02 3.48 2.255e+09 9.02e+09 9.02e+09 25283804000 0.3567501
## 4 2009-04-03 3.58 2.255e+09 9.02e+09 9.02e+09 25283804000 0.3567501
## 5 2009-04-06 3.66 2.255e+09 9.02e+09 9.02e+09 25283804000 0.3567501
## 6 2009-04-07 3.55 2.255e+09 9.02e+09 9.02e+09 25283804000 0.3567501
## annl_eps pe_ltm pe_annl
## 1 0.3567501 9.081987 9.081987
## 2 0.3567501 9.390326 9.390326
## 3 0.3567501 9.754727 9.754727
## 4 0.3567501 10.035035 10.035035
## 5 0.3567501 10.259282 10.259282
## 6 0.3567501 9.950943 9.950943
Reflexiones Preliminares
En esta primera parte del trabajo pudimos ver que realizar un script tiene varias ventajas:
- Facilmente Automatizable
- Documentación del procedimiento
- Manipulaicón de datos eficaz
- Centralización del trabajo
A mi parecer, de sólo pensar tener que realizar estos pasos en una hója de cálculos me quita las ganas de hacerlo. Pero sobre todo, la parametización del trabajo cambia la cadena de “Pivot Tables” o infinidad de “index + match” que ocupan lugar en las celdas para realizar cálculos, la condesanción de todo el proceso en pocas líneas de código ayuda a encontrar errores, mejorar el procedimiento y ESCALAR EL TRABAJO.
Visualización
Si hay un motivo por el cual R es conocido es por sus motores gráficos. Además de ser bellos, su versatilidad ayuda a realizar todo tipo de análisis exploratorio o crear un buen Stoy Telling.
Para empezar lo fundamental es ver el recorrido que ha tenido su precio.
# Precio
df %>%
ggplot(aes(date)) +
geom_line(aes(y = price), color = "#C73866") +
labs(title = "Precio Apple",
x = "",
y = "Price (US$)") Pero como bien sabemos poco nos dice el precio actual y pasado sobre su performance futura. Entonces representaremos el LTM - P/E, es decir, el multiplo del precio en base a los ingresos netos de los últimos doce meses que calculamos anteriormente.
# Multiplo
df %>%
ggplot(aes(date)) +
geom_line(aes(y = pe_ltm), color = "#fba29d") +
labs(title = "Múltiplo LTM-P/E Apple",
x = "",
y = "LTM P/E (x)") Podemos observar que, salvo el descalabro reciente, el ratio promedia aproximadamente 16x, oscilando entre 10 y 20.
# Tabla Multiplo LTM-P/E
df %>%
summarise(Average = round(mean(pe_ltm),1),
"Std. deviation" = round(sd(pe_ltm),1),
Minimum = round(min(pe_ltm),1),
Maximum = round(max(pe_ltm),1)) %>%
knitr::kable(caption = "Resumen Estadístico Apple LTM-P/E")| Average | Std. deviation | Minimum | Maximum |
|---|---|---|---|
| 15.9 | 4.2 | 7.8 | 25.9 |
Ahora sí estamos en grado de colocar ambos gráficos a la par y observar como relacionan los ciclos.
# Confrontación Precio y LTM-P/E
df %>%
ggplot(aes(date)) +
geom_line(aes(y = pe_ltm), color = "#fba29d") +
geom_line(aes(y = price), color = "#C73866") +
labs(title = "Precio vs Múltiplo LTM-P/E Apple",
x = "",
y = "")Queda bastante en evidencia que a pesar de que el precio parecía desorbitante, enrealidad iba siempre de la mano de los ingresos. Otra medida y tal vez más informativa es el histograma, para darnos una idea de la frecuencia de los distintos nivels del múltiplo. Para ello superpondremos una distribución normal.
# Multiplo histograma
df %>%
ggplot(aes(pe_ltm)) +
geom_histogram(aes(y = ..density..),
bins = 50,
fill = "#C2649A",
color = "white") +
stat_function(fun = dnorm,
args = list(mean = mean(df$pe_ltm),
sd = sd(df$pe_ltm)),
lwd = 1.25,
alpha = 0.7,
color = "#FFBD71") +
labs(title = "Histograma Apple multiplo LTM-P/E",
x = "",
y = "Density")Para empezar, la distribución de nuestro LTM-P/E no es normal. Lo principal acá es tener esto en cuento a la hora de modelizar, ya que la normalidad es una asunción presente en gran parte de los tests y modelos.
Modelando
Quisieramos con todos estos datos ya procesados crear algún mecanismo para tomar decisiones. Salvando las distancias ya discutidas, crearemos un modelo lineal para averiguar si la valuación tiene algún efecto en el precio de la acción.
Próximo paso será crear un data frame con los retornos (ganancia/pérdida de tener la acción, no los ingresos de Apple) a 1, 3, 6 y 12 meses. Así como en pasado usamos la función lag() ahora analogamente utilizaremos lead(). Agregaremos los resultado al final de nuestro df pero lo guardaremos en uno nuevo llamado mult_mod. Recomiendo hacerlo sobre todo cuando los data sets tienen usos distintos.
# Crear el data frame
mult_mod <- df %>%
mutate(ret_1m = lead(price,22)/price - 1,
ret_3m = lead(price, 66)/price -1,
ret_6m = lead(price, 132)/price - 1,
ret_12m = lead(price, 252)/price - 1)La segunda parte para crear nuestro modelo es algo avanzada desde la programación, lo importante es la intución del poder de los loops.
Desde lo estadístico regresaremos los distintos retornos contra el múltiplo de valuación LTM-P/E con el que hemos estado trabajando.
# Correr el modelo
model_list <- list()
y_vars <- c("ret_1m", "ret_3m", "ret_6m", "ret_12m")
for(vars in y_vars){
forms <- as.formula(paste(vars, "pe_ltm", sep = " ~ "))
model_list[[vars]] <- lm(forms, mult_mod)
}
# Produce results
mod_out <- data.frame(mods = y_vars,
size_eff = rep(0,4),
p_vals = rep(0,4),
rsqs = rep(0,4))
for(i in 1:length(model_list)){
mod_out[i,2] <- as.numeric(summary(model_list[[i]])$coeff[2,1])
mod_out[i,3] <- as.numeric(summary(model_list[[i]])$coeff[2,4])
mod_out[i,4] <- as.numeric(summary(model_list[[i]])$r.squared)
}Los resultados de la regresión lineal son los siguientes:
mod_out %>%
mutate(mods = case_when(mods == "ret_1m" ~ "One-month",
mods == "ret_3m" ~ "Three-month",
mods == "ret_6m" ~ "Six-month",
mods == "ret_12m" ~ "One-year"),
size_eff = round(size_eff, 3),
p_vals = format(round(p_vals,3), nsmall = 3),
rsqs = round(rsqs,3)*100) %>%
rename("Models" = mods,
"Size effect" = size_eff,
"P value" = p_vals,
"R-squared (%)" = rsqs) %>%
knitr::kable(caption = "Resultado del Modelo",
align = c("l", "r", "r", "r"))| Models | Size effect | P value | R-squared (%) |
|---|---|---|---|
| One-month | -0.002 | 0.000 | 1.4 |
| Three-month | -0.008 | 0.000 | 6.2 |
| Six-month | -0.016 | 0.000 | 11.8 |
| One-year | -0.027 | 0.000 | 13.2 |
Antes de mirar el R2 y descartar todo, algo de sentido encontramos en la relación inversa entre el precio y el ratio (size effect of the ratio en inglés), ya que por cada punto que sube, la acción se vuelve relativamente más costosa en relación a su valor. Y los p-value son practicamente 0, es decir que la posibilidad de que los valores no son diferentes a valores aleatorios es menor al 5%.
Igualmente no es sorprendente que el R2 sea bajo, he hecho es reconfortante ya que es en este tipo de análisis cuando es muy bueno y relativamente simple…suele haber algún error.
Dado neustro primer insight, probemos a montar el modelo completo ahora. Usando la valuación para predecir el precio. Nos sirve entonves dividir nuestros datos disponibles y reservar algunos para entrenar y otros para testear a nuestro modelo.
Elegimos, arbitrareamente 2009-2015 para entrenar y 2016-2019 para testear el modelo entonces. Sin hacer ningún tipo de time-series cross-validation para manternlo simple el modelo es el siguiente:
# modelo predictivo: determinar los periodos
train <- mult_mod %>% filter(date < "2016-01-01")
test <- mult_mod %>% filter(date >= "2016-01-01", date <= "2018-12-31")
# RMSE (root-mean-squared-error) del modelo entrenado
train_mods <- data.frame(mods = y_vars, rmsqs = rep(0,4))
for(i in 1:length(y_vars)){
forms <- as.formula(paste(y_vars[i], "pe_ltm", sep = " ~ "))
lm_mod <- lm(forms, train)
lm_pred <- predict(lm_mod, train)
train_mods[i,2] <- sqrt(mean((lm_pred - train[,y_vars[i]])^2, na.rm = TRUE))
}
# RMSE en la prueba (tests)
test_mods <- data.frame(mods = y_vars, rmsqs = rep(0,4))
for(i in 1:length(y_vars)){
forms <- as.formula(paste(y_vars[i], "pe_ltm", sep = " ~ "))
lm_mod <- lm(forms, train)
lm_pred <- predict(lm_mod, test)
test_mods[i,2] <- sqrt(mean((lm_pred - test[,y_vars[i]])^2, na.rm = TRUE))
}Veamos los resultados de nuestro modelo:
# Graph train and test RMSE
train_graph <- train_mods %>%
mutate(mods = case_when(mods == "ret_1m" ~ "1-m",
mods == "ret_3m" ~ "3-m",
mods == "ret_6m" ~ "6-m",
mods == "ret_12m" ~ "12-m"),
rmsqs = round(rmsqs, 3)*100) %>%
ggplot(aes(reorder(mods,rmsqs), rmsqs)) +
geom_bar(stat = "identity", fill = "#6AAB9C", color = "#6AAB9C") +
labs(title = "RMSE M. Entrenado",
x = "",
y = "RMSE (%)") +
scale_y_continuous(limits = c(0,40)) +
geom_text(aes(y = rmsqs, label = rmsqs),
nudge_y = 2,
size = 3.5)
test_graph <- test_mods %>%
mutate(mods = case_when(mods == "ret_1m" ~ "1-m",
mods == "ret_3m" ~ "3-m",
mods == "ret_6m" ~ "6-m",
mods == "ret_12m" ~ "12-m"),
rmsqs = round(rmsqs, 3)*100) %>%
ggplot(aes(reorder(mods,rmsqs), rmsqs)) +
geom_bar(stat = "identity", fill = "#FFBD71", color = "#FFBD71") +
labs(title = "RMSE Set de Test",
x = "",
y = "") +
scale_y_continuous(limits = c(0,40)) +
geom_text(aes(y = rmsqs, label = rmsqs),
nudge_y = 2,
size = 3.5)
gridExtra::grid.arrange(train_graph, test_graph, nrow = 1)Antes de seguir con algunas explicaciones, podemos decir que el modelo funcionó bien en el test ya que el error es menor. Otro detalle a tener en cuenta es que a mayor horizonte temporal mayor el error, lo cuál es lógico ya que l señal pierde fuerza en el tiempo. Pero a no emocionarse, si observamos el periodo de tiempo del test (elegido arbitrariamente, si claro…) la correlación es más mayor ahí que en el tiempo de entrenamiento.
Conclusión
LLegado el final pudimos observar el proceso completo, de la recopilación de los datos al modelo de decisión con R. Es claro que no es una tarea extremadamente sencilla, pero vale la pena animarse y si sirve de motivación para otros develar que se puede hacer análisis avanzado de forma automática desde tu casa sin gastar fortunas.
R es gratuito, aprendiendo a utilizar algunas herramientas al final solo se debe pagar por los datos, y ni siquiera tanto gracias a la creciente competencia. A mi me motivó para empezar el hecho que es facilmente reproducible y escalable, entonces si a este código quiero sumar más tests o observaciones no tengo que empezar de 0. Ni hablar de la velocidad de ejecución y capacidad de manejo de bases de datos mayores, pero un paso a la vez.
El Primero de muchos…espero
Para matenerte actualizado, suscribíte a mi newsletter desde mi sitio web o seguíme en twitter: