knitr::opts_chunk$set(echo = TRUE)
En este informe, se realizará la extracción de señales o descomposición de series temporales aplicadas a los ingresos totales trimestrales de la empresa Colombina S.A. durante el período comprendido entre el primer trimestre de 2019 (1T2019) y el tercer trimestre de 2024 (3T2024).
El análisis de series temporales permite identificar patrones subyacentes en los datos, como tendencias, estacionalidad y componentes irregulares, lo que facilita una mejor comprensión del comportamiento de la utilidad de la empresa. Para ello, se emplearán técnicas estadísticas y métodos de descomposición, con el fin de extraer información clave que pueda contribuir a la toma de decisiones estratégicas.
A lo largo del informe, se presentarán los resultados obtenidos y se discutirán sus implicaciones en el contexto financiero de Colombina S.A.
knitr::include_graphics("Colombina.jpg")
En este análisis, se realizará un estudio exploratorio de la variable Utilidad de la empresa Colombina. Para ello, se calcularán estadísticas descriptivas que nos permitirán conocer mejor la distribución y principales características de la variable en estudio.
Además, se complementará el análisis con la visualización de gráficos, los cuales facilitarán la identificación de patrones, tendencias y posibles valores atípicos en los datos.
El objetivo de este análisis es obtener una comprensión inicial de la información disponible y generar insights que puedan ser utilizados en estudios posteriores más avanzados.
A continuación, se presenta el desarrollo del análisis.
# Cargar librerías necesarias
library
## function (package, help, pos = 2, lib.loc = NULL, character.only = FALSE,
## logical.return = FALSE, warn.conflicts, quietly = FALSE,
## verbose = getOption("verbose"), mask.ok, exclude, include.only,
## attach.required = missing(include.only))
## {
## conf.ctrl <- getOption("conflicts.policy")
## if (is.character(conf.ctrl))
## conf.ctrl <- switch(conf.ctrl, strict = list(error = TRUE,
## warn = FALSE), depends.ok = list(error = TRUE, generics.ok = TRUE,
## can.mask = c("base", "methods", "utils", "grDevices",
## "graphics", "stats"), depends.ok = TRUE), warning(gettextf("unknown conflict policy: %s",
## sQuote(conf.ctrl)), call. = FALSE, domain = NA))
## if (!is.list(conf.ctrl))
## conf.ctrl <- NULL
## stopOnConflict <- isTRUE(conf.ctrl$error)
## if (missing(warn.conflicts))
## warn.conflicts <- !isFALSE(conf.ctrl$warn)
## if (!missing(include.only) && !missing(exclude))
## stop("only one of 'include.only' and 'exclude' can be used",
## call. = FALSE)
## testRversion <- function(pkgInfo, pkgname, pkgpath) {
## if (is.null(built <- pkgInfo$Built))
## stop(gettextf("package %s has not been installed properly\n",
## sQuote(pkgname)), call. = FALSE, domain = NA)
## R_version_built_under <- as.numeric_version(built$R)
## if (R_version_built_under < "3.0.0")
## stop(gettextf("package %s was built before R 3.0.0: please re-install it",
## sQuote(pkgname)), call. = FALSE, domain = NA)
## current <- getRversion()
## if (length(Rdeps <- pkgInfo$Rdepends2)) {
## for (dep in Rdeps) if (length(dep) > 1L) {
## target <- dep$version
## res <- do.call(dep$op, if (is.character(target))
## list(as.numeric(R.version[["svn rev"]]), as.numeric(sub("^r",
## "", target)))
## else list(current, as.numeric_version(target)))
## if (!res)
## stop(gettextf("This is R %s, package %s needs %s %s",
## current, sQuote(pkgname), dep$op, target),
## call. = FALSE, domain = NA)
## }
## }
## if (R_version_built_under > current)
## warning(gettextf("package %s was built under R version %s",
## sQuote(pkgname), as.character(built$R)), call. = FALSE,
## domain = NA)
## platform <- built$Platform
## r_arch <- .Platform$r_arch
## if (.Platform$OS.type == "unix") {
## }
## else {
## if (nzchar(platform) && !grepl("mingw", platform))
## stop(gettextf("package %s was built for %s",
## sQuote(pkgname), platform), call. = FALSE,
## domain = NA)
## }
## if (nzchar(r_arch) && file.exists(file.path(pkgpath,
## "libs")) && !file.exists(file.path(pkgpath, "libs",
## r_arch)))
## stop(gettextf("package %s is not installed for 'arch = %s'",
## sQuote(pkgname), r_arch), call. = FALSE, domain = NA)
## }
## checkNoGenerics <- function(env, pkg) {
## nenv <- env
## ns <- .getNamespace(as.name(pkg))
## if (!is.null(ns))
## nenv <- asNamespace(ns)
## if (exists(".noGenerics", envir = nenv, inherits = FALSE))
## TRUE
## else {
## !any(startsWith(names(env), ".__T"))
## }
## }
## checkConflicts <- function(package, pkgname, pkgpath, nogenerics,
## env) {
## dont.mind <- c("last.dump", "last.warning", ".Last.value",
## ".Random.seed", ".Last.lib", ".onDetach", ".packageName",
## ".noGenerics", ".required", ".no_S3_generics", ".Depends",
## ".requireCachedGenerics")
## sp <- search()
## lib.pos <- which(sp == pkgname)
## ob <- names(as.environment(lib.pos))
## if (!nogenerics) {
## these <- ob[startsWith(ob, ".__T__")]
## gen <- gsub(".__T__(.*):([^:]+)", "\\1", these)
## from <- gsub(".__T__(.*):([^:]+)", "\\2", these)
## gen <- gen[from != package]
## ob <- ob[!(ob %in% gen)]
## }
## ipos <- seq_along(sp)[-c(lib.pos, match(c("Autoloads",
## "CheckExEnv"), sp, 0L))]
## cpos <- NULL
## conflicts <- vector("list", 0)
## for (i in ipos) {
## obj.same <- match(names(as.environment(i)), ob, nomatch = 0L)
## if (any(obj.same > 0L)) {
## same <- ob[obj.same]
## same <- same[!(same %in% dont.mind)]
## Classobjs <- which(startsWith(same, ".__"))
## if (length(Classobjs))
## same <- same[-Classobjs]
## same.isFn <- function(where) vapply(same, exists,
## NA, where = where, mode = "function", inherits = FALSE)
## same <- same[same.isFn(i) == same.isFn(lib.pos)]
## not.Ident <- function(ch, TRAFO = identity, ...) vapply(ch,
## function(.) !identical(TRAFO(get(., i)), TRAFO(get(.,
## lib.pos)), ...), NA)
## if (length(same))
## same <- same[not.Ident(same)]
## if (length(same) && identical(sp[i], "package:base"))
## same <- same[not.Ident(same, ignore.environment = TRUE)]
## if (length(same)) {
## conflicts[[sp[i]]] <- same
## cpos[sp[i]] <- i
## }
## }
## }
## if (length(conflicts)) {
## if (stopOnConflict) {
## emsg <- ""
## pkg <- names(conflicts)
## notOK <- vector("list", 0)
## for (i in seq_along(conflicts)) {
## pkgname <- sub("^package:", "", pkg[i])
## if (pkgname %in% canMaskEnv$canMask)
## next
## same <- conflicts[[i]]
## if (is.list(mask.ok))
## myMaskOK <- mask.ok[[pkgname]]
## else myMaskOK <- mask.ok
## if (isTRUE(myMaskOK))
## same <- NULL
## else if (is.character(myMaskOK))
## same <- setdiff(same, myMaskOK)
## if (length(same)) {
## notOK[[pkg[i]]] <- same
## msg <- .maskedMsg(sort(same), pkg = sQuote(pkg[i]),
## by = cpos[i] < lib.pos)
## emsg <- paste(emsg, msg, sep = "\n")
## }
## }
## if (length(notOK)) {
## msg <- gettextf("Conflicts attaching package %s:\n%s",
## sQuote(package), emsg)
## stop(errorCondition(msg, package = package,
## conflicts = conflicts, class = "packageConflictError"))
## }
## }
## if (warn.conflicts) {
## packageStartupMessage(gettextf("\nAttaching package: %s\n",
## sQuote(package)), domain = NA)
## pkg <- names(conflicts)
## for (i in seq_along(conflicts)) {
## msg <- .maskedMsg(sort(conflicts[[i]]), pkg = sQuote(pkg[i]),
## by = cpos[i] < lib.pos)
## packageStartupMessage(msg, domain = NA)
## }
## }
## }
## }
## if (verbose && quietly)
## message("'verbose' and 'quietly' are both true; being verbose then ..")
## if (!missing(package)) {
## if (is.null(lib.loc))
## lib.loc <- .libPaths()
## lib.loc <- lib.loc[dir.exists(lib.loc)]
## if (!character.only)
## package <- as.character(substitute(package))
## if (length(package) != 1L)
## stop("'package' must be of length 1")
## if (is.na(package) || (package == ""))
## stop("invalid package name")
## pkgname <- paste0("package:", package)
## newpackage <- is.na(match(pkgname, search()))
## if (newpackage) {
## pkgpath <- find.package(package, lib.loc, quiet = TRUE,
## verbose = verbose)
## if (length(pkgpath) == 0L) {
## if (length(lib.loc) && !logical.return)
## stop(packageNotFoundError(package, lib.loc,
## sys.call()))
## txt <- if (length(lib.loc))
## gettextf("there is no package called %s", sQuote(package))
## else gettext("no library trees found in 'lib.loc'")
## if (logical.return) {
## if (!quietly)
## warning(txt, domain = NA)
## return(FALSE)
## }
## else stop(txt, domain = NA)
## }
## which.lib.loc <- normalizePath(dirname(pkgpath),
## "/", TRUE)
## pfile <- system.file("Meta", "package.rds", package = package,
## lib.loc = which.lib.loc)
## if (!nzchar(pfile))
## stop(gettextf("%s is not a valid installed package",
## sQuote(package)), domain = NA)
## pkgInfo <- readRDS(pfile)
## testRversion(pkgInfo, package, pkgpath)
## if (is.character(pos)) {
## npos <- match(pos, search())
## if (is.na(npos)) {
## warning(gettextf("%s not found on search path, using pos = 2",
## sQuote(pos)), domain = NA)
## pos <- 2
## }
## else pos <- npos
## }
## deps <- unique(names(pkgInfo$Depends))
## depsOK <- isTRUE(conf.ctrl$depends.ok)
## if (depsOK) {
## canMaskEnv <- dynGet("__library_can_mask__",
## NULL)
## if (is.null(canMaskEnv)) {
## canMaskEnv <- new.env()
## canMaskEnv$canMask <- union("base", conf.ctrl$can.mask)
## "__library_can_mask__" <- canMaskEnv
## }
## canMaskEnv$canMask <- unique(c(package, deps,
## canMaskEnv$canMask))
## }
## else canMaskEnv <- NULL
## if (attach.required)
## .getRequiredPackages2(pkgInfo, quietly = quietly,
## lib.loc = c(lib.loc, .libPaths()))
## cr <- conflictRules(package)
## if (missing(mask.ok))
## mask.ok <- cr$mask.ok
## if (missing(exclude))
## exclude <- cr$exclude
## if (isNamespaceLoaded(package)) {
## newversion <- as.numeric_version(pkgInfo$DESCRIPTION["Version"])
## oldversion <- as.numeric_version(getNamespaceVersion(package))
## if (newversion != oldversion) {
## tryCatch(unloadNamespace(package), error = function(e) {
## P <- if (!is.null(cc <- conditionCall(e)))
## paste("Error in", deparse(cc)[1L], ": ")
## else "Error : "
## stop(gettextf("Package %s version %s cannot be unloaded:\n %s",
## sQuote(package), oldversion, paste0(P,
## conditionMessage(e), "\n")), domain = NA)
## })
## }
## }
## tt <- tryCatch({
## attr(package, "LibPath") <- which.lib.loc
## ns <- loadNamespace(package, lib.loc)
## env <- attachNamespace(ns, pos = pos, deps, exclude,
## include.only)
## }, error = function(e) {
## P <- if (!is.null(cc <- conditionCall(e)))
## paste(" in", deparse(cc)[1L])
## else ""
## msg <- gettextf("package or namespace load failed for %s%s:\n %s",
## sQuote(package), P, conditionMessage(e))
## if (logical.return && !quietly)
## message(paste("Error:", msg), domain = NA)
## else stop(msg, call. = FALSE, domain = NA)
## })
## if (logical.return && is.null(tt))
## return(FALSE)
## attr(package, "LibPath") <- NULL
## {
## on.exit(detach(pos = pos))
## nogenerics <- !.isMethodsDispatchOn() || checkNoGenerics(env,
## package)
## if (isFALSE(conf.ctrl$generics.ok) || (stopOnConflict &&
## !isTRUE(conf.ctrl$generics.ok)))
## nogenerics <- TRUE
## if (stopOnConflict || (warn.conflicts && !exists(".conflicts.OK",
## envir = env, inherits = FALSE)))
## checkConflicts(package, pkgname, pkgpath, nogenerics,
## ns)
## on.exit()
## if (logical.return)
## return(TRUE)
## else return(invisible(.packages()))
## }
## }
## if (verbose && !newpackage)
## warning(gettextf("package %s already present in search()",
## sQuote(package)), domain = NA)
## }
## else if (!missing(help)) {
## if (!character.only)
## help <- as.character(substitute(help))
## pkgName <- help[1L]
## pkgPath <- find.package(pkgName, lib.loc, verbose = verbose)
## docFiles <- c(file.path(pkgPath, "Meta", "package.rds"),
## file.path(pkgPath, "INDEX"))
## if (file.exists(vignetteIndexRDS <- file.path(pkgPath,
## "Meta", "vignette.rds")))
## docFiles <- c(docFiles, vignetteIndexRDS)
## pkgInfo <- vector("list", 3L)
## readDocFile <- function(f) {
## if (basename(f) %in% "package.rds") {
## txt <- readRDS(f)$DESCRIPTION
## if ("Encoding" %in% names(txt)) {
## to <- if (Sys.getlocale("LC_CTYPE") == "C")
## "ASCII//TRANSLIT"
## else ""
## tmp <- try(iconv(txt, from = txt["Encoding"],
## to = to))
## if (!inherits(tmp, "try-error"))
## txt <- tmp
## else warning("'DESCRIPTION' has an 'Encoding' field and re-encoding is not possible",
## call. = FALSE)
## }
## nm <- paste0(names(txt), ":")
## formatDL(nm, txt, indent = max(nchar(nm, "w")) +
## 3L)
## }
## else if (basename(f) %in% "vignette.rds") {
## txt <- readRDS(f)
## if (is.data.frame(txt) && nrow(txt))
## cbind(basename(gsub("\\.[[:alpha:]]+$", "",
## txt$File)), paste(txt$Title, paste0(rep.int("(source",
## NROW(txt)), ifelse(nzchar(txt$PDF), ", pdf",
## ""), ")")))
## else NULL
## }
## else readLines(f)
## }
## for (i in which(file.exists(docFiles))) pkgInfo[[i]] <- readDocFile(docFiles[i])
## y <- list(name = pkgName, path = pkgPath, info = pkgInfo)
## class(y) <- "packageInfo"
## return(y)
## }
## else {
## if (is.null(lib.loc))
## lib.loc <- .libPaths()
## db <- matrix(character(), nrow = 0L, ncol = 3L)
## nopkgs <- character()
## for (lib in lib.loc) {
## a <- .packages(all.available = TRUE, lib.loc = lib)
## for (i in sort(a)) {
## file <- system.file("Meta", "package.rds", package = i,
## lib.loc = lib)
## title <- if (nzchar(file)) {
## txt <- readRDS(file)
## if (is.list(txt))
## txt <- txt$DESCRIPTION
## if ("Encoding" %in% names(txt)) {
## to <- if (Sys.getlocale("LC_CTYPE") == "C")
## "ASCII//TRANSLIT"
## else ""
## tmp <- try(iconv(txt, txt["Encoding"], to,
## "?"))
## if (!inherits(tmp, "try-error"))
## txt <- tmp
## else warning("'DESCRIPTION' has an 'Encoding' field and re-encoding is not possible",
## call. = FALSE)
## }
## txt["Title"]
## }
## else NA
## if (is.na(title))
## title <- " ** No title available ** "
## db <- rbind(db, cbind(i, lib, title))
## }
## if (length(a) == 0L)
## nopkgs <- c(nopkgs, lib)
## }
## dimnames(db) <- list(NULL, c("Package", "LibPath", "Title"))
## if (length(nopkgs) && !missing(lib.loc)) {
## pkglist <- paste(sQuote(nopkgs), collapse = ", ")
## msg <- sprintf(ngettext(length(nopkgs), "library %s contains no packages",
## "libraries %s contain no packages"), pkglist)
## warning(msg, domain = NA)
## }
## y <- list(header = NULL, results = db, footer = NULL)
## class(y) <- "libraryIQR"
## return(y)
## }
## if (logical.return)
## TRUE
## else invisible(.packages())
## }
## <bytecode: 0x14abaf850>
## <environment: namespace:base>
library(readxl)
library(dplyr)
##
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
##
## filter, lag
## The following objects are masked from 'package:base':
##
## intersect, setdiff, setequal, union
library(ggplot2)
library(plotly)
##
## Attaching package: 'plotly'
## The following object is masked from 'package:ggplot2':
##
## last_plot
## The following object is masked from 'package:stats':
##
## filter
## The following object is masked from 'package:graphics':
##
## layout
library(tseries)
## Registered S3 method overwritten by 'quantmod':
## method from
## as.zoo.data.frame zoo
library(forecast)
library(timetk)
# cargar datos desde el archivo Excel
data <- read_excel("Colombina EXTRA.xlsx")
#Ver las primeras filas de los datos
class(data)
## [1] "tbl_df" "tbl" "data.frame"
colnames(data)
## [1] "Date" "ING" "UTILIDAD" "ACTIVOS" "PASIVOS"
head(data)
## # A tibble: 6 × 5
## Date ING UTILIDAD ACTIVOS PASIVOS
## <dttm> <dbl> <dbl> <dbl> <dbl>
## 1 2019-03-01 00:00:00 433580000 35320000 1565128000 1391576000
## 2 2019-06-01 00:00:00 462186000 32407000 1571254000 1387456000
## 3 2019-09-01 00:00:00 517285000 44634000 1625282000 1418499000
## 4 2019-12-01 00:00:00 528268000 44063000 1690409000 1457298000
## 5 2020-03-01 00:00:00 498207000 47612000 1816189000 1604851000
## 6 2020-06-01 00:00:00 402864000 -2194000 1727606000 1532106000
ts_data <- ts(data$UTILIDAD, start = c(2019, 1), frequency = 4)
knitr::opts_chunk$set(echo = TRUE)
# Calcular estadísticas descriptivas básicas
descriptive_stats <- data.frame(
Min = min(ts_data),
Max = max(ts_data),
Media = mean(ts_data),
Mediana = median(ts_data),
DesviacionEstandar = sd(ts_data),
CoefVar = sd(ts_data) / mean(ts_data)
)
print(descriptive_stats)
## Min Max Media Mediana DesviacionEstandar CoefVar
## 1 -2194000 105801000 54522478 50231000 27259333 0.499965
Las utilidades trimestrales de la empresa muestran un comportamiento financiero con una notable variabilidad a lo largo del período analizado. En el trimestre con peor desempeño, la empresa registró una pérdida de 2,194,000 COP, mientras que en su mejor momento alcanzó una utilidad de 105,801,000 COP, lo que evidencia una oscilación considerable entre periodos. En promedio, las utilidades trimestrales se sitúan en 54.52 millones de COP, reflejando el desempeño esperado en un trimestre típico. La mediana, de 50.23 millones de COP, indica que la mitad de los periodos analizados se encuentran por debajo de este valor y la otra mitad por encima, sugiriendo que la distribución de las utilidades no presenta un sesgo significativo. Sin embargo, el hecho de que la media sea ligeramente mayor sugiere la presencia de algunos valores altos que elevan el promedio general.
El nivel de dispersión de los datos es considerable, con una desviación estándar de 27.26 millones de COP, lo que indica diferencias marcadas en los resultados de un trimestre a otro. Además, el coeficiente de variación del 49.99% señala una volatilidad moderada-alta en relación con la media, lo que implica que las utilidades de la empresa son inestables y pueden verse afectadas por distintos factores, como fluctuaciones en la demanda, costos operativos o dinámicas del mercado. A pesar de la existencia de pérdidas en algunos trimestres, el balance general sigue siendo positivo, aunque la elevada variabilidad sugiere que sería conveniente implementar estrategias para reducir la incertidumbre y fortalecer la estabilidad financiera.
La evolución de la utilidad trimestral de Colombina S.A. muestra una transformación significativa en su desempeño financiero a lo largo del período analizado. Durante 2019 y principios de 2020, la empresa presentó resultados relativamente estables, con utilidades que fluctuaban entre 30 y 45 millones de pesos colombianos, lo que reflejaba una operación consolidada pero sin un crecimiento destacado. Sin embargo, a mediados de 2020, esta estabilidad se vio abruptamente interrumpida con una caída drástica que llevó las utilidades prácticamente a cero. Este desplome coincide con la llegada de la pandemia de COVID-19 a Colombia, lo que evidencia el fuerte impacto de las restricciones, el confinamiento y la contracción económica en la operación de la compañía.
Lo más relevante es la capacidad de recuperación que Colombina demostró posteriormente. Tras alcanzar su punto más bajo, la empresa inició un proceso de crecimiento que, aunque con algunas fluctuaciones, estableció una tendencia alcista clara. Para 2022, no solo había recuperado sus niveles previos a la pandemia, sino que los había superado ampliamente, con utilidades que duplicaban los valores históricos y alcanzaban entre 50 y 75 millones de pesos. Esto sugiere que la empresa no solo resistió la crisis, sino que implementó cambios estratégicos que fortalecieron su rentabilidad, posiblemente a través de mejoras operativas, digitalización o una reconfiguración de su portafolio de productos.
El período 2023-2024 muestra la consolidación de esta nueva etapa de crecimiento, con picos que superan los 100 millones de pesos, aunque con una volatilidad que sigue siendo una característica del negocio. Esta variabilidad no parece ser aleatoria, sino que responde a patrones cíclicos que sugieren influencias estacionales en el consumo. El cierre del período con una fuerte recuperación cerca de los 100 millones indica que, a pesar de las fluctuaciones, Colombina mantiene una sólida capacidad para generar utilidades en niveles históricamente altos.
stl_decomp <- stl(ts_data, s.window = "periodic")
plot(stl_decomp)
serie_ajustada <- ts_data - stl_decomp$time.series[, "seasonal"]
El análisis de la serie temporal permite observar con mayor detalle los diferentes componentes que explican el comportamiento financiero de Colombina S.A. En el primer panel, el gráfico de datos originales confirma la tendencia ascendente previamente descrita, pero al visualizarla de manera desglosada, se aprecia mejor la aceleración sostenida a partir de 2022, lo que sugiere un cambio estructural en la forma en que la empresa genera utilidades.
El segundo panel, que representa el componente estacional, resulta particularmente interesante, ya que revela un patrón cíclico bien definido y recurrente. Esta regularidad indica que la demanda de los productos de Colombina está influenciada por factores estacionales predecibles, como festividades o temporadas específicas. Es probable que se observen picos de consumo durante Navidad, Semana Santa o vacaciones escolares, períodos en los que tradicionalmente aumenta la compra de dulces y productos de confitería. Identificar estos patrones estacionales es clave para la planificación estratégica, ya que permite a la empresa anticipar períodos de alta y baja demanda.
El tercer panel muestra la tendencia pura y confirma la existencia de tres fases diferenciadas: un período inicial de estabilidad (2019-2020), seguido de una leve contracción durante los momentos más críticos de la pandemia, y finalmente, un crecimiento sostenido y acelerado desde finales de 2021 hasta 2024. Este análisis, libre de fluctuaciones estacionales y eventos atípicos, permite comprender mejor la evolución estructural de la empresa.
Por último, el panel de residuos identifica variaciones no explicadas por la tendencia o la estacionalidad, reflejando impactos externos como la crisis sanitaria o decisiones estratégicas internas que pudieron afectar los resultados de manera temporal.
grafico_ajustada <- ggplot() +
geom_line(aes(x = seq_along(ts_data), y = ts_data), color = "green", size = 1, linetype = "solid") +
geom_line(aes(x = seq_along(serie_ajustada), y = serie_ajustada), color = "blue", size = 1, linetype = "dashed") +
ggtitle("Figura 2.Serie Original vs Serie Ajustada por Estacionalidad") +
xlab("Tiempo") +
ylab("Miles de Pesos Colombianos") +
theme_minimal()
ggplotly(grafico_ajustada)
## Don't know how to automatically pick scale for object of type <ts>. Defaulting
## to continuous.
Comparar la serie original (verde claro) con la serie ajustada por estacionalidad (azul-discontinua) ofrece una visión más clara de la evolución financiera de Colombina. La serie original presenta oscilaciones pronunciadas, con caídas abruptas como la registrada en el período 5, que probablemente corresponde al impacto inicial de la pandemia, y picos importantes en los períodos más recientes.
En contraste, la serie ajustada suaviza estas fluctuaciones y permite visualizar con mayor claridad la tendencia subyacente. A partir del período 10, ambas líneas comienzan a separarse cada vez más, lo que indica que los efectos estacionales han cobrado mayor relevancia en la determinación de las utilidades.
La divergencia entre ambas líneas se hace más notoria en los picos de la serie original durante los períodos 15-21, lo que sugiere que una parte significativa de estas ganancias extraordinarias se debe a factores estacionales. Este hallazgo es relevante para la planificación financiera de la empresa, ya que resalta la importancia de considerar la estacionalidad en las estrategias de crecimiento y estabilidad de ingresos.
tendencia <- stl_decomp$time.series[, "trend"]
grafico_tendencia <- ggplot() +
geom_line(aes(x = seq_along(ts_data), y = ts_data), color = "lightblue", size = 1) +
geom_line(aes(x = seq_along(tendencia), y = tendencia), color = "purple", size = 1, linetype = "dashed") +
ggtitle("Figura 3. Serie Original vs Tendencia") +
xlab("Tiempo") +
ylab("Miles de Pesos Colombianos") +
theme_minimal()
ggplotly(grafico_tendencia)
## Don't know how to automatically pick scale for object of type <ts>. Defaulting
## to continuous.
El análisis de la serie original (azul claro) en comparación con la tendencia pura (púrpura-discontinua) permite identificar la evolución financiera de Colombina de manera más clara. La tendencia suaviza las fluctuaciones de la serie original, eliminando efectos estacionales y variaciones irregulares, lo que ayuda a visualizar la dirección general del desempeño de la empresa.
En los primeros períodos, la serie muestra cierta estabilidad, pero con variaciones notables. Sin embargo, alrededor del período 5, se presenta una caída abrupta que lleva los valores a cero, lo que indica un evento externo significativo (la pandemia, por ejemplo) que afectó los resultados financieros. Luego de esta caída, la serie comienza a recuperarse progresivamente, con oscilaciones que reflejan la influencia de factores cíclicos en el desempeño de la empresa.
A partir del período 10, la tendencia toma una dirección ascendente más definida, señalando una mejora constante en los ingresos o utilidades de la empresa. En los períodos más recientes, se observa una mayor volatilidad en la serie original, con picos que superan considerablemente la tendencia, lo que sugiere un comportamiento más dinámico en el mercado.
tasa_crecimiento <- (ts_data[(5:length(ts_data))] / ts_data[1:(length(ts_data) - 4)] - 1) * 100
tasa_tendencia <- (tendencia[(5:length(tendencia))] / tendencia[1:(length(tendencia) - 4)] - 1) * 100
fechas_corregidas <- seq(from = as.Date("2020-01-01"), by = "quarter", length.out = length(tasa_crecimiento))
print(length(fechas_corregidas))
## [1] 19
grafico_crecimiento <- ggplot() +
geom_line(aes(x = fechas_corregidas, y = tasa_crecimiento), color = "darkblue", size = 1) +
geom_line(aes(x = fechas_corregidas, y = tasa_tendencia), color = "red", size = 1, linetype = "dashed") +
ggtitle("Figura 4. Tasa de Crecimiento Anual: Serie Original vs Tendencia") +
xlab("Tiempo") +
ylab("% de Crecimiento Anual") +
theme_minimal()
ggplotly(grafico_crecimiento)
El análisis de la tasa de crecimiento anual permite visualizar la intensidad de los cambios experimentados por Colombina en los últimos años. La serie original (azul) presenta una volatilidad extrema, con una caída que se aproxima al -900% en 2021. Aunque este valor podría parecer un error, en realidad refleja el efecto de comparar un trimestre con utilidades normales frente a otro donde las ganancias fueron prácticamente nulas debido a la pandemia.
Después de este desplome sin precedentes, la empresa experimentó una recuperación extraordinaria, con tasas de crecimiento que superan el 100% en 2022. Este comportamiento sugiere no solo resiliencia operativa, sino también una reestructuración estratégica que fortaleció la capacidad de Colombina para generar utilidades.
En contraste, la tendencia ajustada (rojo-discontinuo) muestra un comportamiento más estable, con variaciones más moderadas a lo largo del tiempo. Esto indica que, eliminando los efectos extremos, la empresa ha mantenido un crecimiento sostenido y controlado. A partir de 2023, ambas líneas convergen en torno al 0%, lo que señala una fase de estabilización tras la recuperación acelerada post-pandemia. Esta nueva etapa de consolidación sugiere que la empresa ha alcanzado un punto de madurez, donde su crecimiento, aunque positivo, se desarrolla de manera más predecible y sostenible.