1. PREPARACIÓN DEL ENTORNO

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.

2. SELECCIÓN DE COLUMNAS

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

3. PIVOTADO Y FORMATO DE PRECISIÓN

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

4. DIAGNÓSTICO DE VALORES FALTANTES

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

5. MISSING CONTROLADO

5.1 Conteo de NAs (diagnóstico previo)

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

5.2 Decisión: Eliminación de filas con NA

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

5.3 Verificación post-eliminación

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

5.4 Visualización comparativa

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

6. RETORNOS DIARIOS — DISPERSIÓN E INCERTIDUMBRE

6.1 Cálculo de retornos diarios

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)
Primeros retornos diarios calculados
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

6.2 Resumen estadístico de dispersión por Ticker

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

6.3 Visualización — Boxplot de dispersión

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

6.4 Visualización — Densidad de retornos (incertidumbre)

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

6.5 Visualización — Volatilidad (Desviación estándar)

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

7. DICCIONARIO DE VARIABLES

Esta sección documenta todas las variables utilizadas en el análisis, organizadas por sección del código.