Versión de impresión: se muestran todas las secciones en una sola página.
Este informe fue elaborado en el rol de analista de la Unidad de Análisis Hospitalario, con el encargo de dimensionar y caracterizar la actividad hospitalaria asociada a una enfermedad específica dentro de los egresos hospitalarios registrados por el mecanismo de pago Grupos Relacionados por el Diagnóstico (GRD) durante el año 2024. El alcance es descriptivo: no se ajustan modelos predictivos ni se realizan pruebas de hipótesis, y ninguna cifra de este documento debe interpretarse como incidencia, prevalencia poblacional o riesgo de enfermar. Cada fila de la base representa un episodio (una hospitalización completa, desde el ingreso hasta el egreso), no una persona: un mismo paciente puede aparecer varias veces en la base.
La enfermedad elegida es la insuficiencia cardíaca. Es una condición crónica y progresiva, causa frecuente de hospitalización y de reingreso en adultos mayores, y su manejo consume recursos hospitalarios significativos (días de estancia prolongados, unidades de cuidados intensivos, procedimientos cardiovasculares). Analizar su peso dentro de los egresos GRD 2024 permite a la jefatura dimensionar la carga hospitalaria asociada, identificar los territorios y establecimientos donde se concentra esta actividad, y detectar diferencias relevantes en la severidad y en la duración de la estancia entre centros.
Se utilizó Claude (Anthropic, modelo Sonnet 5) como
herramienta de apoyo para: (i) escribir y depurar el código R de
lectura, limpieza, cruce con tablas maestras y visualización; (ii)
redactar el texto interpretativo de cada resultado; y (iii) estructurar
el informe conforme a la pauta de la actividad. La verificación de los
resultados se realizó de las siguientes formas: los conteos de cada
etapa de construcción de la cohorte quedan impresos en el propio
documento y son recalculados en cada ejecución del .Rmd (no
están escritos a mano); los códigos CIE-10 utilizados se contrastaron
contra la tabla oficial CIE-10.xlsx; los cruces de código
de hospital y código GRD se validaron contra las tablas maestras
entregadas por FONASA (Tablas_maestras_GRD.xlsx); y todas
las cifras narradas en el texto citan la tabla o figura donde pueden
verificarse. La responsabilidad técnica y de contenido del informe es
del autor.
GRD_PUBLICO_2024.txt,
descargado por el autor desde el portal de datos abiertos de
FONASA.|,
codificación Latin-1 (ISO-8859-1), 129 columnas.| Tabla | Contenido | Uso en este informe |
|---|---|---|
Tablas_maestras_GRD.xlsx — hoja
Hospitales |
Código de hospital → nombre del establecimiento (377 registros) | Identificar los establecimientos en la sección territorial |
Tablas_maestras_GRD.xlsx — hoja IR - GRD |
Código GRD → descripción del grupo relacionado de diagnóstico | Describir los GRD más frecuentes de la cohorte |
Tablas_maestras_GRD.xlsx — hoja Severidad
GRD |
Nivel de severidad (0-3) → etiqueta | Etiquetar el nivel de severidad de los episodios |
Tablas_maestras_GRD.xlsx — hoja Mortalidad
GRD |
Nivel de riesgo de mortalidad (0-3) → etiqueta | Etiquetar el riesgo de mortalidad esperado |
CIE-10.xlsx |
Catálogo CIE-10 versión 2013 | Verificar la descripción oficial de los códigos I50 utilizados |
Adicionalmente, la variable SERVICIO_SALUD, incluida
directamente en la base, se usó para agrupar los episodios por
territorio. Como esta variable no trae la región asociada, se construyó
una tabla de correspondencia Servicio de Salud → Región, basada en la
división administrativa vigente de las 29 direcciones de servicio de
salud de Chile (ver “Distribución territorial e institucional”).
cie10 <- as.data.table(read_excel("CIE-10.xlsx", sheet = "CIE 10"))
cie10_i50 <- cie10[grepl("^I50", Código), .(Código, Descripción, Sección)]
tabla_html(cie10_i50, caption = "Códigos CIE-10 usados para definir la cohorte (fuente: CIE-10.xlsx, catálogo oficial CIE-10 versión 2013)")
| Código | Descripción | Sección |
|---|---|---|
| I50 | Insuficiencia cardíaca | I30-I52 OTRAS FORMAS DE ENFERMEDAD CARDIACA |
| I50.0 | Insuficiencia cardíaca congestiva | I30-I52 OTRAS FORMAS DE ENFERMEDAD CARDIACA |
| I50.1 | Insuficiencia ventricular izquierda | I30-I52 OTRAS FORMAS DE ENFERMEDAD CARDIACA |
| I50.9 | Insuficiencia cardíaca, no especificada | I30-I52 OTRAS FORMAS DE ENFERMEDAD CARDIACA |
Se utilizó el código I50 (Insuficiencia cardíaca) y sus tres subcategorías vigentes en el catálogo chileno: I50.0 (insuficiencia cardíaca congestiva), I50.1 (insuficiencia ventricular izquierda) e I50.9 (insuficiencia cardíaca no especificada). El catálogo no registra subcategorías I50.2 a I50.8 para la versión utilizada en Chile.
cols_diag <- paste0("DIAGNOSTICO", 1:35)
cols_usar <- c("COD_HOSPITAL", "SEXO", "FECHA_NACIMIENTO", "SERVICIO_SALUD", "PREVISION",
"TIPO_INGRESO", "ESPECIALIDAD_MEDICA", "TIPO_ACTIVIDAD", "FECHA_INGRESO",
"FECHAALTA", "TIPOALTA", cols_diag,
"IR_29301_COD_GRD", "IR_29301_PESO", "IR_29301_SEVERIDAD", "IR_29301_MORTALIDAD",
"ID_BENEFICIARIO")
base <- fread("GRD_PUBLICO_2024/GRD_PUBLICO_2024.txt", sep = "|", encoding = "Latin-1",
quote = "", na.strings = "", select = cols_usar,
colClasses = list(character = c("COD_HOSPITAL", "ID_BENEFICIARIO",
"IR_29301_COD_GRD")))
n0 <- nrow(base)
cat("Registros en la base completa GRD 2024:", fnum(n0))
## Registros en la base completa GRD 2024: 1.085.813
tabla_html(base[, .(episodios = .N), by = TIPO_ACTIVIDAD][order(-episodios)],
caption = "Registros por tipo de actividad, base completa")
| TIPO_ACTIVIDAD | episodios |
|---|---|
| HOSPITALIZACIÓN | 873772 |
| CIRUGÍA MAYOR AMBULATORIA (CMA) | 212041 |
La base completa contiene 1.085.813 registros. El
campo TIPO_ACTIVIDAD distingue dos tipos de actividad:
egresos hospitalarios y cirugía mayor ambulatoria (CMA).
Conforme a la pauta, se trabaja solo con egresos hospitalarios; los registros de CMA se excluyen porque corresponden a actividad quirúrgica sin pernoctación, con una dinámica clínica distinta a la hospitalización.
n_cma <- base[TIPO_ACTIVIDAD == "CIRUGÍA MAYOR AMBULATORIA (CMA)", .N]
base_hosp <- base[TIPO_ACTIVIDAD == "HOSPITALIZACIÓN"]
n1 <- nrow(base_hosp)
cat("Registros excluidos por ser CMA:", fnum(n_cma), "\n")
## Registros excluidos por ser CMA: 212.041
cat("Registros de egresos hospitalarios (base de trabajo):", fnum(n1))
## Registros de egresos hospitalarios (base de trabajo): 873.772
La enfermedad puede registrarse en el campo DIAGNOSTICO1
(diagnóstico principal) o en cualquiera de los 34 campos de diagnóstico
secundario (DIAGNOSTICO2 a DIAGNOSTICO35). Se
buscó el patrón ^I50 (es decir, I50, I50.0, I50.1 o I50.9)
en los 35 campos de diagnóstico de cada episodio de la
base de egresos hospitalarios.
mat_i50 <- sapply(cols_diag, function(cc) grepl("^I50", base_hosp[[cc]]))
tiene_i50 <- rowSums(mat_i50) > 0
es_principal <- grepl("^I50", base_hosp$DIAGNOSTICO1)
candidatos <- base_hosp[tiene_i50]
n_cand <- nrow(candidatos)
# cuántos episodios de CMA mencionaban I50 (se excluyen igualmente, se documentan)
base_cma <- base[TIPO_ACTIVIDAD == "CIRUGÍA MAYOR AMBULATORIA (CMA)"]
mat_i50_cma <- sapply(cols_diag, function(cc) grepl("^I50", base_cma[[cc]]))
n_i50_cma <- sum(rowSums(mat_i50_cma) > 0)
cat("Episodios candidatos (I50 en algún campo de diagnóstico, ya sin CMA):", fnum(n_cand), "\n")
## Episodios candidatos (I50 en algún campo de diagnóstico, ya sin CMA): 52.315
cat("De ellos, como diagnóstico principal:", fnum(sum(es_principal[tiene_i50])), "\n")
## De ellos, como diagnóstico principal: 11.331
cat("Registros de CMA con I50 mencionado (excluidos de todas formas por ser CMA):", fnum(n_i50_cma))
## Registros de CMA con I50 mencionado (excluidos de todas formas por ser CMA): 1.546
Sobre los 52.315 episodios candidatos se revisó la validez de las
fechas indispensables para caracterizar el episodio
(FECHA_INGRESO, FECHA_NACIMIENTO,
FECHAALTA). Un número reducido de registros trae el valor
"DESCONOCIDO" en vez de una fecha, lo que impide calcular
edad o estancia; estos registros se excluyen de la cohorte final y se
documentan aquí y en la sección de calidad de los datos.
fecha_valida <- function(x) grepl("^\\d{4}-\\d{2}-\\d{2}$", x)
malas <- !fecha_valida(candidatos$FECHA_INGRESO) |
!fecha_valida(candidatos$FECHA_NACIMIENTO) |
!fecha_valida(candidatos$FECHAALTA)
n_malas <- sum(malas)
cohorte <- candidatos[!malas]
n_final <- nrow(cohorte)
cat("Episodios excluidos por fecha no parseable ('DESCONOCIDO'):", fnum(n_malas), "\n")
## Episodios excluidos por fecha no parseable ('DESCONOCIDO'): 3
cat("Cohorte final:", fnum(n_final))
## Cohorte final: 52.312
No se detectaron duplicados exactos dentro de la cohorte final al comparar la combinación beneficiario + fecha de ingreso + hospital (ver sección de calidad de los datos para el detalle de esta verificación).
trazabilidad <- data.table(
Etapa = c("1 — Base completa GRD 2024",
"2 — Excluye registros de CMA",
"3 — Candidatos con código I50 en algún diagnóstico (dx. 1 a 35)",
"4 — Excluye episodios con fecha no parseable",
"5 — Cohorte final"),
`Episodios` = fnum(c(n0, n1, n_cand, n_final, n_final)),
`Excluidos en la etapa` = c("—", fnum(n_cma), "(subconjunto, no exclusión)", fnum(n_malas), "—")
)
tabla_html(trazabilidad, caption = "Trazabilidad de la construcción de la cohorte, de la base completa a la cohorte final")
| Etapa | Episodios | Excluidos en la etapa |
|---|---|---|
| 1 — Base completa GRD 2024 | 1.085.813 | — |
| 2 — Excluye registros de CMA | 873.772 | 212.041 |
| 3 — Candidatos con código I50 en algún diagnóstico (dx. 1 a 35) | 52.315 | (subconjunto, no exclusión) |
| 4 — Excluye episodios con fecha no parseable | 52.312 | 3 |
| 5 — Cohorte final | 52.312 | — |
es_principal_final <- grepl("^I50", cohorte$DIAGNOSTICO1)
n_principal <- sum(es_principal_final)
n_secundario <- n_final - n_principal
De los 52.312 episodios de la cohorte final, 11.331 (21,7%) tienen la insuficiencia cardíaca como diagnóstico principal, y 40.981 (78,3%) la registran únicamente como diagnóstico secundario (comorbilidad o complicación asociada a otra causa de hospitalización). Esta distinción es relevante: en el primer grupo la insuficiencia cardíaca motivó la hospitalización; en el segundo, acompañó a episodios cuya causa principal fue otra (por ejemplo, neumonía o insuficiencia respiratoria, como se ve en la sección de caracterización).
pct_base_completa <- 100 * n_final / n0
pct_sin_cma <- 100 * n_final / n1
La cohorte de 52.312 episodios representa:
Ambos porcentajes se informan explícitamente porque tienen denominadores distintos y no deben confundirse entre sí.
df_ps <- data.table(tipo = c("Diagnóstico principal", "Diagnóstico secundario"),
n = c(n_principal, n_secundario))
ggplot(df_ps, aes(x = tipo, y = n, fill = tipo)) +
geom_col(width = 0.55, show.legend = FALSE) +
geom_text(aes(label = fnum(n)), vjust = -0.4, size = 4.2) +
scale_fill_manual(values = paleta[c(1,5)]) +
scale_y_continuous(labels = function(x) fnum(x), expand = expansion(mult = c(0, 0.15))) +
labs(title = "Episodios según rol diagnóstico",
x = NULL, y = "Episodios")
Interpretación: la insuficiencia cardíaca aparece con más frecuencia como diagnóstico secundario (78,3% de la cohorte) que como diagnóstico principal (21,7%). Esto sugiere que, dentro de los egresos GRD 2024, la insuficiencia cardíaca actúa mayoritariamente como comorbilidad relevante que acompaña a otras causas de hospitalización, más que como el motivo primario de ingreso.
La distribución temporal se calculó sobre FECHAALTA
(fecha de egreso), ya que es el campo que define la pertenencia del
episodio al año de la base: el 100% de los egresos hospitalarios tiene
FECHAALTA en 2024, mientras que un porcentaje menor de los
ingresos (FECHA_INGRESO) corresponde a fines de 2023
(hospitalizaciones que se prolongaron entre ambos años). Además de los
conteos, se calcula la participación porcentual de la cohorte
sobre el total de egresos hospitalarios de cada trimestre: un
aumento de episodios I50 no es necesariamente específico de la
enfermedad si la actividad hospitalaria total también crece en el mismo
período: solo la participación porcentual permite distinguir ambos
escenarios.
cohorte[, fecha_alta_d := as.Date(FECHAALTA)]
cohorte[, trimestre := paste0("T", as.integer(substr(quarters(fecha_alta_d), 2, 2)))]
base_hosp[, fecha_alta_d := as.Date(FECHAALTA)]
base_hosp[, trimestre := paste0("T", as.integer(substr(quarters(fecha_alta_d), 2, 2)))]
tot_trim <- base_hosp[, .(total_egresos = .N), by = trimestre]
tab_trim <- cohorte[, .(episodios = .N), by = trimestre][order(trimestre)]
tab_trim <- merge(tab_trim, tot_trim, by = "trimestre", sort = FALSE)
setorder(tab_trim, trimestre)
tab_trim[, participacion := 100 * episodios / total_egresos]
tab_trim[, var_absoluta := episodios - shift(episodios)]
tab_trim[, var_relativa := round(100 * var_absoluta / shift(episodios), 1)]
tab_trim[, var_participacion := round(participacion - shift(participacion), 2)]
tab_trim_show <- copy(tab_trim)
tab_trim_show[, episodios := fnum(episodios)]
tab_trim_show[, total_egresos := fnum(total_egresos)]
tab_trim_show[, participacion := fpct(participacion, 2)]
tab_trim_show[, var_absoluta := ifelse(is.na(var_absoluta), "—", fnum(var_absoluta))]
tab_trim_show[, var_relativa := ifelse(is.na(var_relativa), "—", paste0(ifelse(var_relativa>0,"+",""), fnum(var_relativa,1), "%"))]
tab_trim_show[, var_participacion := ifelse(is.na(var_participacion), "—",
paste0(ifelse(var_participacion>0,"+",""), fnum(var_participacion,2), " p.p."))]
setnames(tab_trim_show, c("Trimestre", "Episodios I50", "Total egresos hospitalarios", "% de I50 sobre el total",
"Var. absoluta episodios", "Var. relativa episodios", "Var. participación (p.p.)"))
tabla_html(tab_trim_show, caption = "Distribución trimestral de la cohorte, participación sobre el total de egresos y variación entre trimestres consecutivos (según fecha de egreso)")
| Trimestre | Episodios I50 | Total egresos hospitalarios | % de I50 sobre el total | Var. absoluta episodios | Var. relativa episodios | Var. participación (p.p.) |
|---|---|---|---|---|---|---|
| T1 | 11.517 | 212.586 | 5,42% | — | — | — |
| T2 | 12.720 | 218.126 | 5,83% | 1.203 | +10,4% | +0,41 p.p. |
| T3 | 14.408 | 221.759 | 6,50% | 1.688 | +13,3% | +0,67 p.p. |
| T4 | 13.667 | 221.301 | 6,18% | -741 | -5,1% | -0,32 p.p. |
p1 <- ggplot(tab_trim, aes(x = trimestre, y = episodios)) +
geom_col(fill = paleta[1], width = 0.55) +
geom_text(aes(label = fnum(episodios)), vjust = -0.4, size = 3.6) +
scale_y_continuous(labels = fnum, expand = expansion(mult = c(0, 0.18))) +
labs(title = "Episodios I50 por trimestre", x = "Trimestre", y = "Episodios")
p2 <- ggplot(tab_trim, aes(x = trimestre, y = participacion, group = 1)) +
geom_line(color = paleta[2], linewidth = 1) +
geom_point(color = paleta[2], size = 2.5) +
geom_text(aes(label = fpct(participacion, 2)), vjust = -1, size = 3.6) +
scale_y_continuous(labels = function(x) fpct(x, 1), expand = expansion(mult = c(0.15, 0.25))) +
labs(title = "Participación de I50 sobre el total de egresos", x = "Trimestre", y = "% del total")
gridExtra::grid.arrange(p1, p2, ncol = 2)
Interpretación: el número de episodios aumenta de forma sostenida entre el primer trimestre (11.517) y el tercero (14.408), con un incremento relativo de 10,4% entre T1 y T2 y de 13,3% entre T2 y T3, para luego descender 5,1% en T4. Estas dos variaciones (T1→T2 y T2→T3) son comparaciones distintas y no deben promediarse ni citarse de forma intercambiable. Este mismo patrón se observa en la participación porcentual de la cohorte sobre el total de egresos hospitalarios: sube de 5,42% en T1 a 6,50% en T3, lo que indica que el aumento no se explica solo por una mayor actividad hospitalaria general en esos trimestres, sino que I50 gana peso relativo dentro de esa actividad. El patrón es compatible con la mayor frecuencia de descompensaciones cardíacas asociadas a cuadros respiratorios invernales, aunque este informe no evalúa causalidad.
Los episodios con insuficiencia cardíaca como diagnóstico principal y
aquellos donde aparece solo como diagnóstico secundario representan
situaciones clínicas distintas (en el primer grupo motivó la
hospitalización; en el segundo acompañó otra causa de ingreso). Por eso,
cada variable de esta sección se describe por separado según ese
rol diagnóstico (rol), y además se contrasta
contra una referencia de comparación: la misma medida
calculada sobre base_hosp, es decir, sobre el total de
egresos hospitalarios de la base 2024 (con y sin I50), que es lo que
permite dimensionar si estos episodios son o no distintos del resto de
la actividad hospitalaria.
cohorte[, fecha_ingreso_d := as.Date(FECHA_INGRESO)]
cohorte[, fecha_nac_d := as.Date(FECHA_NACIMIENTO)]
cohorte[, edad := as.numeric(difftime(fecha_ingreso_d, fecha_nac_d, units = "days")) / 365.25]
cohorte[, estancia := as.numeric(difftime(fecha_alta_d, fecha_ingreso_d, units = "days"))]
cohorte[, peso_grd := as.numeric(gsub(",", ".", IR_29301_PESO))]
cohorte[, rol := factor(ifelse(es_principal_final, "Principal", "Secundario"),
levels = c("Principal", "Secundario"))]
# Puntos de corte con right = FALSE: un episodio de 44,9 años queda en "< 45" y uno de
# 45,0 años en "45-64" (con right = TRUE, que es el valor por defecto, un episodio de 44,3
# años quedaba incorrectamente asignado a "45-64").
cohorte[, grupo_etario := cut(edad,
breaks = c(-Inf, 45, 65, 75, 85, Inf), right = FALSE,
labels = c("< 45", "45-64", "65-74", "75-84", "85 y más"))]
sev_lab <- c("0" = "0 · Sin gravedad", "1" = "1 · Menor", "2" = "2 · Moderada", "3" = "3 · Mayor")
mort_lab <- c("0" = "0 · Sin gravedad", "1" = "1 · Menor", "2" = "2 · Moderada", "3" = "3 · Mayor")
cohorte[, severidad_lab := factor(ifelse(IR_29301_SEVERIDAD %in% names(sev_lab),
sev_lab[IR_29301_SEVERIDAD], "Sin información"),
levels = c(sev_lab, "Sin información"))]
cohorte[, mortalidad_lab := factor(ifelse(IR_29301_MORTALIDAD %in% names(mort_lab),
mort_lab[IR_29301_MORTALIDAD], "Sin información"),
levels = c(mort_lab, "Sin información"))]
# Mismas variables sobre la base general de egresos hospitalarios (con y sin I50), para
# usarlas como referencia de comparación en cada resumen de esta sección.
base_hosp[, fecha_ingreso_d := as.Date(FECHA_INGRESO)]
base_hosp[, fecha_nac_d := as.Date(FECHA_NACIMIENTO)]
base_hosp[, edad := as.numeric(difftime(fecha_ingreso_d, fecha_nac_d, units = "days")) / 365.25]
base_hosp[, estancia := as.numeric(difftime(fecha_alta_d, fecha_ingreso_d, units = "days"))]
base_hosp[, peso_grd := as.numeric(gsub(",", ".", IR_29301_PESO))]
resumen_numerico <- function(x, dec = 0) {
data.table(N = fnum(sum(!is.na(x))),
Mínimo = fnum(min(x, na.rm = TRUE), dec),
`Percentil 25` = fnum(quantile(x, .25, na.rm = TRUE), dec),
Mediana = fnum(median(x, na.rm = TRUE), dec),
Media = fnum(mean(x, na.rm = TRUE), dec),
`Percentil 75` = fnum(quantile(x, .75, na.rm = TRUE), dec),
Máximo = fnum(max(x, na.rm = TRUE), dec))
}
tabla_por_rol <- function(var, dec = 0) {
tab <- cohorte[, resumen_numerico(get(var), dec), by = rol]
ref <- data.table(rol = "Base general (todos los egresos hospitalarios)")
ref <- cbind(ref, resumen_numerico(base_hosp[[var]], dec))
out <- rbind(tab, ref)
setnames(out, "rol", "Grupo")
out
}
tabla_html(tabla_por_rol("edad", 1),
caption = "Edad (años) por rol diagnóstico, con la base general de egresos hospitalarios como referencia (N = observaciones válidas)")
| Grupo | N | Mínimo | Percentil 25 | Mediana | Media | Percentil 75 | Máximo |
|---|---|---|---|---|---|---|---|
| Secundario | 40.981 | 0,0 | 64,3 | 73,6 | 71,7 | 81,6 | 104,4 |
| Principal | 11.331 | 0,0 | 63,5 | 72,8 | 71,4 | 81,3 | 108,0 |
| Base general (todos los egresos hospitalarios) | 873.719 | 0,0 | 25,3 | 44,2 | 44,8 | 66,9 | 108,0 |
ggplot(cohorte[!is.na(grupo_etario)], aes(x = grupo_etario, fill = rol)) +
geom_bar(position = "dodge") +
geom_text(stat = "count", aes(label = fnum(after_stat(count))),
position = position_dodge(width = 0.9), vjust = -0.4, size = 3.2) +
scale_fill_manual(values = paleta[c(1,2)], name = "Rol diagnóstico") +
scale_y_continuous(labels = fnum, expand = expansion(mult = c(0, 0.15))) +
labs(title = "Episodios por grupo etario y rol diagnóstico",
x = "Grupo etario", y = "Episodios")
Interpretación: la edad mediana es similar entre ambos grupos — 72,8 años cuando la insuficiencia cardíaca es diagnóstico principal y 73,6 años cuando es secundario—, pero ambas superan ampliamente la mediana de la base general de egresos hospitalarios (44,2 años). Esto confirma que, independientemente del rol diagnóstico, los episodios con insuficiencia cardíaca ocurren en una población sistemáticamente mayor que la del resto de la actividad hospitalaria. En ambos grupos, más de dos tercios de los episodios ocurre en personas de 65 años o más.
tab_sexo_rol <- cohorte[, .(episodios = .N), by = .(rol, SEXO)]
tab_sexo_rol[, pct_dentro_rol := 100 * episodios / sum(episodios), by = rol]
tab_sexo_wide <- dcast(tab_sexo_rol, SEXO ~ rol, value.var = c("episodios", "pct_dentro_rol"), fill = 0)
tab_sexo_show <- tab_sexo_wide[, .(SEXO,
`Principal (n)` = fnum(episodios_Principal),
`Principal (%)` = fpct(pct_dentro_rol_Principal),
`Secundario (n)` = fnum(episodios_Secundario),
`Secundario (%)` = fpct(pct_dentro_rol_Secundario))]
tabla_html(tab_sexo_show, caption = "Episodios por sexo y rol diagnóstico (% calculado dentro de cada rol)")
| SEXO | Principal (n) | Principal (%) | Secundario (n) | Secundario (%) |
|---|---|---|---|---|
| DESCONOCIDO | 0 | 0,0% | 1 | 0,0% |
| HOMBRE | 5.824 | 51,4% | 20.870 | 50,9% |
| MUJER | 5.507 | 48,6% | 20.110 | 49,1% |
ggplot(tab_sexo_rol, aes(x = reorder(SEXO, -episodios), y = episodios, fill = rol)) +
geom_col(position = "dodge") +
geom_text(aes(label = fnum(episodios)), position = position_dodge(width = 0.9), vjust = -0.4, size = 3.4) +
scale_fill_manual(values = paleta[c(1,2)], name = "Rol diagnóstico") +
scale_y_continuous(labels = fnum, expand = expansion(mult = c(0, 0.15))) +
labs(title = "Episodios por sexo y rol diagnóstico", x = NULL, y = "Episodios")
Interpretación: la distribución por sexo es
prácticamente idéntica en ambos grupos — alrededor de 51% hombres y 49%
mujeres tanto cuando la insuficiencia cardíaca es diagnóstico principal
como cuando es secundario—, por lo que el rol diagnóstico no está
asociado a una composición por sexo distinta. El registro sin
información (SEXO = "DESCONOCIDO") es marginal (un solo
episodio).
La estancia es una variable con distribución asimétrica hacia la derecha (algunos episodios muy prolongados), por lo que se reporta mediana y rango intercuartílico en vez del promedio, conforme al criterio estadístico de la pauta.
tabla_html(tabla_por_rol("estancia", 0),
caption = "Estancia (días) por rol diagnóstico, con la base general de egresos hospitalarios como referencia (N = observaciones válidas)")
| Grupo | N | Mínimo | Percentil 25 | Mediana | Media | Percentil 75 | Máximo |
|---|---|---|---|---|---|---|---|
| Secundario | 40.981 | 0 | 3 | 7 | 12 | 14 | 645 |
| Principal | 11.331 | 0 | 3 | 6 | 8 | 10 | 263 |
| Base general (todos los egresos hospitalarios) | 873.720 | 0 | 2 | 3 | 7 | 7 | 661 |
p99_est <- quantile(cohorte$estancia, .99, na.rm = TRUE)
ggplot(cohorte[estancia <= p99_est], aes(x = estancia, fill = rol)) +
geom_histogram(binwidth = 2, boundary = 0, position = "identity", alpha = 0.55) +
geom_vline(data = cohorte[, .(mediana = median(estancia, na.rm = TRUE)), by = rol],
aes(xintercept = mediana, color = rol), linetype = "dashed", linewidth = 0.8) +
scale_fill_manual(values = paleta[c(1,2)], name = "Rol diagnóstico") +
scale_color_manual(values = paleta[c(1,2)], guide = "none") +
labs(title = "Distribución de la estancia hospitalaria por rol diagnóstico",
subtitle = "Recortada al percentil 99 para visualización; líneas punteadas = mediana de cada grupo",
x = "Estancia (días)", y = "Episodios")
Interpretación: la estancia mediana es de 7 días cuando la insuficiencia cardíaca es diagnóstico secundario (RIC 3-14) y de 6 días cuando es diagnóstico principal (RIC 3-10). La estancia es más larga cuando I50 es secundaria, lo esperable si la duración depende principalmente de la causa de ingreso y la descompensación cardíaca se suma como complicación. Ambos grupos superan con holgura la mediana de la base general de egresos hospitalarios (3 días, RIC 2-7), lo que confirma que los episodios de esta cohorte —con cualquier rol diagnóstico— son más prolongados que el resto de la actividad hospitalaria. En ambos grupos la media supera bastante a la mediana, lo que confirma la asimetría de la variable y justifica reportar mediana y RIC en lugar del promedio.
tab_alta <- cohorte[, .(episodios = .N), by = TIPOALTA][order(-episodios)]
tab_alta[, pct := fpct(100*episodios/n_final)]
tabla_html(copy(tab_alta)[, episodios := fnum(episodios)],
caption = "Condición de egreso de la cohorte final (% sobre el total de la cohorte)")
| TIPOALTA | episodios | pct |
|---|---|---|
| DOMICILIO | 36.464 | 69,7% |
| HOSPITALIZACIÓN DOMICILIARIA | 5.827 | 11,1% |
| FALLECIDO | 5.231 | 10,0% |
| DERIVACIÓN OTRO HOSPITAL DEL SERVICIO | 2.617 | 5,0% |
| DERIVACIÓN OTRO HOSPITAL DE LA RED NACIONAL | 838 | 1,6% |
| ALTA VOLUNTARIA | 594 | 1,1% |
| DERIVACIÓN INST. PRIVADA (COMPRA DE SERVICIOS | 419 | 0,8% |
| DERIVACIÓN A OTROS CENTROS (CÁRCEL, HOGAR DE | 186 | 0,4% |
| FUGA DEL PACIENTE | 86 | 0,2% |
| DERIVACIÓN INST. PRIVADA (VOLUNTARIO) | 50 | 0,1% |
ggplot(tab_alta, aes(x = reorder(TIPOALTA, episodios), y = episodios)) +
geom_col(fill = paleta[1]) +
coord_flip() +
scale_x_discrete(labels = wrap_lbl) +
scale_y_continuous(labels = fnum, expand = expansion(mult = c(0, 0.12))) +
labs(title = "Condición de egreso de los episodios",
x = NULL, y = "Episodios")
La condición de egreso —y en particular la mortalidad intrahospitalaria— también se describe por separado según el rol diagnóstico, porque agregarla en una sola cifra mezcla dos poblaciones con severidad de base distinta.
tab_alta_rol <- cohorte[, .(episodios = .N), by = .(rol, TIPOALTA)]
tab_alta_rol[, pct := 100 * episodios / sum(episodios), by = rol]
tab_mort_rol <- cohorte[, .(episodios = .N, fallecidos = sum(TIPOALTA == "FALLECIDO")), by = rol]
tab_mort_rol[, pct_mortalidad := fpct(100 * fallecidos / episodios, 1)]
tab_mort_show <- copy(tab_mort_rol)[, `:=`(episodios = fnum(episodios), fallecidos = fnum(fallecidos))]
setnames(tab_mort_show, c("Rol diagnóstico", "Episodios", "Fallecidos", "% mortalidad intrahospitalaria"))
tabla_html(tab_mort_show, caption = "Mortalidad intrahospitalaria por rol diagnóstico")
| Rol diagnóstico | Episodios | Fallecidos | % mortalidad intrahospitalaria |
|---|---|---|---|
| Secundario | 40.981 | 4.423 | 10,8% |
| Principal | 11.331 | 808 | 7,1% |
ggplot(tab_alta_rol, aes(x = reorder(TIPOALTA, pct), y = pct, fill = rol)) +
geom_col(position = "dodge") +
coord_flip() +
scale_x_discrete(labels = wrap_lbl) +
scale_fill_manual(values = paleta[c(1,2)], name = "Rol diagnóstico") +
scale_y_continuous(labels = function(x) fpct(x, 0), expand = expansion(mult = c(0, 0.12))) +
labs(title = "Condición de egreso por rol diagnóstico",
subtitle = "% calculado dentro de cada rol (no sobre el total de la cohorte)",
x = NULL, y = "% dentro del rol")
Interpretación: la mayoría de los episodios termina en alta a domicilio (69,7% del total de la cohorte), pero la mortalidad intrahospitalaria difiere claramente según el rol diagnóstico: 10,8% de los episodios donde I50 es diagnóstico secundario termina en fallecimiento, frente a 7,1% cuando es diagnóstico principal. Esto es clínicamente esperable: cuando la insuficiencia cardíaca complica otra causa de ingreso, suele reflejar un paciente de base más grave que cuando la insuficiencia cardíaca es en sí misma el motivo de hospitalización. Una cifra única de mortalidad para toda la cohorte ocultaría esta diferencia. La hospitalización domiciliaria como destino de alta también es algo más frecuente en el grupo secundario, consistente con estancias más prolongadas y mayor comorbilidad.
tabla_html(tabla_por_rol("peso_grd", 2),
caption = "Peso GRD por rol diagnóstico, con la base general de egresos hospitalarios como referencia (N = observaciones válidas)")
| Grupo | N | Mínimo | Percentil 25 | Mediana | Media | Percentil 75 | Máximo |
|---|---|---|---|---|---|---|---|
| Secundario | 40.979 | 0,35 | 0,79 | 1,07 | 1,58 | 1,65 | 17,14 |
| Principal | 11.331 | 0,47 | 0,63 | 1,01 | 1,12 | 1,01 | 17,14 |
| Base general (todos los egresos hospitalarios) | 873.757 | 0,00 | 0,52 | 0,77 | 1,07 | 1,15 | 20,65 |
tab_sev <- cohorte[, .(episodios = .N), by = .(rol, severidad_lab)]
tab_sev[, pct := 100 * episodios / sum(episodios), by = rol]
tab_sev_wide <- dcast(tab_sev, severidad_lab ~ rol, value.var = c("episodios", "pct"), fill = 0)
tab_sev_show <- tab_sev_wide[, .(`Nivel de severidad` = severidad_lab,
`Principal (n)` = fnum(episodios_Principal),
`Principal (%)` = fpct(pct_Principal),
`Secundario (n)` = fnum(episodios_Secundario),
`Secundario (%)` = fpct(pct_Secundario))]
tabla_html(tab_sev_show, caption = "Episodios por nivel de severidad GRD y rol diagnóstico (% dentro de cada rol)")
| Nivel de severidad | Principal (n) | Principal (%) | Secundario (n) | Secundario (%) |
|---|---|---|---|---|
| 1 · Menor | 721 | 6,4% | 13 | 0,0% |
| 2 · Moderada | 2.781 | 24,5% | 17.050 | 41,6% |
| 3 · Mayor | 7.829 | 69,1% | 23.916 | 58,4% |
| Sin información | 0 | 0,0% | 2 | 0,0% |
El peso GRD de un episodio se calcula, precisamente, a partir de su código GRD y de su nivel de severidad; por eso la relación entre ambas variables es en gran medida definicional y no un hallazgo independiente. Se muestra igualmente porque permite verificar que el agrupador se comportó como se espera dentro de esta cohorte, pero la comparación relevante para dimensionar la enfermedad es la de la tabla anterior: el peso GRD de la cohorte frente al de la base general.
ggplot(cohorte, aes(x = severidad_lab, y = peso_grd, fill = rol)) +
geom_boxplot(outlier.alpha = 0.12, outlier.size = 0.5, position = position_dodge(width = 0.8)) +
scale_fill_manual(values = paleta[c(1,2)], name = "Rol diagnóstico") +
labs(title = "Peso GRD según nivel de severidad y rol diagnóstico",
x = "Nivel de severidad", y = "Peso GRD") +
theme(axis.text.x = element_text(angle = 15, hjust = 1))
Interpretación: el peso GRD mediano de la cohorte (1,01) es claramente superior al de la base general de egresos hospitalarios (0,77), lo que indica que, en promedio, estos episodios consumen más recursos que el episodio hospitalario general de la base 2024 —esta es la comparación que dimensiona la carga de la enfermedad, y no la relación con la severidad, que es esperable por construcción del agrupador. Dentro de cada nivel de severidad, los episodios donde I50 es diagnóstico secundario muestran pesos algo más altos y dispersos que aquellos donde es principal, compatible con la comorbilidad adicional que suele acompañar a esos casos.
irgrd <- as.data.table(read_excel("Tablas_maestras_GRD.xlsx", sheet = "IR - GRD"))
setnames(irgrd, c("cod_grd", "descripcion_grd"))
irgrd[, cod_grd := as.character(cod_grd)]
cohorte[, cod_grd_norm := sub("^0+", "", IR_29301_COD_GRD)]
top_grd <- cohorte[, .(episodios = .N), by = cod_grd_norm][order(-episodios)][1:8]
top_grd <- merge(top_grd, irgrd, by.x = "cod_grd_norm", by.y = "cod_grd", all.x = TRUE, sort = FALSE)
top_grd <- top_grd[order(-episodios)]
setnames(top_grd, "cod_grd_norm", "Código GRD")
setnames(top_grd, "descripcion_grd", "Descripción")
tabla_html(copy(top_grd)[, episodios := fnum(episodios)][, .(`Código GRD`, Descripción, episodios)],
caption = "GRD más frecuentes dentro de la cohorte de insuficiencia cardíaca (top 8)")
| Código GRD | Descripción | episodios |
|---|---|---|
| 54123 | MH INSUFICIENCIA CARDIACA W/MCC | 6.946 |
| 44163 | MH NEUMONÍA SIMPLE Y TOS FERINA W/MCC | 2.738 |
| 54122 | MH INSUFICIENCIA CARDIACA W/CC | 2.450 |
| 44173 | MH ENFERMEDAD PULMONAR OBSTRUCTIVA CRÓNICA W/MCC | 1.534 |
| 41023 | PH VENTILACIÓN MECÁNICA PROLONGADA SIN TRAQUEOSTOMÍA W/MCC | 1.431 |
| 41203 | PH PROCEDIMIENTOS NO COMPLEJOS SOBRE APARATO RESPIRATORIO W/MCC | 1.215 |
| 51152 | PH CATETERISMO CARDIACO W/CC | 1.000 |
| 44153 | MH INFECCIONES E INFLAMACIONES RESPIRATORIAS W/MCC | 939 |
Interpretación: los dos GRD más frecuentes corresponden directamente a insuficiencia cardíaca con y sin complicación mayor (MH Insuficiencia Cardíaca W/MCC y W/CC), coherente con los episodios donde la enfermedad es diagnóstico principal. El resto de la lista está dominado por GRD respiratorios (neumonía, EPOC, ventilación mecánica prolongada) y cardiovasculares (cateterismo, procedimientos percutáneos, arritmias), lo que confirma que buena parte de la cohorte corresponde a episodios donde la insuficiencia cardíaca es una comorbilidad relevante de otra causa de ingreso.
ss_region <- c(
"ARICA" = "Arica y Parinacota",
"IQUIQUE" = "Tarapacá",
"ANTOFAGASTA" = "Antofagasta",
"ATACAMA" = "Atacama",
"COQUIMBO" = "Coquimbo",
"VALPARAISO SAN ANTONIO" = "Valparaíso",
"VIÑA DEL MAR QUILLOTA" = "Valparaíso",
"ACONCAGUA" = "Valparaíso",
"METROPOLITANO NORTE" = "Metropolitana",
"METROPOLITANO OCCIDENTE" = "Metropolitana",
"METROPOLITANO CENTRAL" = "Metropolitana",
"METROPOLITANO ORIENTE" = "Metropolitana",
"METROPOLITANO SUR" = "Metropolitana",
"METROPOLITANO SURORIENTE" = "Metropolitana",
"LIBERTADOR B. O HIGGINS" = "O'Higgins",
"DEL MAULE" = "Maule",
"ÑUBLE" = "Ñuble",
"CONCEPCIÓN" = "Biobío",
"TALCAHUANO" = "Biobío",
"BIOBIO" = "Biobío",
"ARAUCO" = "Biobío",
"ARAUCANÍA NORTE" = "Araucanía",
"ARAUCANÍA SUR" = "Araucanía",
"DEL RELONCAVÍ" = "Los Lagos",
"OSORNO" = "Los Lagos",
"CHILOÉ" = "Los Lagos",
"VALDIVIA" = "Los Ríos",
"AYSEN" = "Aysén",
"MAGALLANES" = "Magallanes"
)
cohorte[, region := ifelse(SERVICIO_SALUD %in% names(ss_region), ss_region[SERVICIO_SALUD], "Sin información")]
tab_ss <- cohorte[, .(episodios = .N), by = SERVICIO_SALUD][order(-episodios)][1:10]
tabla_html(copy(tab_ss)[, episodios := fnum(episodios)],
caption = "Servicios de salud con mayor número de episodios (top 10)")
| SERVICIO_SALUD | episodios |
|---|---|
| METROPOLITANO SURORIENTE | 5.078 |
| DEL MAULE | 4.384 |
| METROPOLITANO SUR | 3.619 |
| METROPOLITANO OCCIDENTE | 3.293 |
| METROPOLITANO CENTRAL | 2.918 |
| CONCEPCIÓN | 2.674 |
| ARAUCANÍA SUR | 2.619 |
| LIBERTADOR B. O HIGGINS | 2.218 |
| METROPOLITANO ORIENTE | 2.184 |
| ÑUBLE | 2.127 |
tab_region <- cohorte[, .(episodios = .N), by = region][order(-episodios)]
tabla_html(copy(tab_region)[, episodios := fnum(episodios)],
caption = "Episodios por región (agregación de servicios de salud)")
| region | episodios |
|---|---|
| Metropolitana | 18.455 |
| Biobío | 6.831 |
| Valparaíso | 5.024 |
| Maule | 4.384 |
| Araucanía | 3.361 |
| Los Lagos | 3.334 |
| O’Higgins | 2.218 |
| Ñuble | 2.127 |
| Coquimbo | 1.778 |
| Los Ríos | 1.115 |
| Antofagasta | 862 |
| Atacama | 741 |
| Magallanes | 624 |
| Tarapacá | 602 |
| Arica y Parinacota | 500 |
| Aysén | 335 |
| Sin información | 21 |
ggplot(tab_ss, aes(x = reorder(SERVICIO_SALUD, episodios), y = episodios)) +
geom_col(fill = paleta[1]) +
coord_flip() +
scale_y_continuous(labels = fnum, expand = expansion(mult = c(0, 0.12))) +
labs(title = "Servicios de salud con mayor número de episodios",
x = NULL, y = "Episodios")
Interpretación: el Servicio de Salud Metropolitano Suroriente concentra el mayor número absoluto de episodios (5.078), seguido por Del Maule y Metropolitano Sur. A nivel de región, la Región Metropolitana concentra la mayor cantidad de episodios en términos absolutos —esperable por su mayor población y oferta hospitalaria—, pero esto no implica necesariamente una mayor concentración relativa de la enfermedad, como se examina a continuación con los establecimientos.
hosp <- as.data.table(read_excel("Tablas_maestras_GRD.xlsx", sheet = "Hospitales"))
setnames(hosp, c("cod_hospital", "nombre_hospital"))
hosp[, cod_hospital := as.character(as.integer(cod_hospital))]
top_hosp <- cohorte[, .(episodios = .N), by = COD_HOSPITAL][order(-episodios)][1:10]
top_hosp <- merge(top_hosp, hosp, by.x = "COD_HOSPITAL", by.y = "cod_hospital", all.x = TRUE, sort = FALSE)
top_hosp[is.na(nombre_hospital), nombre_hospital := paste0("Cód. ", COD_HOSPITAL, " (sin nombre en tabla maestra)")]
top_hosp <- top_hosp[order(-episodios)]
tabla_html(copy(top_hosp)[, episodios := fnum(episodios)][, .(Establecimiento = nombre_hospital, `Código` = COD_HOSPITAL, episodios)],
caption = "Establecimientos con mayor número absoluto de episodios (top 10)")
| Establecimiento | Código | episodios |
|---|---|---|
| Complejo Hospitalario Dr. Sótero del Río (Santiago, Puente Alto) | 114101 | 3.138 |
| Hospital Clínico Regional Dr. Guillermo Grant Benavente (Concepción) | 118100 | 2.078 |
| Hospital Barros Luco Trudeau (Santiago, San Miguel) | 113100 | 1.987 |
| Hospital Regional de Rancagua | 115100 | 1.611 |
| Complejo Asistencial Dr. Víctor Ríos Ruiz (Los Ángeles) | 120101 | 1.606 |
| Hospital Dr. Félix Bulnes Cerda (Santiago, Quinta Normal) | 110120 | 1.492 |
| Hospital de Puerto Montt | 124105 | 1.475 |
| Hospital Dr. César Garavagno Burotto (Talca) | 116105 | 1.452 |
| Hospital Las Higueras (Talcahuano) | 119100 | 1.386 |
| Hospital Clínico Herminda Martín (Chillán) | 117101 | 1.382 |
ggplot(top_hosp, aes(x = reorder(nombre_hospital, episodios), y = episodios)) +
geom_col(fill = paleta[1]) +
coord_flip() +
scale_x_discrete(labels = wrap_lbl) +
scale_y_continuous(labels = fnum, expand = expansion(mult = c(0, 0.12))) +
labs(title = "Establecimientos con mayor número de episodios",
x = NULL, y = "Episodios")
Interpretación: el Complejo Hospitalario Dr. Sótero del Río concentra el mayor volumen absoluto de episodios (3.138), seguido por el Hospital Regional de Concepción y el Hospital Barros Luco Trudeau. Son, en general, grandes hospitales de alta complejidad con población asignada numerosa, por lo que su volumen alto es esperable y no implica por sí solo una mayor concentración relativa de la enfermedad dentro de su propia actividad.
La estabilidad de una proporción depende del tamaño de su denominador (el total de egresos del establecimiento), no del numerador (los episodios de I50). Restringir el análisis a establecimientos con muchos episodios de I50 no evita que un establecimiento con un denominador pequeño muestre un porcentaje inestable; por eso el umbral se aplica sobre el total de egresos del establecimiento, y cada proporción se acompaña de un intervalo de confianza del 95% (método de Wilson) que cuantifica esa incertidumbre.
tot_hosp <- base_hosp[, .(total_egresos = .N), by = COD_HOSPITAL]
prop_hosp <- cohorte[, .(episodios_i50 = .N), by = COD_HOSPITAL]
prop_hosp <- merge(prop_hosp, tot_hosp, by = "COD_HOSPITAL")
prop_hosp[, pct := 100 * episodios_i50 / total_egresos]
prop_hosp <- cbind(prop_hosp, wilson_ci(prop_hosp$episodios_i50, prop_hosp$total_egresos))
prop_hosp <- merge(prop_hosp, hosp, by.x = "COD_HOSPITAL", by.y = "cod_hospital", all.x = TRUE, sort = FALSE)
prop_hosp[is.na(nombre_hospital), nombre_hospital := paste0("Cód. ", COD_HOSPITAL)]
umbral_egresos <- 500
top_prop <- prop_hosp[total_egresos >= umbral_egresos][order(-pct)][1:10]
top_prop[, ic95 := paste0(fnum(ci_lo,1), "%-", fnum(ci_hi,1), "%")]
tabla_html(copy(top_prop)[, `:=`(episodios_i50 = fnum(episodios_i50),
total_egresos = fnum(total_egresos),
pct = fpct(pct))][, .(Establecimiento = nombre_hospital,
`Episodios I50` = episodios_i50,
`Total egresos del establecimiento` = total_egresos,
`% de su propia actividad` = pct,
`IC 95% (Wilson)` = ic95)],
caption = paste0("Establecimientos donde la insuficiencia cardíaca representa mayor proporción de su propia actividad (mínimo ",
fnum(umbral_egresos), " egresos hospitalarios totales en el establecimiento)"))
| Establecimiento | Episodios I50 | Total egresos del establecimiento | % de su propia actividad | IC 95% (Wilson) |
|---|---|---|---|---|
| Instituto Nacional de Enfermedades Respiratorias y Cirugía Torácica | 717 | 3.951 | 18,1% | 17,0%-19,4% |
| Hospital de Constitución | 369 | 2.796 | 13,2% | 12,0%-14,5% |
| Hospital de Tomé | 375 | 3.210 | 11,7% | 10,6%-12,8% |
| Hospital de Pitrufquén | 311 | 2.904 | 10,7% | 9,6%-11,9% |
| Hospital de Urgencia Asistencia Pública Dr. Alejandro del Río | 1.237 | 11.565 | 10,7% | 10,1%-11,3% |
| Hospital Penco - Lirquén | 293 | 2.751 | 10,7% | 9,6%-11,9% |
| Hospital de Lota | 406 | 4.113 | 9,9% | 9,0%-10,8% |
| Hospital de San Carlos | 695 | 7.112 | 9,8% | 9,1%-10,5% |
| Hospital Intercultural de Nueva Imperial | 396 | 4.133 | 9,6% | 8,7%-10,5% |
| Hospital Dr. Abraham Godoy (Lautaro) | 255 | 2.724 | 9,4% | 8,3%-10,5% |
Interpretación: el establecimiento con mayor peso relativo es el Instituto Nacional de Enfermedades Respiratorias y Cirugía Torácica, donde la insuficiencia cardíaca explica 18,1% de sus egresos hospitalarios totales (IC 95% 17,0%-19,4%), muy por encima de hospitales con volumen absoluto mucho mayor pero actividad más diversificada (como el Complejo Sótero del Río, donde la enfermedad representa una fracción menor de su actividad total). Que el primer lugar corresponda a un instituto especializado en enfermedades respiratorias —no a un hospital general— es un motivo adicional para interpretar este ranking con cautela: su actividad no es representativa de un hospital general y la comorbilidad cardiorrespiratoria puede sobrerrepresentar a I50 en su casuística. En este caso el umbral por denominador no cambia la lista respecto de exigir un mínimo de casos de I50, porque los 72 establecimientos que registran al menos un episodio I50 tienen, todos, más de 500 egresos totales; el intervalo de confianza es, de todas formas, la forma correcta de comunicar cuánta certeza hay detrás de cada porcentaje, independientemente del umbral usado para filtrar la tabla.
Esta comparación se restringe a los diez establecimientos de mayor volumen, de modo que describe la variabilidad dentro de ese subgrupo y no necesariamente la del sistema completo; los establecimientos con menor volumen (la mayoría de los 72 que registran algún episodio I50) no están representados aquí.
var_hosp <- cohorte[, .(episodios = .N,
mediana_estancia = median(estancia, na.rm = TRUE),
ric_estancia = paste0(fnum(quantile(estancia,.25,na.rm=TRUE)), "-", fnum(quantile(estancia,.75,na.rm=TRUE))),
mediana_peso = round(median(peso_grd, na.rm = TRUE), 2)),
by = COD_HOSPITAL][episodios >= 30]
var_hosp <- merge(var_hosp, hosp, by.x = "COD_HOSPITAL", by.y = "cod_hospital", all.x = TRUE, sort = FALSE)
var_hosp[is.na(nombre_hospital), nombre_hospital := paste0("Cód. ", COD_HOSPITAL)]
var_hosp <- var_hosp[order(-episodios)][1:10]
tabla_html(copy(var_hosp)[, episodios := fnum(episodios)][, .(Establecimiento = nombre_hospital, episodios,
`Mediana estancia (días)` = mediana_estancia,
`RIC estancia` = ric_estancia,
`Mediana peso GRD` = mediana_peso)],
caption = "Estancia y peso GRD por establecimiento, top 10 establecimientos por volumen (mínimo 30 episodios)")
| Establecimiento | episodios | Mediana estancia (días) | RIC estancia | Mediana peso GRD |
|---|---|---|---|---|
| Complejo Hospitalario Dr. Sótero del Río (Santiago, Puente Alto) | 3.138 | 4 | 1-10 | 1.07 |
| Hospital Clínico Regional Dr. Guillermo Grant Benavente (Concepción) | 2.078 | 6 | 3-12 | 1.10 |
| Hospital Barros Luco Trudeau (Santiago, San Miguel) | 1.987 | 7 | 3-15 | 1.17 |
| Hospital Regional de Rancagua | 1.611 | 5 | 3-10 | 1.01 |
| Complejo Asistencial Dr. Víctor Ríos Ruiz (Los Ángeles) | 1.606 | 6 | 4-11 | 1.07 |
| Hospital Dr. Félix Bulnes Cerda (Santiago, Quinta Normal) | 1.492 | 7 | 3-12 | 1.01 |
| Hospital de Puerto Montt | 1.475 | 7 | 4-13 | 1.01 |
| Hospital Dr. César Garavagno Burotto (Talca) | 1.452 | 8 | 4-14 | 1.03 |
| Hospital Las Higueras (Talcahuano) | 1.386 | 6 | 3-11 | 1.01 |
| Hospital Clínico Herminda Martín (Chillán) | 1.382 | 6 | 3-11 | 1.01 |
resumen_var <- var_hosp[, .(mediana_de_medianas = median(mediana_estancia),
p25_de_medianas = quantile(mediana_estancia, .25),
p75_de_medianas = quantile(mediana_estancia, .75))]
Resumir estas diez medianas solo con su rango (mínimo y máximo) depende exclusivamente de los dos establecimientos más extremos y puede ser inestable; por eso se resumen también con su propia mediana y rango intercuartílico, y se muestra la distribución completa de la estancia (no solo el punto de la mediana) en cada uno de estos establecimientos.
ggplot(cohorte[COD_HOSPITAL %in% var_hosp$COD_HOSPITAL], aes(x = reorder(COD_HOSPITAL, estancia, FUN = median), y = estancia)) +
geom_boxplot(fill = paleta[1], outlier.alpha = 0.1, outlier.size = 0.5) +
scale_x_discrete(labels = function(x) wrap_lbl(var_hosp$nombre_hospital[match(x, var_hosp$COD_HOSPITAL)], width = 22)) +
coord_flip(ylim = c(0, quantile(cohorte$estancia, .97, na.rm = TRUE))) +
labs(title = "Distribución de la estancia por establecimiento",
subtitle = "Los diez establecimientos de mayor volumen; eje recortado al percentil 97 para visualización",
x = NULL, y = "Estancia (días)")
Interpretación: entre estos diez establecimientos, la mediana de estancia va de 4 a 8 días. Esas diez medianas tienen, a su vez, una mediana de 6 días (RIC 6-7), un resumen menos sensible a un único establecimiento atípico que el rango. El boxplot muestra además que la dispersión dentro de cada establecimiento es, en general, mayor que la diferencia entre sus medianas: la variabilidad intra-establecimiento domina por sobre la variabilidad entre-establecimientos. Estas diferencias pueden reflejar distinta complejidad de los casos atendidos, distinta disponibilidad de cupos de hospitalización domiciliaria o de camas de continuidad de cuidados, y no deben interpretarse como una medida directa de calidad de atención sin un ajuste por case-mix, que excede el alcance descriptivo de este informe. Tampoco deben generalizarse al resto de los establecimientos de la base, que no forman parte de esta comparación.
n_sin_info <- c(
sum(cohorte$SEXO == "DESCONOCIDO"),
sum(cohorte$SERVICIO_SALUD == "DESCONOCIDO"),
sum(cohorte$TIPO_INGRESO == "DESCONOCIDO"),
sum(cohorte$IR_29301_SEVERIDAD == "DESCONOCIDO"),
sum(cohorte$IR_29301_MORTALIDAD == "DESCONOCIDO"),
sum(is.na(cohorte$peso_grd)),
sum(!fecha_valida(candidatos$FECHA_INGRESO)),
sum(!fecha_valida(candidatos$FECHA_NACIMIENTO))
)
denom_sin_info <- c(n_final, n_final, n_final, n_final, n_final, n_final, n_cand, n_cand)
calidad <- data.table(
Variable = c("SEXO", "SERVICIO_SALUD", "TIPO_INGRESO", "IR_29301_SEVERIDAD",
"IR_29301_MORTALIDAD", "IR_29301_PESO (peso GRD, tras as.numeric)",
"FECHA_INGRESO (previo a depuración)", "FECHA_NACIMIENTO (previo a depuración)"),
`Base de cálculo` = c(rep("Cohorte final", 6), rep("Candidatos (pre-depuración)", 2)),
`Sin información / DESCONOCIDO` = fnum(n_sin_info),
`%` = fpct(100 * n_sin_info / denom_sin_info, 2)
)
tabla_html(calidad, caption = "Valores sin información por variable utilizada en el análisis (% calculado sobre la base indicada en cada fila)")
| Variable | Base de cálculo | Sin información / DESCONOCIDO | % |
|---|---|---|---|
| SEXO | Cohorte final | 1 | 0,00% |
| SERVICIO_SALUD | Cohorte final | 21 | 0,04% |
| TIPO_INGRESO | Cohorte final | 3 | 0,01% |
| IR_29301_SEVERIDAD | Cohorte final | 2 | 0,00% |
| IR_29301_MORTALIDAD | Cohorte final | 2 | 0,00% |
| IR_29301_PESO (peso GRD, tras as.numeric) | Cohorte final | 2 | 0,00% |
| FECHA_INGRESO (previo a depuración) | Candidatos (pre-depuración) | 2 | 0,00% |
| FECHA_NACIMIENTO (previo a depuración) | Candidatos (pre-depuración) | 1 | 0,00% |
Todas las medidas resumen de esta sección (mediana, media, cuantiles)
se calcularon con na.rm = TRUE, lo que excluye del cálculo
los valores faltantes de esta tabla. Por eso cada tabla de resumen de la
sección “Caracterización de los episodios” incluye una columna
N con el número de observaciones efectivamente utilizadas
en esa fila, que en general es muy cercano al tamaño de la cohorte o del
grupo, salvo en IR_29301_PESO, donde la conversión con
as.numeric() puede transformar en NA valores
no numéricos de origen (por ejemplo, el texto
"DESCONOCIDO"); esa columna N es la forma de
detectar ese efecto sin que quede oculto en la tabla.
n_grd_desconocido <- sum(cohorte$IR_29301_COD_GRD == "DESCONOCIDO")
n_hosp_sin_tabla <- sum(!(cohorte$COD_HOSPITAL %in% hosp$cod_hospital))
cat("Episodios con código GRD 'DESCONOCIDO' (sin agrupador asignado):", fnum(n_grd_desconocido), "\n")
## Episodios con código GRD 'DESCONOCIDO' (sin agrupador asignado): 2
cat("Episodios cuyo código de hospital no aparece en la tabla maestra de establecimientos:", fnum(n_hosp_sin_tabla))
## Episodios cuyo código de hospital no aparece en la tabla maestra de establecimientos: 514
Los episodios sin código GRD asignado (2) y aquellos cuyo hospital no figura en la tabla maestra (514, correspondiente al código 200717) se mantienen en la cohorte —porque el diagnóstico I50 sí está identificado— pero se excluyen o se marcan como “sin información” en las tablas y figuras que dependen específicamente de esas variables (GRD y nombre de establecimiento, respectivamente).
dup_key_base <- base_hosp[, .(N = .N, n_diag1 = uniqueN(DIAGNOSTICO1), n_tipoalta = uniqueN(TIPOALTA)),
by = .(ID_BENEFICIARIO, FECHA_INGRESO, COD_HOSPITAL)]
dup_grupos <- dup_key_base[N > 1]
n_grupos_dup <- nrow(dup_grupos)
n_filas_dup <- sum(dup_grupos$N)
n_dup_iguales <- sum(dup_grupos$n_diag1 == 1 & dup_grupos$n_tipoalta == 1)
n_dup_diag_distinto <- sum(dup_grupos$n_diag1 > 1)
dup_key_cohorte <- cohorte[, .N, by = .(ID_BENEFICIARIO, FECHA_INGRESO, COD_HOSPITAL)]
n_dup_cohorte <- sum(dup_key_cohorte$N > 1)
cat("Base de egresos hospitalarios: combinaciones beneficiario+ingreso+hospital repetidas:",
fnum(n_grupos_dup), "(", fnum(n_filas_dup), "filas involucradas )\n")
## Base de egresos hospitalarios: combinaciones beneficiario+ingreso+hospital repetidas: 415 ( 874 filas involucradas )
cat("De esas combinaciones, con el mismo DIAGNOSTICO1 y la misma TIPOALTA (más parecido a un duplicado exacto):",
fnum(n_dup_iguales), "\n")
## De esas combinaciones, con el mismo DIAGNOSTICO1 y la misma TIPOALTA (más parecido a un duplicado exacto): 38
cat("Con DIAGNOSTICO1 distinto entre las filas del grupo (compatible con registros clínicos distintos):",
fnum(n_dup_diag_distinto), "\n")
## Con DIAGNOSTICO1 distinto entre las filas del grupo (compatible con registros clínicos distintos): 367
cat("Dentro de la cohorte de insuficiencia cardíaca: combinaciones repetidas:", fnum(n_dup_cohorte))
## Dentro de la cohorte de insuficiencia cardíaca: combinaciones repetidas: 0
Antes de interpretar estas combinaciones repetidas como reingresos o
como errores de administración, se verificó si las filas de cada grupo
comparten el mismo diagnóstico principal y la misma condición de egreso.
De las 415 combinaciones repetidas en el total de egresos hospitalarios
(874 filas), la mayoría (367, 88,4%) tiene un DIAGNOSTICO1
distinto entre sus filas, lo que es compatible con registros clínicos
genuinamente distintos (por ejemplo, reingresos el mismo día) y no con
duplicados de administración. Solo 38 grupos (9,2%) comparten
diagnóstico principal y condición de egreso, que es el patrón más
parecido a un registro duplicado. Ninguna de estas combinaciones
cae dentro de la cohorte final de insuficiencia cardíaca, por
lo que esta verificación no cambia el tamaño de la cohorte, pero sí
respalda con evidencia la interpretación de que la mayoría de estas
repeticiones no son errores de registro.
n_est_cero <- sum(cohorte$estancia == 0, na.rm = TRUE)
n_est_larga <- sum(cohorte$estancia > 100, na.rm = TRUE)
n_edad_extrema <- sum(cohorte$edad < 1 | cohorte$edad > 100, na.rm = TRUE)
n_edad_neg <- sum(cohorte$edad < 0, na.rm = TRUE)
n_est_neg <- sum(cohorte$estancia < 0, na.rm = TRUE)
cat("Episodios con estancia = 0 días (alta el mismo día del ingreso):", fnum(n_est_cero), "\n")
## Episodios con estancia = 0 días (alta el mismo día del ingreso): 1.594
cat("Episodios con estancia > 100 días:", fnum(n_est_larga), "\n")
## Episodios con estancia > 100 días: 221
cat("Episodios con edad < 1 año o > 100 años:", fnum(n_edad_extrema), "\n")
## Episodios con edad < 1 año o > 100 años: 289
cat("Episodios con edad o estancia negativas (incompatibles, ninguno esperado):",
fnum(n_edad_neg + n_est_neg))
## Episodios con edad o estancia negativas (incompatibles, ninguno esperado): 0
En vez de solo proponer explicaciones para la estancia de 0 días, se
contrastaron con la condición de egreso (TIPOALTA) de esos
mismos episodios:
tab_cero <- cohorte[estancia == 0, .N, by = TIPOALTA][order(-N)]
tab_cero[, pct := fpct(100 * N / n_est_cero, 1)]
tabla_html(copy(tab_cero)[, N := fnum(N)], caption = "Condición de egreso de los episodios con estancia = 0 días (% sobre esos episodios)")
| TIPOALTA | N | pct |
|---|---|---|
| DOMICILIO | 809 | 50,8% |
| DERIVACIÓN OTRO HOSPITAL DEL SERVICIO | 290 | 18,2% |
| FALLECIDO | 269 | 16,9% |
| HOSPITALIZACIÓN DOMICILIARIA | 112 | 7,0% |
| DERIVACIÓN INST. PRIVADA (COMPRA DE SERVICIOS | 60 | 3,8% |
| ALTA VOLUNTARIA | 27 | 1,7% |
| DERIVACIÓN OTRO HOSPITAL DE LA RED NACIONAL | 18 | 1,1% |
| FUGA DEL PACIENTE | 5 | 0,3% |
| DERIVACIÓN INST. PRIVADA (VOLUNTARIO) | 3 | 0,2% |
| DERIVACIÓN A OTROS CENTROS (CÁRCEL, HOGAR DE | 1 | 0,1% |
No se detectaron edades ni estancias negativas (valores incompatibles). De los 1.594 episodios con estancia de 0 días, 50,8% termina en alta a domicilio (compatible con ingresos y altas administrativas el mismo día) y 16,9% en fallecimiento (compatible con muertes tempranas); el resto corresponde mayormente a derivaciones a otro hospital el mismo día de ingreso. Este cruce confirma con evidencia, y no solo por plausibilidad general, que la explicación propuesta para este valor extremo es consistente con los datos. Adicionalmente, se observan 221 episodios con estancias superiores a 100 días, que son extremos pero clínicamente plausibles en pacientes con insuficiencia cardíaca compleja y prolongada. Ninguno de estos valores se excluyó de la cohorte, ya que corresponden a valores extremos posibles y no a errores de registro identificables.
En total se descartaron 3 episodios de los 52.315
candidatos iniciales (0,006%), exclusivamente por traer fecha de ingreso
o de nacimiento no parseable ("DESCONOCIDO"). El efecto
sobre el tamaño final de la cohorte es marginal: de 52.315 a 52.312
episodios.
Con la misma base de datos es posible construir un análisis
descriptivo de reingresos hospitalarios por insuficiencia
cardíaca dentro de una ventana de tiempo definida (por ejemplo, 30
días), utilizando ID_BENEFICIARIO para identificar
al mismo beneficiario y las fechas de FECHAALTA de un
episodio y FECHA_INGRESO del siguiente episodio con
diagnóstico I50. Este análisis permitiría dimensionar la frecuencia de
reingresos tempranos y cómo se distribuyen por establecimiento, sin
requerir datos adicionales a los ya utilizados en este informe.
GRD_PUBLICO_2024.txt, extraído el 21 de agosto de
2026.Tablas_maestras_GRD.xlsx.CIE-10.xlsx.