library(readxl)
library(dplyr)
library(lubridate) #Usamos lubridate para asegurar que la fecha este de forma crónologica y no como texto
library(stringr)
library(janitor) #Usamos Janitor para normalizar los nombres de las columnasTaller 1 ADFG
Objetivo
Este documento detalla el proceso de para la creación de un dataset financiero unificado. El objetivo es consolidar múltiples fuentes de datos de acciones, tratar valores faltantes y estandarizar las divisas.
Contexto
Como equipo, optamos por la ruta ‘A’, consistente en el seguimiento de los precios diarios de activos financieros. Específicamente, seleccionamos empresas del sector retail enfocadas en el cuidado del hogar y personal: Colgate, Kenvue, Unilever, P&G, Reckitt, Essity y Kimberly Clark. La fuente de información utilizada fue la plataforma Refinitiv, extrayendo el valor de las acciones para el periodo comprendido entre el 1 de enero de 2021 y el 31 de diciembre de 2025. Cabe señalar que, en el caso de Kenvue, los registros solo están disponibles a partir de 2023; el tratamiento dado a estos datos faltantes se explicará más adelante.
Diccionario
-Nombres: Colgate, Kenvue, Unilever, P&G, Reckitt, Essity y Kimberly Clark.
-Descripción: Empresas del sector retail (Enfocadas en Cuidado del Hogar y Personal)
-Unidad: Dólar estadounidense (USD), con conversiones para activos en otras divisas
-Frecuencia: Diario (comprendido entre 1 de enero 2021 - 31 de diciembre 2026)
-Llave: Fecha
Obtención de Datos
Los datos fueron obtenidos de la herramienta Refinitiv. Decidimos ir por la ruta A y descargar datos de las acciones de empresas manufactureras.Hicimos una revisión individual a los archivos para entender sus formatos, las fechas que tenian datos disponibles,etc.
Entorno
Para garantizar la reproducibilidad y el manejo de datos, cargamos el ecosistema tidyverse y herramientas específicas para la limpieza de nombres y manejo de fechas.
Carga de Datos
En esta etapa, automatizamos la lectura de múltiples archivos Excel guardados en una carpeta dentro del PC. Usamos la función read_one para extraer el nombre de la empresa del nombre del archivo y estandarizamos las columnas a date y ticker.
data_dir <- "C:/Users/sebas/Downloads/Taller 1 Prueba ADFG"
files <- list.files(data_dir, pattern = "\\.xlsx$", full.names = TRUE)
read_one <- function(path) {
ticker <- tools::file_path_sans_ext(basename(path)) |>
str_remove("^Acciones\\s*") |>
str_replace_all("\\s+", "_") |>
tolower()
df <- read_excel(path) |>
clean_names() |>
select(1:2)
names(df) <- c("date", ticker)
df$date <- as.Date(df$date)
df
}
dfs <- setNames(lapply(files, read_one),
nm = tools::file_path_sans_ext(basename(files)))
# Ver qué cargó
lapply(dfs, nrow) #Aqui confirmamos que se han cargado los archivos de nuestra carpeta y revisamos que el número de filas sea el correcto.$`Acciones Colgate`
[1] 1255
$`Acciones Essity`
[1] 1257
$`Acciones Kenvue`
[1] 668
$`Acciones Kimberly Clark`
[1] 1255
$`Acciones P&G`
[1] 1255
$`Acciones Reckit`
[1] 1285
$`Acciones Unilever`
[1] 1285
Antes de unificar la información, debemos garantizar la integridad de los datos. Con esto nos referimos a que no existan registros duplicados para una misma fecha en los archivos individuales
library(dplyr)
library(purrr)
#| warning: false
#| echo: true
#La función fix_dupes identifica la columna de interés(En este caso la fecha)las agrupa y en caso de existir duplicidad, selecciona el último registro válido (no nulo) disponible
fix_dupes <- function(df) {
value_col <- setdiff(names(df), "date")[1] # la columna de la empresa
df %>%
arrange(date) %>%
group_by(date) %>%
summarise(
"{value_col}" := dplyr::last(na.omit(.data[[value_col]])),
.groups = "drop"
)
}
dfs_clean <- lapply(dfs, fix_dupes)Después de confirmar que los archivos individuales esten “Limpios”, procedemos a unirlos por medio de Full_join. Adicional nos preguntamos cuantos faltantes identificamos por empresa.
df_prices <- reduce(dfs_clean, full_join, by = "date") %>%
arrange(date)
head(df_prices, 10)
# Revisemos faltantes por empresa|
sapply(df_prices, function(x) sum(is.na(x)))# A tibble: 10 × 8
date colgate essity kenvue kimberly_clark `p&g` reckit unilever
<date> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 2021-01-04 84.3 268. NA 133. 138. 6794. 4779.
2 2021-01-05 84.5 265. NA 134. 139. 6718. 4752.
3 2021-01-06 82.2 NA NA 133. 140. 6746. 4785.
4 2021-01-07 81.8 264. NA 132. 139. 6678. 4764.
5 2021-01-08 82.4 264. NA 132. 139. 6670. 4767.
6 2021-01-11 82.1 260. NA 131. 138. 6566. 4730.
7 2021-01-12 82.3 262. NA 130. 137. 6367. 4629.
8 2021-01-13 82.5 266. NA 132. 137. 6317. 4662.
9 2021-01-14 81.9 270. NA 132. 136. 6335. 4664.
10 2021-01-15 81.9 265. NA 131. 135. 6283. 4707.
date colgate essity kenvue kimberly_clark
0 37 35 624 37
p&g reckit unilever
37 31 31
Faltantes
Después de detectar los datos faltantes y para facilitar su análisis, extrajimos estos registros en un Dataframe independiente, lo que nos ayudó a definir la mejor estrategia de limpieza para cada faltante.
na_rows <- df_prices %>%
dplyr::filter(dplyr::if_any(-date, is.na)) %>%
dplyr::arrange(date)
na_rows <- as.data.frame(na_rows)Para el archivo Kenvue, encontramos que desde el 4 de Enero de 2021 hasta el 3 de Mayo de 2023, no se tienen datos. Para efectos de este análisis, se optó por completar estos faltantes mediante la generación de valores aleatorios, basados en el promedio y la desviación estándar de los datos disponibles.
library(dplyr)
library(lubridate)
set.seed(123)
start <- dmy("04/01/2021")
end <- dmy("03/05/2023")
# Calculamos el promedio y la desviación estandar
kenvue_obs <- df_prices$kenvue[!is.na(df_prices$kenvue)]
mu <- mean(kenvue_obs)
sigma <- sd(kenvue_obs)
# Identificamos las filas del rango que definimos (Solo las NA)
cond <- df_prices$date >= start & df_prices$date <= end & is.na(df_prices$kenvue)
idx <- which(cond)
length(idx)[1] 604
# Generamos valores aleatorios
df_prices$kenvue[idx] <- rnorm(length(idx), mean = mu, sd = sigma)
# Evitamos valores negativos
df_prices$kenvue[idx] <- pmax(df_prices$kenvue[idx], 0)
# Revisamos que en el rango determinado no existen más NA
sum(is.na(df_prices$kenvue) & df_prices$date >= start & df_prices$date <= end)[1] 0
Para los demás datos faltantes, notamos que se pueden deber a que las acciones cotizan en distintas bolsas y no todas cierran los mismos días festivos. Para solucionar esto, aplicamos la técnica LOCF (Last Observation Carried Forward), que básicamente consiste en llenar el espacio vacío usando el precio de cierre del día anterior.
library(dplyr)
library(tidyr)
df_prices <- df_prices %>% arrange(date)
# Revisamos los NA actuales
na_before <- sapply(df_prices %>% select(-date), \(x) sum(is.na(x)))
# Cambiamos el valor de los NA con el resultado del día anterior más cercano
df_prices_filled <- df_prices %>%
tidyr::fill(-date, .direction = "down")
# Revisamos nuevamente los NA para confirmar que se hayan cambiado
na_after <- sapply(df_prices_filled %>% select(-date), \(x) sum(is.na(x)))
na_after colgate essity kenvue kimberly_clark p&g
0 0 0 0 0
reckit unilever
0 0
Conversión de Divisas
Ahora procederemos a la conversión de divisas para Unilever y Reckitt. Como sus precios están expresados en GBX (peniques), los pasaremos a USD tomando la tasa del 7 de marzo de 2026, que sitúa el cambio en 75.08 GBX por cada dólar.
# Convertimos los valores de Reckitt y Unilever que vienen en GBX a USD
rate_gbx_per_usd <- 75.08 # 1 USD = 75.08 GBX => USD = GBX / 75.08
# Ajustamos nombres de columnas
cols_gbx <- intersect(names(df_prices_filled), c("reckitt", "reckit", "unilever"))
if (length(cols_gbx) == 0) {
cols_gbx <- names(df_prices_filled)[grepl("reckitt|reckit|unilever", names(df_prices_filled), ignore.case = TRUE)]
}
cols_gbx [1] "reckit" "unilever"
# Aplicamos la conversión
df_prices_filled <- df_prices_filled %>%
mutate(across(all_of(cols_gbx), ~ .x / rate_gbx_per_usd))
# Imprimimos el DF con los nuevos valores
df_final <- as.data.frame(df_prices_filled)
head(df_final[, c("date", cols_gbx)], 10) date reckit unilever
1 2021-01-04 90.49111 63.64764
2 2021-01-05 89.47735 63.29095
3 2021-01-06 89.85084 63.73324
4 2021-01-07 88.94379 63.44789
5 2021-01-08 88.83708 63.49069
6 2021-01-11 87.44984 63.00559
7 2021-01-12 84.80873 61.65018
8 2021-01-13 84.14179 62.09247
9 2021-01-14 84.38189 62.12101
10 2021-01-15 83.68827 62.69171
De igual manera, realizamos la conversión de divisa para la empresa Essity, cuyos precios están expresados en Coronas Suecas (SEK). Para llevarlos a USD, utilizaremos el tipo de cambio del 7 de marzo de 2026, donde 9.18 SEK equivalen a 1 USD.
# Convertimos los valores de Essity que estan en Coronas Suecas (Sek) en USD
rate_sek_per_usd <- 9.18 # 9.18 SEK = 1 USD => USD = SEK / 9.18
# Detectamps columna Essity
col_essity <- intersect(names(df_prices_filled), c("essity"))
if (length(col_essity) == 0) {
col_essity <- names(df_prices_filled)[grepl("essity", names(df_prices_filled), ignore.case = TRUE)]
}
col_essity [1] "essity"
# Aplicamos la conversión
df_prices_filled <- df_prices_filled %>%
mutate(across(all_of(col_essity), ~ .x / rate_sek_per_usd))
# Imprimimos el DF con los nuevos valores
df_final <- as.data.frame(df_prices_filled)
head(df_final[, c("date", col_essity)], 10) date essity
1 2021-01-04 29.15033
2 2021-01-05 28.87800
3 2021-01-06 28.87800
4 2021-01-07 28.81264
5 2021-01-08 28.79085
6 2021-01-11 28.33333
7 2021-01-12 28.52941
8 2021-01-13 29.00871
9 2021-01-14 29.35730
10 2021-01-15 28.89978
DATASET FINAL
Como resultado final, obtenemos un Dataset consolidado, libre de valores faltantes y con todos los precios estandarizados en una misma divisa (USD).
# --- DATASET FINAL (sin NA y en USD) ---
# Hacemos una última revisión de faltantes
na_by_company_final <- colSums(is.na(df_prices_filled %>% dplyr::select(-date)))
na_total_final <- sum(na_by_company_final)
na_by_company_final colgate essity kenvue kimberly_clark p&g
0 0 0 0 0
reckit unilever
0 0
na_total_final[1] 0
stopifnot(na_total_final == 0) # falla si todavía queda algún NA
# Dataset Final
df_final_usd <- as.data.frame(df_prices_filled)
dim(df_final_usd)[1] 1292 8
# Podemos ver el DF completo en el ambiente de R para confirmar que todo quedo bien.
View(df_final_usd)
# Descargamos el archivo en formato CSV para usarlo como base en el Taller #2.
write.csv(df_final_usd,
"C:/Users/sebas/Downloads/Taller 2/dataset_final_usd.csv",
row.names = FALSE)