Primero cargamos las librerias necesarias y nuestra base de datos original.
library(readr)
library(dplyr)
library(tidyr)
library(ggplot2)
library(DT)
library(scales)
Cargamos el dataset final del Taller 1.
sp500_raw <- read_csv("sp500_top10_stocks_clean.csv")
## Rows: 35765 Columns: 8
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr (1): Ticker
## dbl (6): Open, High, Low, Close, Adj_Close, Volume
## date (1): Date
##
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
Para nuestro diagnóstico financiero, nos centraremos exclusivamente en los precios de cierre diarios para cada uno de los Tickers.
sp500_reducido <- sp500_raw %>%
select(Date, Ticker, Close)
Mostramos las primeras filas para verificar la limpieza.
knitr::kable(head(sp500_reducido), caption = "Datos filtrados (Formato Largo)")
| Date | Ticker | Close |
|---|---|---|
| 2010-01-04 | AAPL | 6.412384 |
| 2010-01-04 | AMZN | 6.695000 |
| 2010-01-04 | AVGO | 1.328563 |
| 2010-01-04 | GOOG | 15.494032 |
| 2010-01-04 | GOOGL | 15.566814 |
| 2010-01-04 | MSFT | 23.130056 |
Transformamos la base para comparar los activos cara a cara. Además, redondeamos a dos decimales para mantener la sobriedad financiera.
sp500_ancho <- sp500_reducido %>%
pivot_wider(names_from = Ticker, values_from = Close) %>%
mutate(across(where(is.numeric), ~ round(., 2)))
Mostramos el resultado de la transposición y el redondeo.
knitr::kable(head(sp500_ancho), caption = "Datos pivoteados y redondeados (Formato Ancho)")
| Date | AAPL | AMZN | AVGO | GOOG | GOOGL | MSFT | NVDA | TSLA | META |
|---|---|---|---|---|---|---|---|---|---|
| 2010-01-04 | 6.41 | 6.70 | 1.33 | 15.49 | 15.57 | 23.13 | 0.42 | NA | NA |
| 2010-01-05 | 6.42 | 6.73 | 1.34 | 15.43 | 15.50 | 23.14 | 0.43 | NA | NA |
| 2010-01-06 | 6.32 | 6.61 | 1.35 | 15.04 | 15.11 | 23.00 | 0.43 | NA | NA |
| 2010-01-07 | 6.31 | 6.50 | 1.34 | 14.69 | 14.76 | 22.76 | 0.42 | NA | NA |
| 2010-01-08 | 6.35 | 6.68 | 1.35 | 14.88 | 14.95 | 22.91 | 0.43 | NA | NA |
| 2010-01-11 | 6.30 | 6.52 | 1.36 | 14.86 | 14.93 | 22.62 | 0.42 | NA | NA |
Verificamos si existen valores NA en el dataset después del pivotado.
total_nas <- sum(is.na(sp500_ancho))
cat("Total de NAs en el dataset:", total_nas, "\n")
## Total de NAs en el dataset: 721
# NAs por columna (por cada Ticker)
nas_por_columna <- colSums(is.na(sp500_ancho))
# Mostramos en tabla
knitr::kable(
data.frame(
Ticker = names(nas_por_columna),
NAs = as.integer(nas_por_columna),
Porcentaje = paste0(round(colMeans(is.na(sp500_ancho)) * 100, 2), "%")),
caption = "Conteo de NAs por Ticker")
| Ticker | NAs | Porcentaje |
|---|---|---|
| Date | 0 | 0% |
| AAPL | 0 | 0% |
| AMZN | 0 | 0% |
| AVGO | 0 | 0% |
| GOOG | 0 | 0% |
| GOOGL | 0 | 0% |
| MSFT | 0 | 0% |
| NVDA | 0 | 0% |
| TSLA | 122 | 3.01% |
| META | 599 | 14.78% |
Visualizamos los NAs gráficamente.
nas_df <- data.frame(
Ticker = names(nas_por_columna),
NAs = as.integer(nas_por_columna)
)
ggplot(nas_df, aes(x = reorder(Ticker, -NAs), y = NAs, fill = NAs > 0)) +
geom_col() +
scale_fill_manual(values = c("TRUE" = "#e74c3c", "FALSE" = "#2ecc71"),
labels = c("Sin NAs", "Con NAs")) +
labs(
title = "Valores faltantes por Ticker",
x = "Ticker",
y = "Cantidad de NAs",
fill = ""
) +
theme_minimal()
# Total de NAs
total_nas <- sum(is.na(sp500_ancho))
cat("Total de NAs en el dataset:", total_nas, "\n\n")
## Total de NAs en el dataset: 721
# NAs por columna con porcentaje
nas_resumen <- data.frame(
Ticker = names(colSums(is.na(sp500_ancho))),
NAs = as.integer(colSums(is.na(sp500_ancho))),
Porcentaje = round(colMeans(is.na(sp500_ancho)) * 100, 2)
)
knitr::kable(nas_resumen, caption = "Conteo de NAs por Ticker antes de imputar")
| Ticker | NAs | Porcentaje | |
|---|---|---|---|
| Date | Date | 0 | 0.00 |
| AAPL | AAPL | 0 | 0.00 |
| AMZN | AMZN | 0 | 0.00 |
| AVGO | AVGO | 0 | 0.00 |
| GOOG | GOOG | 0 | 0.00 |
| GOOGL | GOOGL | 0 | 0.00 |
| MSFT | MSFT | 0 | 0.00 |
| NVDA | NVDA | 0 | 0.00 |
| TSLA | TSLA | 122 | 3.01 |
| META | META | 599 | 14.78 |
Se eliminan todas las filas que contengan al menos un valor NA, garantizando que el dataset final esté 100% completo para el cálculo de retornos.
sp500_imputado <- sp500_ancho %>%
drop_na()
# Confirmamos dimensiones antes y después
cat("Filas antes de eliminar NAs:", nrow(sp500_ancho), "\n")
## Filas antes de eliminar NAs: 4054
cat("Filas después de eliminar NAs:", nrow(sp500_imputado), "\n")
## Filas después de eliminar NAs: 3455
cat("Filas eliminadas:", nrow(sp500_ancho) - nrow(sp500_imputado), "\n")
## Filas eliminadas: 599
nas_post <- data.frame(
Ticker = names(colSums(is.na(sp500_ancho))),
NAs_antes = as.integer(colSums(is.na(sp500_ancho))),
NAs_despues = as.integer(colSums(is.na(sp500_imputado))))
knitr::kable(nas_post, caption = "Comparativo de NAs antes y después de eliminar")
| Ticker | NAs_antes | NAs_despues |
|---|---|---|
| Date | 0 | 0 |
| AAPL | 0 | 0 |
| AMZN | 0 | 0 |
| AVGO | 0 | 0 |
| GOOG | 0 | 0 |
| GOOGL | 0 | 0 |
| MSFT | 0 | 0 |
| NVDA | 0 | 0 |
| TSLA | 122 | 0 |
| META | 599 | 0 |
nas_df <- nas_resumen %>% filter(Ticker != "Date")
ggplot(nas_df, aes(x = reorder(Ticker, -NAs), y = NAs, fill = NAs > 0)) +
geom_col() +
scale_fill_manual(values = c("TRUE" = "#e74c3c", "FALSE" = "#2ecc71"),
labels = c("Sin NAs", "Con NAs")) +
labs(
title = "NAs eliminados por Ticker",
subtitle = paste("Total de filas eliminadas:",
nrow(sp500_ancho) - nrow(sp500_imputado)),
x = "Ticker",
y = "Cantidad de NAs",
fill = ""
) +
theme_minimal()
Los retornos diarios se calculan como la variación porcentual del precio de cierre respecto al día anterior: R = (Pt - Pt-1) / Pt-1 × 100
library(dplyr)
library(tidyr)
library(ggplot2)
# Calculamos retornos diarios en formato largo
sp500_retornos <- sp500_imputado %>%
pivot_longer(cols = -Date, names_to = "Ticker", values_to = "Precio") %>%
arrange(Ticker, Date) %>%
group_by(Ticker) %>%
mutate(Retorno = (Precio - lag(Precio)) / lag(Precio) * 100) %>%
filter(!is.na(Retorno)) %>%
ungroup()
knitr::kable(head(sp500_retornos, 10),
caption = "Primeros retornos diarios calculados",
digits = 4)
| Date | Ticker | Precio | Retorno |
|---|---|---|---|
| 2012-05-21 | AAPL | 16.82 | 5.8527 |
| 2012-05-22 | AAPL | 16.69 | -0.7729 |
| 2012-05-23 | AAPL | 17.10 | 2.4566 |
| 2012-05-24 | AAPL | 16.94 | -0.9357 |
| 2012-05-25 | AAPL | 16.85 | -0.5313 |
| 2012-05-29 | AAPL | 17.15 | 1.7804 |
| 2012-05-30 | AAPL | 17.35 | 1.1662 |
| 2012-05-31 | AAPL | 17.31 | -0.2305 |
| 2012-06-01 | AAPL | 16.81 | -2.8885 |
| 2012-06-04 | AAPL | 16.91 | 0.5949 |
resumen_retornos <- sp500_retornos %>%
group_by(Ticker) %>%
summarise(
Media = round(mean(Retorno), 4),
Mediana = round(median(Retorno), 4),
Desv_Std = round(sd(Retorno), 4),
Min = round(min(Retorno), 4),
Max = round(max(Retorno), 4),
Rango = round(max(Retorno) - min(Retorno), 4)
) %>%
arrange(desc(Desv_Std))
knitr::kable(resumen_retornos,
caption = "Dispersión e incertidumbre por Ticker (Retornos Diarios %)")
| Ticker | Media | Mediana | Desv_Std | Min | Max | Rango |
|---|---|---|---|---|---|---|
| TSLA | 0.2221 | 0.1250 | 3.6172 | -21.0628 | 24.4624 | 45.5252 |
| NVDA | 0.2295 | 0.0000 | 2.9019 | -18.7625 | 29.3413 | 48.1038 |
| META | 0.1131 | 0.0936 | 2.5038 | -26.3904 | 29.5860 | 55.9765 |
| AVGO | 0.1738 | 0.1449 | 2.3866 | -19.9007 | 24.4344 | 44.3351 |
| AMZN | 0.1052 | 0.0830 | 2.0294 | -14.0526 | 14.1026 | 28.1551 |
| AAPL | 0.0964 | 0.0878 | 1.7851 | -12.8648 | 15.3317 | 28.1965 |
| GOOGL | 0.1025 | 0.1026 | 1.7352 | -11.6329 | 16.2760 | 27.9089 |
| GOOG | 0.1026 | 0.0956 | 1.7342 | -11.1019 | 16.0528 | 27.1547 |
| MSFT | 0.0961 | 0.0771 | 1.6478 | -14.7401 | 14.2145 | 28.9546 |
El boxplot permite identificar la volatilidad y los valores atípicos por cada Ticker.
ggplot(sp500_retornos, aes(x = reorder(Ticker, Retorno, FUN = sd),
y = Retorno, fill = Ticker)) +
geom_boxplot(outlier.color = "#e74c3c", outlier.size = 1.5, alpha = 0.8) +
geom_hline(yintercept = 0, linetype = "dashed", color = "gray40") +
labs(
title = "Dispersión de Retornos Diarios por Ticker",
subtitle = "Ordenado de menor a mayor volatilidad",
x = "Ticker",
y = "Retorno Diario (%)",
fill = "Ticker"
) +
theme_minimal() +
theme(legend.position = "none")
La curva de densidad muestra la distribución de probabilidad de los retornos, permitiendo detectar asimetría y colas pesadas.
ggplot(sp500_retornos, aes(x = Retorno, fill = Ticker, color = Ticker)) +
geom_density(alpha = 0.3, linewidth = 0.7) +
geom_vline(xintercept = 0, linetype = "dashed", color = "gray40") +
labs(
title = "Distribución de Retornos Diarios por Ticker",
subtitle = "Colas anchas indican mayor incertidumbre",
x = "Retorno Diario (%)",
y = "Densidad",
fill = "Ticker",
color = "Ticker"
) +
theme_minimal()
ggplot(resumen_retornos, aes(x = reorder(Ticker, -Desv_Std),
y = Desv_Std, fill = Desv_Std)) +
geom_col() +
scale_fill_gradient(low = "#2ecc71", high = "#e74c3c") +
labs(
title = "Volatilidad por Ticker (Desviación Estándar de Retornos)",
subtitle = "Mayor valor = mayor incertidumbre",
x = "Ticker",
y = "Desviación Estándar (%)",
fill = "Volatilidad"
) +
theme_minimal()
Esta sección documenta todas las variables utilizadas en el análisis, organizadas por sección del código.