# Instalación (Solo si es necesario)
# install.packages(c("tidyverse", "janitor", "writexl", "openxlsx", "stringi", "mice", "VIM", "MASS"))
# install.packages("naniar")
# análisis, diagnóstico y visualización de valores faltantes (NA)
# install.packages("stringr")
# install.packages("readr")
# install.packages("glue")
# install.packages("mice", repos = "https://cloud.r-project.org")
library(tidyverse) # ggplot2, dplyr, tidyr, readr, purrr
library(janitor) # Limpieza de nombres
library(stringi) # Manipulación avanzada de texto
library(VIM) # Visualización de datos faltantes
library(MASS, exclude = "select") # Evita conflicto con dplyr::select
library(writexl) # Exportación
library(openxlsx) # Lectura/Escritura avanzada Excel
library(naniar) # Análisis, diagnóstico y visualización de valores faltantes (NA)
library(stringr) # limpiar texto (strings) de forma consistente y segura.
library(readr) # leer archivos de datos (CSV, TXT) de forma rápida y correcta.
library(glue) # construir textos dinámicos combinando texto + variables.
library(mice) # Imputar Valores.
# 1. Carga del archivo con las nuevas columnas
df_raw <- read_csv(
"C:/Users/john/Desktop/Data2AI_LATAM/db/2_Factores_de_emisión_del_inventario_Nacional_de_Emisiones_Atmosféricas_20260131.csv",
locale = locale(encoding = "UTF-8")
)
df <- df_raw %>%
janitor::clean_names() # estandariza nombres: minúsculas + _
glimpse(df)
Rows: 226,381
Columns: 31
$ ano <dbl> 1990, 1990, 1990, 1990, 1990, 1990, 1990, 199…
$ region <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, N…
$ desagregacion_especifica <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, N…
$ cartera <chr> "Minenergía", "Minenergía", "Minenergía", "Mi…
$ entidad <chr> "UPME", "UPME", "UPME", "UPME", "UPME", "UPME…
$ instrumento <chr> "FECOC 2016", "FECOC 2016", "FECOC 2016", "FE…
$ fuente_especifica <chr> "FECOC - UPME nombre del archivo FECOC-UPME_E…
$ s <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, …
$ s_2 <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, N…
$ c <chr> "A", "A", "A", "A", "A", "A", "A", "A", "A", …
$ sb1 <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, …
$ sb2 <chr> "a", "a", "a", "a", "a", "a", "b", "b", "b", …
$ sb3 <chr> "i", "i", "i", "i", "i", "i", NA, NA, NA, NA,…
$ sb4 <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, N…
$ sector_ipcc <chr> "1. Energía", "1. Energía", "1. Energía", "1.…
$ categoria <chr> "1.A. Actividades de quema de combustibles", …
$ subcategoria1 <chr> "1.A.1. Industrias de la energía", "1.A.1. In…
$ subcategoria2 <chr> "1.A.1.a. Producción de electricidad pública …
$ subcategoria3 <chr> "1.A.1.a.i. Generación de electricidad", "1.A…
$ subcategoria4 <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, N…
$ clasificacion1 <chr> "CARBON MINERAL", "DIESEL OIL", "FUEL OIL", "…
$ clasificacion2 <chr> "CENTRALES TERMICAS", "CENTRALES TERMICAS", "…
$ clasificacion3 <chr> NA, NA, NA, NA, NA, NA, "CONSUMO TOTAL", "CON…
$ nombre_factor <chr> "CONSUMO DE COMBUSTIBLE EN GENERACION DE ENER…
$ nivel <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, N…
$ codigo_factor <chr> "1990NACIONAL1A1aiCARBON MINERALCENTRALES TER…
$ valor_f <dbl> 88136.00, 74193483.00, 78281203.00, 55539.11,…
$ unidad_f <chr> "kg CO2/TJ", "kg CO2/TJ", "kg CO2/TJ", "kg CO…
$ inc_inferior <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
$ inc_superior <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
$ contaminante <chr> "CO2", "CO2", "CO2", "CO2", "CO2", "CO2", "CO…
Detecta formatos tipo:
“74,193,483” (coma como miles)
“55,539.11” (coma miles, punto decimal)
to_num_mixed <- function(x) {
x <- as.character(x)
x <- str_replace_all(x, "\\s+", "")
x <- na_if(x, "")
# Caso: coma + punto → coma = miles
x <- ifelse(
str_detect(x, ",") & str_detect(x, "\\."),
str_replace_all(x, ",", ""),
x
)
# Caso: solo coma → coma = miles
x <- ifelse(
str_detect(x, ",") & !str_detect(x, "\\."),
str_replace_all(x, ",", ""),
x
)
suppressWarnings(as.numeric(x))
}
df_clean <- df %>%
mutate(
valor_f = to_num_mixed(valor_f),
inc_inferior = to_num_mixed(inc_inferior),
inc_superior = to_num_mixed(inc_superior)
)
Normalizamos (mayúsculas, sin tildes).
Generamos abreviatura por siglas + recorte.
Creamos un diccionario por columna para reemplazo consistente.
normalize_key <- function(x) {
x %>%
as.character() %>%
stringi::stri_enc_toutf8(is_unknown_8bit = TRUE) %>%
stringi::stri_trans_general("Latin-ASCII") %>%
str_squish() %>%
str_to_upper()
}
make_abbrev <- function(x, max_len = 28) {
if (is.na(x) || nchar(x) <= max_len) return(x)
cleaned <- x %>%
str_replace_all("[^A-Z0-9 ]", " ") %>%
str_squish() %>%
str_replace_all(
"\\b(DE|DEL|LA|LAS|EL|LOS|Y|EN|PARA|POR|CON|SIN|A|AL)\\b",
""
) %>%
str_squish()
words <- unlist(str_split(cleaned, " "))
sigla <- paste0(substr(words, 1, 1), collapse = "")
pref <- substr(words[1], 1, min(3, nchar(words[1])))
out <- str_squish(paste0(pref, "_", sigla))
if (nchar(out) < 6) out <- substr(x, 1, max_len)
substr(out, 1, max_len)
}
cols_to_abbrev <- intersect(
names(df_clean),
c(
"region","desagregacion_especifica","cartera","entidad","instrumento",
"fuente_especifica","sector_ipcc","categoria",
"subcategoria1","subcategoria2","subcategoria3","subcategoria4",
"clasificacion1","clasificacion2","clasificacion3",
"nombre_factor","nivel","unidad_f","contaminante"
)
)
# Copias claras para auditoría
df_before <- df_clean
df_abbrev <- df_clean
abbrev_maps <- list()
max_len <- 28
for (col in cols_to_abbrev) {
v_norm <- normalize_key(df_abbrev[[col]])
u <- sort(unique(v_norm[!is.na(v_norm)]))
ab <- purrr::map_chr(u, make_abbrev, max_len = max_len)
# Resolver colisiones
if (any(duplicated(ab))) {
for (d in unique(ab[duplicated(ab)])) {
idx <- which(ab == d)
ab[idx] <- paste0(ab[idx], "_", seq_along(idx))
ab[idx] <- substr(ab[idx], 1, max_len)
}
}
map_tbl <- tibble(
column = col,
original_norm = u,
abbreviated = ab
)
abbrev_maps[[col]] <- map_tbl
# Aplicar mapeo
m <- setNames(map_tbl$abbreviated, map_tbl$original_norm)
x_norm <- normalize_key(df_abbrev[[col]])
idx <- !is.na(x_norm)
mapped <- unname(m[x_norm[idx]])
df_abbrev[[col]][idx] <- ifelse(
is.na(mapped),
df_abbrev[[col]][idx],
mapped
)
}
dict_abbrev <- bind_rows(abbrev_maps)
df_after <- df_abbrev
Cuántos cambiaron, NA antes/después, y cuántos quedaron largos.
audit_cols <- intersect(cols_to_abbrev, intersect(names(df_before), names(df_after)))
audit_summary <- purrr::map_dfr(audit_cols, function(col) {
before <- as.character(df_before[[col]])
after <- as.character(df_after[[col]])
tibble(
column = col,
n_total = length(after),
n_na_before = sum(is.na(before)),
n_na_after = sum(is.na(after)),
n_changed = sum(!is.na(before) & !is.na(after) & before != after),
n_long_before = sum(!is.na(before) & nchar(before) > max_len),
n_long_after = sum(!is.na(after) & nchar(after) > max_len)
)
})
audit_summary
# A tibble: 19 × 7
column n_total n_na_before n_na_after n_changed n_long_before n_long_after
<chr> <int> <int> <int> <int> <int> <int>
1 region 226381 82927 82927 9440 0 0
2 desagreg… 226381 221325 221325 5056 1376 0
3 cartera 226381 3688 3688 222693 4160 0
4 entidad 226381 25057 25057 188680 125829 0
5 instrume… 226381 169467 169467 14762 2129 0
6 fuente_e… 226381 64838 64838 152739 136862 0
7 sector_i… 226381 0 0 226381 149454 0
8 categoria 226381 24067 24067 202314 30484 0
9 subcateg… 226381 3008 3008 223373 60228 0
10 subcateg… 226381 17515 17515 208866 157818 0
11 subcateg… 226381 44068 44068 182313 166371 0
12 subcateg… 226381 221261 221261 5120 3556 0
13 clasific… 226381 15273 15273 52182 6986 0
14 clasific… 226381 81369 81369 30032 192 0
15 clasific… 226381 90199 90199 8960 0 0
16 nombre_f… 226381 697 697 222956 73816 0
17 nivel 226381 38267 38267 149970 0 0
18 unidad_f 226381 3469 3469 194722 23780 0
19 contamin… 226381 69349 69349 13127 0 0
audit_summary %>%
select(column, n_long_before, n_long_after)
# A tibble: 19 × 3
column n_long_before n_long_after
<chr> <int> <int>
1 region 0 0
2 desagregacion_especifica 1376 0
3 cartera 4160 0
4 entidad 125829 0
5 instrumento 2129 0
6 fuente_especifica 136862 0
7 sector_ipcc 149454 0
8 categoria 30484 0
9 subcategoria1 60228 0
10 subcategoria2 157818 0
11 subcategoria3 166371 0
12 subcategoria4 3556 0
13 clasificacion1 6986 0
14 clasificacion2 192 0
15 clasificacion3 0 0
16 nombre_factor 73816 0
17 nivel 0 0
18 unidad_f 23780 0
19 contaminante 0 0
El proceso de preparación de datos aplicado al Inventario Nacional de Emisiones Atmosféricas permitió consolidar un conjunto de datos limpio, consistente, trazable y apto para análisis estadístico y modelamiento, sin comprometer la integridad de la información original. A continuación, se presentan las conclusiones principales del procedimiento.
Durante todo el pipeline de procesamiento se conservaron las 226.381 observaciones originales, sin pérdida ni duplicación de registros, además la estructura del dataset (filas y columnas) se mantuvo estable en todas las etapas.
No se introdujeron desalineaciones, errores de indexación ni conflictos de tipos de datos, lo que confirma que el proceso no fue destructivo y respetó la estructura original del inventario.
En el análisis comparativo antes y después del procesamiento se observó que el número de valores faltantes (NA) se mantuvo idéntico por columna, no se generaron valores faltantes adicionales durante la limpieza, normalización o abreviación y no se eliminaron ni reemplazaron valores válidos por error, lo que garantiza que el proceso no alteró la calidad semántica ni la completitud de la información disponible.
La limpieza de texto permitió estandarizar de manera homogénea todos los campos categóricos mediante la corrección de problemas de encoding (UTF-8 / mojibake), la eiminación de tildes y caracteres especiales, unificación de mayúsculas/minúsculas, además de la compactación de espacios y tratamiento explícito de valores vacíos. Esta normalización redujo la ambigüedad semántica y evitó duplicidades causadas por diferencias superficiales de escritura, mejorando la consistencia interna del dataset.
Las variables numéricas críticas (valor_f, inc_inferior, inc_superior) fueron transformadas correctamente desde formatos mixtos de origen (comas como separadores de miles y puntos decimales) hacia valores numéricos homogéneos, sin pérdida de información.
El procedimiento fue reproducible, tolerante a formatos inconsistentes y seguro frente a valores faltantes, lo que habilita el uso directo de estas variables en análisis estadísticos, modelos de incertidumbre y procesos de agregación.
Las columnas con textos extensos (por ejemplo: entidad, sector_ipcc, fuente_especifica, subcategoria*) fueron tratadas mediante un sistema de abreviación inteligente que solo abrevia valores que exceden un umbral máximo de longitud (28 caracteres), utiliza siglas informativas combinadas con prefijos semánticos, elimina conectores no informativos para mejorar legibilidad y aplica un diccionario de reemplazo por columna, garantizando consistencia.
El número de valores largos se redujo de decenas de miles a cero en todas las columnas auditadas, no se produjeron colisiones semánticas (abreviaturas ambiguas) y la información se mantuvo interpretable y trazable.
El proceso generó Un diccionario de abreviaciones, que permite auditar los cambios realizados, revertir el proceso si es necesario y documentar el mapeo original abreviado.
Tras el procesamiento, el conjunto de datos queda plenamente preparado para analisis de nulos por variables, análisis exploratorio y estadístico, modelamiento de emisiones y escenarios y visualización.
# Función de revisión de nulos
reporte_nulos <- df_after %>%
summarise(across(everything(),
~ sum(is.na(.)),
.names = "nulos_{col}")) %>%
pivot_longer(everything(),
names_to = "variable",
values_to = "nulos") %>%
mutate(
variable = str_replace(variable, "nulos_", ""),
porcentaje = round((nulos / nrow(df_after)) * 100, 2)
) %>%
arrange(desc(nulos)) # ← AQUÍ se ordena por cantidad de nulos
options(dplyr.width = Inf)
options(tibble.print_max = Inf)
# Mostrar resultados
reporte_nulos
# A tibble: 31 × 3
variable nulos porcentaje
<chr> <int> <dbl>
1 desagregacion_especifica 221325 97.8
2 subcategoria4 221261 97.7
3 sb4 213183 94.2
4 sb3 185098 81.8
5 inc_superior 175622 77.6
6 inc_inferior 175547 77.5
7 instrumento 169467 74.9
8 s_2 100621 44.4
9 clasificacion3 90199 39.8
10 region 82927 36.6
11 clasificacion2 81369 35.9
12 contaminante 69349 30.6
13 fuente_especifica 64838 28.6
14 subcategoria3 44068 19.5
15 nivel 38267 16.9
16 entidad 25057 11.1
17 categoria 24067 10.6
18 sb2 17516 7.74
19 subcategoria2 17515 7.74
20 clasificacion1 15273 6.75
21 valor_f 14825 6.55
22 sb1 6275 2.77
23 ano 4149 1.83
24 cartera 3688 1.63
25 unidad_f 3469 1.53
26 s 3267 1.44
27 c 3267 1.44
28 subcategoria1 3008 1.33
29 nombre_factor 697 0.31
30 codigo_factor 610 0.27
31 sector_ipcc 0 0
En tu dataset, esto aplica muy bien a niveles jerárquicos que “no existen” para ciertas filas:
cols_na_apl <- c(
"desagregacion_especifica",
"s_2",
"sb3", "sb4",
"subcategoria4",
"clasificacion3",
"nivel"
)
Usa esto cuando el campo normalmente debería existir, pero viene vacío (más “dato faltante real”):
cols_na_rep <- c(
"region"
# agrega aquí otras si en tu diagnóstico aplica, por ejemplo:
# "fuente_especifica", "contaminante"
)
df_after <- df_after %>%
mutate(
across(all_of(intersect(cols_na_apl, names(df_after))),
~ tidyr::replace_na(as.character(.x), "NA_APL")),
across(all_of(intersect(cols_na_rep, names(df_after))),
~ tidyr::replace_na(as.character(.x), "NA_REP"))
)
reporte_nulos <- df_after %>%
summarise(
across(
everything(),
~ {
# NA reales
if (is.character(.x)) {
sum(is.na(.x))
} else {
sum(is.na(.x))
}
},
.names = "nulos_{.col}"
)
) %>%
pivot_longer(
everything(),
names_to = "variable",
values_to = "nulos"
) %>%
mutate(
variable = str_replace(variable, "nulos_", ""),
porcentaje = round((nulos / nrow(df_after)) * 100, 2)
) %>%
arrange(desc(nulos))
options(dplyr.width = Inf)
options(tibble.print_max = Inf)
reporte_nulos
# A tibble: 31 × 3
variable nulos porcentaje
<chr> <int> <dbl>
1 inc_superior 175622 77.6
2 inc_inferior 175547 77.5
3 instrumento 169467 74.9
4 clasificacion2 81369 35.9
5 contaminante 69349 30.6
6 fuente_especifica 64838 28.6
7 subcategoria3 44068 19.5
8 entidad 25057 11.1
9 categoria 24067 10.6
10 sb2 17516 7.74
11 subcategoria2 17515 7.74
12 clasificacion1 15273 6.75
13 valor_f 14825 6.55
14 sb1 6275 2.77
15 ano 4149 1.83
16 cartera 3688 1.63
17 unidad_f 3469 1.53
18 s 3267 1.44
19 c 3267 1.44
20 subcategoria1 3008 1.33
21 nombre_factor 697 0.31
22 codigo_factor 610 0.27
23 region 0 0
24 desagregacion_especifica 0 0
25 s_2 0 0
26 sb3 0 0
27 sb4 0 0
28 sector_ipcc 0 0
29 subcategoria4 0 0
30 clasificacion3 0 0
31 nivel 0 0
Para modelar emisiones, se necesita: categoria, subcategoria1, valor_f, contaminante, ano
cols_clave <- c(
"categoria",
"subcategoria1",
"valor_f",
"contaminante",
"ano"
)
df_modelo <- df_after %>%
filter(
across(
all_of(cols_clave),
~ !(.x %in% c("NA_APL", "NA_REP") | is.na(.x))
)
)
tibble(
filas_originales = nrow(df_after),
filas_modelo = nrow(df_modelo),
filas_eliminadas = nrow(df_after) - nrow(df_modelo),
porcentaje_perdida = round(
(nrow(df_after) - nrow(df_modelo)) / nrow(df_after) * 100, 2
)
)
# A tibble: 1 × 4
filas_originales filas_modelo filas_eliminadas porcentaje_perdida
<int> <int> <int> <dbl>
1 226381 115334 111047 49.0
df_after %>%
mutate(
motivo = case_when(
contaminante == "NA_REP" ~ "contaminante",
region == "NA_REP" ~ "region",
categoria == "NA_APL" ~ "categoria",
subcategoria1 == "NA_APL" ~ "subcategoria1",
TRUE ~ "OK"
)
) %>%
count(motivo, sort = TRUE)
# A tibble: 2 × 2
motivo n
<chr> <int>
1 OK 143454
2 region 82927
cols_ignorar <- c(
"s", "sb1", "sb4", # codificación jerárquica
"s_2", "sb2", "sb3", # etiquetas internas
"c", # clasificación técnica
"inc_inferior", "inc_superior", # incertidumbre incompleta
"ano" # índice temporal (no predictor)
)
df_modelo <- df_after %>%
dplyr::select(-any_of(cols_ignorar))
setdiff(cols_ignorar, names(df_modelo))
[1] "s" "sb1" "sb4" "s_2" "sb2"
[6] "sb3" "c" "inc_inferior" "inc_superior" "ano"
glimpse(df_modelo)
Rows: 226,381
Columns: 21
$ region <chr> "NA_REP", "NA_REP", "NA_REP", "NA_REP", "NA_R…
$ desagregacion_especifica <chr> "NA_APL", "NA_APL", "NA_APL", "NA_APL", "NA_A…
$ cartera <chr> "MINENERGIA", "MINENERGIA", "MINENERGIA", "MI…
$ entidad <chr> "UPME", "UPME", "UPME", "UPME", "UPME", "UPME…
$ instrumento <chr> "FECOC 2016", "FECOC 2016", "FECOC 2016", "FE…
$ fuente_especifica <chr> "FEC_FUNAFUE102", "FEC_FUNAFUE102", "FEC_FUNA…
$ sector_ipcc <chr> "1. ENERGIA", "1. ENERGIA", "1. ENERGIA", "1.…
$ categoria <chr> "1_1AQC", "1_1AQC", "1_1AQC", "1_1AQC", "1_1A…
$ subcategoria1 <chr> "1_11IE", "1_11IE", "1_11IE", "1_11IE", "1_11…
$ subcategoria2 <chr> "1_11PEPCCAP", "1_11PEPCCAP", "1_11PEPCCAP", …
$ subcategoria3 <chr> "1_11IGE", "1_11IGE", "1_11IGE", "1_11IGE", "…
$ subcategoria4 <chr> "NA_APL", "NA_APL", "NA_APL", "NA_APL", "NA_A…
$ clasificacion1 <chr> "CARBON MINERAL", "DIESEL OIL", "FUEL OIL", "…
$ clasificacion2 <chr> "CENTRALES TERMICAS", "CENTRALES TERMICAS", "…
$ clasificacion3 <chr> "NA_APL", "NA_APL", "NA_APL", "NA_APL", "NA_A…
$ nombre_factor <chr> "CON_CCGE", "CON_CCGE", "CON_CCGE", "CON_CCGE…
$ nivel <chr> "NA_APL", "NA_APL", "NA_APL", "NA_APL", "NA_A…
$ codigo_factor <chr> "1990NACIONAL1A1aiCARBON MINERALCENTRALES TER…
$ valor_f <dbl> 88136.00, 74193483.00, 78281203.00, 55539.11,…
$ unidad_f <chr> "KG CO2/TJ", "KG CO2/TJ", "KG CO2/TJ", "KG CO…
$ contaminante <chr> "CO2", "CO2", "CO2", "CO2", "CO2", "CO2", "CO…
registro_abreviaturas <- tibble::tribble(
~abreviatura, ~columna, ~significado, ~tipo,
"NA_APL", "Varias", "No Aplica", "Control de nulos",
"NA_REP", "region", "No Reportado", "Control de nulos",
"MINENERGIA", "cartera", "Ministerio de Minas y Energía", "Institucional",
"UPME", "entidad", "Unidad de Planeación Minero Energética", "Institucional",
"FECOC 2016", "instrumento", "Factor de Emisión de Combustión (2016)", "Metodológico",
"FEC_FUNAFUE102", "fuente_especifica", "Fuente técnica FUNAFUE código 102", "Fuente técnica",
"1. ENERGIA", "sector_ipcc", "Sector IPCC Energía", "Clasificación IPCC",
"1_1AQC", "categoria", "Quema de combustibles", "Clasificación IPCC",
"1_11IE", "subcategoria1", "Industrias de la energía", "Clasificación IPCC",
"1_11PEPCCAP", "subcategoria2", "Producción de electricidad", "Clasificación IPCC",
"1_11IGE", "subcategoria3", "Generación eléctrica", "Clasificación IPCC",
"CON_CCGE", "nombre_factor", "Consumo de combustible en generación eléctrica", "Variable técnica",
"KG CO2/TJ", "unidad_f", "Kilogramos de CO2 por terajulio", "Unidad",
"CO2", "contaminante", "Dióxido de carbono", "Contaminante"
)
ruta_salida <- "C:/Users/john/Desktop/Data2AI_LATAM/db"
# Guardar como CSV
readr::write_csv(
registro_abreviaturas,
file.path(ruta_salida, "registro_abreviaturas.csv")
)
# Guardar como Excel (.xlsx)
openxlsx::write.xlsx(
registro_abreviaturas,
file = file.path(ruta_salida, "registro_abreviaturas.xlsx"),
overwrite = TRUE
)
# Verificación rápida (recomendada)
list.files(
path = ruta_salida,
pattern = "registro_abreviaturas",
full.names = TRUE
)
[1] "C:/Users/john/Desktop/Data2AI_LATAM/db/registro_abreviaturas.csv"
[2] "C:/Users/john/Desktop/Data2AI_LATAM/db/registro_abreviaturas.xlsx"
# attr(registro_abreviaturas, "ruta_guardado") <- ruta_salida
# attr(registro_abreviaturas, "fecha_creacion") <- Sys.Date()
# Función de revisión de nulos
reporte_nulos <- df_modelo %>%
summarise(across(everything(),
~ sum(is.na(.)),
.names = "nulos_{col}")) %>%
pivot_longer(everything(),
names_to = "variable",
values_to = "nulos") %>%
mutate(
variable = str_replace(variable, "nulos_", ""),
porcentaje = round((nulos / nrow(df_modelo)) * 100, 2)
) %>%
arrange(desc(nulos)) # ← AQUÍ se ordena por cantidad de nulos
options(dplyr.width = Inf)
options(tibble.print_max = Inf)
# Mostrar resultados
reporte_nulos
# A tibble: 21 × 3
variable nulos porcentaje
<chr> <int> <dbl>
1 instrumento 169467 74.9
2 clasificacion2 81369 35.9
3 contaminante 69349 30.6
4 fuente_especifica 64838 28.6
5 subcategoria3 44068 19.5
6 entidad 25057 11.1
7 categoria 24067 10.6
8 subcategoria2 17515 7.74
9 clasificacion1 15273 6.75
10 valor_f 14825 6.55
11 cartera 3688 1.63
12 unidad_f 3469 1.53
13 subcategoria1 3008 1.33
14 nombre_factor 697 0.31
15 codigo_factor 610 0.27
16 region 0 0
17 desagregacion_especifica 0 0
18 sector_ipcc 0 0
19 subcategoria4 0 0
20 clasificacion3 0 0
21 nivel 0 0
imputar / no imputar / tratar como categoría. Proceso de imputación paso a paso en R (con código claro). Según Rubin (1976), los datos faltantes pueden ser:
El NA no depende de ninguna variable observada ni no observada. Se puede eliminar o imputar sin sesgo.
El NA depende de otras variables observadas. Imputación válida (regresión, MICE, etc.).
El NA depende de la variable faltante en sí (información estructural). NO se debe imputar con valores “inventados”.
En inventarios ambientales y administrativos, la mayoría de NA son MAR o MNAR, no MCAR.
Son estructurales o administrativas:
| Variable | % NA | Decisión | Justificación |
|---|---|---|---|
1_instrumento |
74.9 | NO imputar | Ausencia estructural (MNAR) |
4_fuente_especifica |
28.6 | NO imputar | No aplica a todos los registros |
5_subcategoria3 |
19.5 | NO imputar | Nivel jerárquico no obligatorio |
7_categoria, 13_subcategoria1,
8_subcategoria2 |
7–10 | NO imputar | Estructura IPCC |
9_clasificacion1, 2_clasificacion2 |
6–36 | NO imputar | Clasificación técnica |
6_entidad, 11_cartera |
<12 | NO imputar | Administrativas |
Nota: Se usó No aplica (NA_APL), No reporta (NA_REP) como categorías explícitas.
No imputación estadística:
| Variable | Acción |
|---|---|
1_instrumento |
"NA_APL" |
4_fuente_especifica |
"NA_APL" |
2_clasificacion2 |
"NA_APL" |
3_contaminante |
"NA_REP" |
| Variable | % NA | Tipo | Decisión |
|---|---|---|---|
10_valor_f |
6.55 | Numérica continua | Imputar (MAR) |
valor_f depende de sector_ipcc, categoria, subcategorías, contaminante, unidad_f
df_imputacion <- df_modelo %>%
select(
valor_f,
sector_ipcc,
categoria,
subcategoria1,
subcategoria2,
subcategoria3,
contaminante,
unidad_f
)
#library(mice)
# Inicializar
ini <- mice(df_imputacion, maxit = 0)
# Método: predictive mean matching
meth <- ini$method
meth["valor_f"] <- "pmm"
# No imputar categóricas
meth[names(meth) != "valor_f"] <- ""
# Ejecutar imputación
imp <- mice(
df_imputacion,
method = meth,
m = 5,
maxit = 15,
seed = 5477976
)
iter imp variable
1 1 valor_f
1 2 valor_f
1 3 valor_f
1 4 valor_f
1 5 valor_f
2 1 valor_f
2 2 valor_f
2 3 valor_f
2 4 valor_f
2 5 valor_f
3 1 valor_f
3 2 valor_f
3 3 valor_f
3 4 valor_f
3 5 valor_f
4 1 valor_f
4 2 valor_f
4 3 valor_f
4 4 valor_f
4 5 valor_f
5 1 valor_f
5 2 valor_f
5 3 valor_f
5 4 valor_f
5 5 valor_f
6 1 valor_f
6 2 valor_f
6 3 valor_f
6 4 valor_f
6 5 valor_f
7 1 valor_f
7 2 valor_f
7 3 valor_f
7 4 valor_f
7 5 valor_f
8 1 valor_f
8 2 valor_f
8 3 valor_f
8 4 valor_f
8 5 valor_f
9 1 valor_f
9 2 valor_f
9 3 valor_f
9 4 valor_f
9 5 valor_f
10 1 valor_f
10 2 valor_f
10 3 valor_f
10 4 valor_f
10 5 valor_f
11 1 valor_f
11 2 valor_f
11 3 valor_f
11 4 valor_f
11 5 valor_f
12 1 valor_f
12 2 valor_f
12 3 valor_f
12 4 valor_f
12 5 valor_f
13 1 valor_f
13 2 valor_f
13 3 valor_f
13 4 valor_f
13 5 valor_f
14 1 valor_f
14 2 valor_f
14 3 valor_f
14 4 valor_f
14 5 valor_f
15 1 valor_f
15 2 valor_f
15 3 valor_f
15 4 valor_f
15 5 valor_f
df_modelo$valor_f <- complete(imp)$valor_f
# Antes de imputar
sum(is.na(df_before$valor_f))
[1] 14825
# Después de imputar
sum(is.na(df_modelo$valor_f))
[1] 0
# Exportar a CSV
readr::write_csv(
df_modelo,
file.path(ruta_salida, "2_Factores_de_emision_clean.csv")
)
# Exportar a Excel (.xlsx)
openxlsx::write.xlsx(
df_modelo,
file = file.path(ruta_salida, "2_Factores_de_emision_clean.xlsx"),
overwrite = TRUE
)
# Verificación
list.files(
path = ruta_salida,
pattern = "2_Factores_de_emision_clean",
full.names = TRUE
)
[1] "C:/Users/john/Desktop/Data2AI_LATAM/db/2_Factores_de_emision_clean.csv"
[2] "C:/Users/john/Desktop/Data2AI_LATAM/db/2_Factores_de_emision_clean.xlsx"