1. Introducción

Los seguros de salud necesitan saber, con la mayor precisión posible, cuánto les va a costar atender a cada persona asegurada. Con esa información fijan el precio de las pólizas. Algunas características de las personas, como la edad, el peso o fumar, pueden hacer que una persona use más servicios médicos y, por lo tanto, genere costos más altos.

En este trabajo usamos una base de datos de 1.338 personas aseguradas en Estados Unidos para responder la siguiente pregunta:

Pregunta de investigación: ¿Qué características de una persona (edad, sexo, índice de masa corporal, número de hijos, si fuma y región donde vive) están relacionadas con los costos médicos que le factura el seguro de salud, y qué tan fuerte es esa relación?

Objetivo de modelación: construir un modelo de regresión lineal múltiple que explique los costos médicos anuales de una persona a partir de esas características. Con el modelo queremos saber cuáles de ellas pesan más y cuánto cambian los costos cuando cambia cada una.

2. Metodología

2.1 Fuente de datos

  • Nombre: Medical Cost Personal Datasets (Conjunto de datos personales de costos médicos).
  • Fuente: Kaggle, publicado por Miri Choi: https://www.kaggle.com/datasets/mirichoi0218/insurance. También está disponible en GitHub: https://github.com/stedy/Machine-Learning-with-R-datasets.
  • Origen: los datos acompañan el libro Machine Learning with R de Brett Lantz. Son de dominio público y fueron limpiados y recodificados para el libro.
  • Unidad de análisis: cada fila es una persona asegurada (el beneficiario principal de la póliza).
  • Período: la base trae un solo momento en el tiempo (corte transversal) y no dice el año exacto. Los costos son anuales y están en dólares estadounidenses (USD).
  • Tamaño: 1.338 filas (personas) y 7 columnas (variables).

Primero cargamos las librerías y la base de datos:

# Cargar librerias
library(tidyverse)   # incluye dplyr, ggplot2, tidyr y readr
library(knitr)
library(kableExtra)
library(plotly)
library(ggcorrplot)
library(broom)
library(lmtest)
library(car)

Data <- read.csv("insurance.csv")
head(Data, 10) %>% kable(caption = "Tabla 1. Primeras 10 filas de la base original") %>%
  kable_styling(bootstrap_options = c("striped", "bordered"), full_width = FALSE)
Tabla 1. Primeras 10 filas de la base original
age sex bmi children smoker region charges
19 female 27.900 0 yes southwest 16884.924
18 male 33.770 1 no southeast 1725.552
28 male 33.000 3 no southeast 4449.462
33 male 22.705 0 no northwest 21984.471
32 male 28.880 0 no northwest 3866.855
31 female 25.740 0 no southeast 3756.622
46 female 33.440 1 no southeast 8240.590
37 female 27.740 3 no northwest 7281.506
37 male 29.830 2 no northeast 6406.411
60 female 25.840 0 no northwest 28923.137

Construcción y limpieza de la base

Antes de analizar los datos revisamos si hay problemas de calidad: datos faltantes (celdas vacías) y filas repetidas.

# ¿Cuántos datos faltantes hay en cada columna?
faltantes <- data.frame(Variable = names(Data),
                        Faltantes = colSums(is.na(Data)),
                        row.names = NULL)
faltantes %>% kable(caption = "Tabla 2. Datos faltantes por variable") %>%
  kable_styling(bootstrap_options = c("striped", "bordered"), full_width = FALSE)
Tabla 2. Datos faltantes por variable
Variable Faltantes
age 0
sex 0
bmi 0
children 0
smoker 0
region 0
charges 0
# ¿Hay filas repetidas?
n_duplicados <- sum(duplicated(Data))
Data[duplicated(Data) | duplicated(Data, fromLast = TRUE), ] %>%
  kable(caption = "Tabla 3. Filas repetidas encontradas") %>%
  kable_styling(bootstrap_options = c("striped", "bordered"), full_width = FALSE)
Tabla 3. Filas repetidas encontradas
age sex bmi children smoker region charges
196 19 male 30.59 0 no northwest 1639.563
582 19 male 30.59 0 no northwest 1639.563

Qué encontramos:

  • Datos faltantes: la base no tiene ninguno (0 en total), así que no hubo que eliminar ni rellenar nada.
  • Filas repetidas: hay 1 persona que aparece dos veces con exactamente los mismos datos. Como es muy poco probable que dos personas distintas coincidan en todo, la tomamos como un error de registro y dejamos solo una copia.

Después traducimos los nombres y categorías al español y convertimos las variables de texto en factores, que es como R maneja las variables categóricas. En cada variable categórica elegimos una categoría de referencia, que es el grupo contra el que se comparan los demás en el modelo:

  • Fuma: “No” (no fumador).
  • Sexo: “Mujer”.
  • Región: “Noreste”.
base_limpia <- Data %>%
  distinct() %>%                                   # quitar la fila repetida
  rename(Edad = age, Sexo = sex, IMC = bmi, Hijos = children,
         Fuma = smoker, Region = region, Costos = charges) %>%
  mutate(
    Sexo   = factor(dplyr::recode(Sexo, female = "Mujer", male = "Hombre"),
                    levels = c("Mujer", "Hombre")),
    Fuma   = factor(dplyr::recode(Fuma, no = "No", yes = "Sí"),
                    levels = c("No", "Sí")),
    Region = factor(dplyr::recode(Region, northeast = "Noreste", northwest = "Noroeste",
                           southeast = "Sureste", southwest = "Suroeste"),
                    levels = c("Noreste", "Noroeste", "Sureste", "Suroeste"))
  )

str(base_limpia)
## 'data.frame':    1337 obs. of  7 variables:
##  $ Edad  : int  19 18 28 33 32 31 46 37 37 60 ...
##  $ Sexo  : Factor w/ 2 levels "Mujer","Hombre": 1 2 2 2 2 1 1 1 2 1 ...
##  $ IMC   : num  27.9 33.8 33 22.7 28.9 ...
##  $ Hijos : int  0 1 3 0 0 0 1 3 2 0 ...
##  $ Fuma  : Factor w/ 2 levels "No","Sí": 2 1 1 1 1 1 1 1 1 1 ...
##  $ Region: Factor w/ 4 levels "Noreste","Noroeste",..: 4 3 3 2 2 3 3 2 1 2 ...
##  $ Costos: num  16885 1726 4449 21984 3867 ...

La base final tiene 1337 personas y 7 variables. Este es el archivo que usamos en todo el análisis. Lo guardamos para entregarlo junto con el informe:

write.csv(base_limpia, "insurance_limpia.csv", row.names = FALSE)

2.2 Descripción de las variables del modelo

data.frame(
  Variable = c("Costos", "Edad", "Sexo", "IMC", "Hijos", "Fuma", "Region"),
  Papel    = c("Dependiente (Y)", rep("Explicativa (X)", 6)),
  Tipo     = c("Cuantitativa continua", "Cuantitativa discreta", "Categórica (2 grupos)",
               "Cuantitativa continua", "Cuantitativa discreta", "Categórica (2 grupos)",
               "Categórica (4 grupos)"),
  Unidad   = c("Dólares (USD) al año", "Años", "Mujer / Hombre", "kg/m²",
               "Número de hijos", "Sí / No", "Noreste / Noroeste / Sureste / Suroeste"),
  Descripcion = c("Costos médicos individuales facturados por el seguro",
                  "Edad del beneficiario principal",
                  "Sexo del titular del seguro",
                  "Índice de masa corporal (peso / estatura²). Lo ideal está entre 18,5 y 24,9",
                  "Hijos o dependientes cubiertos por el seguro",
                  "Indica si la persona fuma",
                  "Zona de Estados Unidos donde vive")
) %>%
  kable(caption = "Tabla 4. Variables utilizadas") %>%
  kable_styling(bootstrap_options = c("striped", "bordered"), full_width = TRUE)
Tabla 4. Variables utilizadas
Variable Papel Tipo Unidad Descripcion
Costos Dependiente (Y) Cuantitativa continua Dólares (USD) al año Costos médicos individuales facturados por el seguro
Edad Explicativa (X) Cuantitativa discreta Años Edad del beneficiario principal
Sexo Explicativa (X) Categórica (2 grupos) Mujer / Hombre Sexo del titular del seguro
IMC Explicativa (X) Cuantitativa continua kg/m² Índice de masa corporal (peso / estatura²). Lo ideal está entre 18,5 y 24,9
Hijos Explicativa (X) Cuantitativa discreta Número de hijos Hijos o dependientes cubiertos por el seguro
Fuma Explicativa (X) Categórica (2 grupos) Sí / No Indica si la persona fuma
Region Explicativa (X) Categórica (4 grupos) Noreste / Noroeste / Sureste / Suroeste Zona de Estados Unidos donde vive

¿Por qué elegimos estas variables? Usamos todas las que trae la base, porque cada una tiene una razón lógica para relacionarse con los costos médicos:

  • Edad: con los años aparecen más enfermedades y se va más al médico.
  • IMC: el sobrepeso y la obesidad aumentan el riesgo de diabetes, enfermedades del corazón y otros problemas.
  • Fuma: fumar está ligado a enfermedades del pulmón, del corazón y a varios tipos de cáncer.
  • Hijos: las personas con más dependientes pueden usar más el seguro.
  • Sexo: puede haber diferencias en el tipo de atención que necesitan hombres y mujeres.
  • Región: los precios de la salud y los hábitos de vida cambian de una zona a otra del país.

No descartamos ninguna variable al principio. Dejamos que el modelo muestre cuáles son importantes y cuáles no.

2.3 Descripción del modelo utilizado

Usamos un modelo de regresión lineal múltiple. En palabras simples, es una fórmula que intenta “predecir” los costos sumando el efecto de cada característica de la persona. La ecuación es:

\[ \text{Costos}_i = \beta_0 + \beta_1\,\text{Edad}_i + \beta_2\,\text{Hombre}_i + \beta_3\,\text{IMC}_i + \beta_4\,\text{Hijos}_i + \beta_5\,\text{Fuma}_i + \beta_6\,\text{Noroeste}_i + \beta_7\,\text{Sureste}_i + \beta_8\,\text{Suroeste}_i + \varepsilon_i \]

Donde:

  • \(\beta_0\) es el punto de partida (intercepto).
  • Cada \(\beta\) dice cuántos dólares cambian los costos cuando esa variable aumenta en una unidad (o cuando la persona pertenece a ese grupo), dejando todo lo demás igual.
  • Hombre, Fuma, Noroeste, Sureste y Suroeste son variables “dummy” (valen 1 si la persona está en ese grupo y 0 si no). Se comparan contra las categorías de referencia: mujer, no fumador y región Noreste.
  • \(\varepsilon_i\) es el error: la parte de los costos que el modelo no logra explicar.

3. Resultados Descriptivos

3.1 Costos médicos (variable dependiente)

base_limpia %>%
  summarise(Media      = round(mean(Costos), 2),
            Mediana    = round(median(Costos), 2),
            D.Estandar = round(sd(Costos), 2),
            Min        = round(min(Costos), 2),
            Max        = round(max(Costos), 2)) %>%
  kable(caption = "Tabla 5. Resumen de los costos médicos (USD)") %>%
  kable_styling(bootstrap_options = c("striped", "bordered"), full_width = FALSE)
Tabla 5. Resumen de los costos médicos (USD)
Media Mediana D.Estandar Min Max
13279.12 9386.16 12110.36 1121.87 63770.43
p1 <- ggplot(base_limpia, aes(x = Costos)) +
  geom_histogram(bins = 40, fill = "#2c7fb8", color = "white") +
  geom_vline(aes(xintercept = mean(Costos)), color = "red", linetype = "dashed") +
  geom_vline(aes(xintercept = median(Costos)), color = "darkgreen", linetype = "dashed") +
  labs(title = "Distribución de los costos médicos",
       subtitle = "Línea roja = media, línea verde = mediana",
       x = "Costos (USD)", y = "Número de personas") +
  theme_minimal()

ggplotly(p1)

¿Qué vemos? La mayoría de las personas tiene costos bajos, entre 1.000 y 15.000 dólares. Unas pocas tienen costos muy altos, de más de 40.000 dólares. Por eso el gráfico tiene una “cola larga” hacia la derecha. La media (13.279 USD) es mayor que la mediana (9.386 USD) porque esos pocos casos muy caros “jalan” el promedio hacia arriba.

Valores atípicos

q1  <- quantile(base_limpia$Costos, 0.25)
q3  <- quantile(base_limpia$Costos, 0.75)
lim <- q3 + 1.5 * (q3 - q1)
atipicos <- base_limpia %>% filter(Costos > lim)

atipicos %>% count(Fuma) %>%
  mutate(Porcentaje = round(n / sum(n) * 100, 2)) %>%
  kable(caption = "Tabla 6. Personas con costos atípicos según si fuman",
        col.names = c("¿Fuma?", "Número de personas", "Porcentaje (%)")) %>%
  kable_styling(bootstrap_options = c("striped", "bordered"), full_width = FALSE)
Tabla 6. Personas con costos atípicos según si fuman
¿Fuma? Número de personas Porcentaje (%)
No 3 2.16
Sí 136 97.84

Con la regla del boxplot (valores por encima de Q3 + 1,5 × rango intercuartílico) hay 139 personas con costos atípicos, es decir, por encima de 34.525 USD. La gran mayoría de ellas son fumadoras. Esto indica que no son errores en los datos sino casos reales: son personas con más riesgo que de verdad le cuestan más al seguro. Por eso decidimos no eliminarlas. Quitarlas haría que el modelo ignorara justamente a las personas más costosas.

3.2 Variables explicativas cuantitativas

base_limpia %>%
  select(Edad, IMC, Hijos) %>%
  pivot_longer(everything(), names_to = "Variable", values_to = "Valor") %>%
  group_by(Variable) %>%
  summarise(Media      = round(mean(Valor), 2),
            Mediana    = round(median(Valor), 2),
            D.Estandar = round(sd(Valor), 2),
            Min        = round(min(Valor), 2),
            Max        = round(max(Valor), 2)) %>%
  kable(caption = "Tabla 7. Resumen de las variables cuantitativas") %>%
  kable_styling(bootstrap_options = c("striped", "bordered"), full_width = FALSE)
Tabla 7. Resumen de las variables cuantitativas
Variable Media Mediana D.Estandar Min Max
Edad 39.22 39.0 14.04 18.00 64.00
Hijos 1.10 1.0 1.21 0.00 5.00
IMC 30.66 30.4 6.10 15.96 53.13
base_limpia %>%
  select(Edad, IMC, Hijos) %>%
  pivot_longer(everything(), names_to = "Variable", values_to = "Valor") %>%
  ggplot(aes(x = Valor)) +
  geom_histogram(bins = 25, fill = "#41ab5d", color = "white") +
  facet_wrap(~Variable, scales = "free") +
  labs(title = "Distribución de Edad, Hijos e IMC", x = "", y = "Número de personas") +
  theme_minimal()

¿Qué vemos?

  • Edad: va de 18 a 64 años y está repartida de forma bastante pareja. Hay un grupo algo más grande de jóvenes de 18 y 19 años.
  • IMC: tiene forma de campana, centrada cerca de 30. Eso significa que cerca de la mitad de las personas tiene obesidad (IMC de 30 o más). Solo 16.8% está en el rango saludable (18,5 a 24,9).
  • Hijos: la mayoría no tiene hijos o tiene uno solo. Muy pocas personas tienen 4 o 5.

3.3 Variables explicativas categóricas

# TABLA DE FRECUENCIAS PARA LAS VARIABLES CATEGÓRICAS (DUMMIES)
bind_rows(
  base_limpia %>% count(Categoria = Fuma)   %>% mutate(Variable = "Fuma"),
  base_limpia %>% count(Categoria = Sexo)   %>% mutate(Variable = "Sexo"),
  base_limpia %>% count(Categoria = Region) %>% mutate(Variable = "Region")
) %>%
  group_by(Variable) %>%
  mutate(Porcentaje = round(n / sum(n) * 100, 2)) %>%
  ungroup() %>%
  select(Variable, Categoria, n, Porcentaje) %>%
  kable(caption = "Tabla 8. Frecuencias de las variables categóricas",
        col.names = c("Variable", "Categoría", "Frecuencia (n)", "Participación (%)")) %>%
  kable_styling(bootstrap_options = c("striped", "bordered"), full_width = FALSE)
Tabla 8. Frecuencias de las variables categóricas
Variable Categoría Frecuencia (n) Participación (%)
Fuma No 1063 79.51
Fuma Sí 274 20.49
Sexo Mujer 662 49.51
Sexo Hombre 675 50.49
Region Noreste 324 24.23
Region Noroeste 324 24.23
Region Sureste 364 27.23
Region Suroeste 325 24.31
base_limpia %>%
  select(Fuma, Sexo, Region) %>%
  pivot_longer(everything(), names_to = "Variable", values_to = "Categoria") %>%
  ggplot(aes(x = Categoria, fill = Variable)) +
  geom_bar() +
  geom_text(stat = "count", aes(label = after_stat(count)), vjust = -0.5) +
  facet_wrap(~Variable, scales = "free_x") +
  labs(title = "Distribución de personas por categoría", x = "", y = "Cantidad de personas") +
  theme_minimal() +
  theme(legend.position = "none")

¿Qué vemos? Hombres y mujeres están casi en la misma proporción, y las cuatro regiones también tienen tamaños parecidos. En cambio, solo una de cada cinco personas fuma (20.5%).

3.4 Relación entre los costos y cada variable

Costos según si la persona fuma, su sexo y su región

tabla_fuma <- base_limpia %>%
  group_by(Fuma) %>%
  summarise(n          = n(),
            Media      = round(mean(Costos), 2),
            Mediana    = round(median(Costos), 2),
            D.Estandar = round(sd(Costos), 2))

tabla_fuma %>%
  kable(caption = "Tabla 9. Costos médicos (USD) según si la persona fuma") %>%
  kable_styling(bootstrap_options = c("striped", "bordered"), full_width = FALSE)
Tabla 9. Costos médicos (USD) según si la persona fuma
Fuma n Media Mediana D.Estandar
No 1063 8440.66 7345.73 5992.97
Sí 274 32050.23 34456.35 11541.55
p2 <- ggplot(base_limpia, aes(x = Fuma, y = Costos, fill = Fuma)) +
  geom_boxplot() +
  labs(title = "Costos médicos según si la persona fuma", x = "¿Fuma?", y = "Costos (USD)") +
  theme_minimal() + theme(legend.position = "none")
ggplotly(p2)
base_limpia %>%
  select(Costos, Sexo, Region) %>%
  pivot_longer(c(Sexo, Region), names_to = "Variable", values_to = "Categoria") %>%
  ggplot(aes(x = Categoria, y = Costos, fill = Categoria)) +
  geom_boxplot() +
  facet_wrap(~Variable, scales = "free_x") +
  labs(title = "Costos médicos según sexo y región", x = "", y = "Costos (USD)") +
  theme_minimal() + theme(legend.position = "none")

¿Qué vemos? La diferencia más grande, por mucho, es entre fumadores y no fumadores. Un fumador cuesta en promedio 32.050 USD al año, frente a 8.441 USD de un no fumador. Es casi 3.8 veces más. Entre hombres y mujeres, y entre regiones, las cajas se ven muy parecidas, así que esas diferencias parecen pequeñas.

Costos frente a Edad, IMC e Hijos

p3 <- ggplot(base_limpia, aes(x = Edad, y = Costos, color = Fuma)) +
  geom_point(size = 2, alpha = 0.6) +
  geom_smooth(method = "lm", se = TRUE, color = "gray40") +
  labs(title = "Edad vs Costos médicos", x = "Edad (años)", y = "Costos (USD)") +
  theme_minimal()

ggplotly(p3)

Edad: los costos suben a medida que la persona es mayor. Además se ven tres “franjas”: la de abajo son casi todos no fumadores, la de arriba casi todos fumadores y la del medio es mixta. En las tres franjas los costos crecen con la edad.

p4 <- ggplot(base_limpia, aes(x = IMC, y = Costos, color = Fuma)) +
  geom_point(size = 2, alpha = 0.6) +
  geom_smooth(method = "lm", se = TRUE, color = "gray40") +
  geom_vline(xintercept = 30, linetype = "dashed") +
  labs(title = "IMC vs Costos médicos (línea punteada = obesidad, IMC 30)",
       x = "IMC (kg/m²)", y = "Costos (USD)") +
  theme_minimal()

ggplotly(p4)

IMC: en los no fumadores (puntos rojos) el IMC casi no cambia los costos. En los fumadores (puntos turquesa) se ve un “salto” muy claro: los fumadores con obesidad (IMC mayor a 30) tienen costos muchísimo más altos que los fumadores sin obesidad. Es decir, fumar y la obesidad juntos son una combinación especialmente cara.

p5 <- ggplot(base_limpia, aes(x = factor(Hijos), y = Costos, fill = factor(Hijos))) +
  geom_boxplot() +
  labs(title = "Costos médicos según número de hijos", x = "Número de hijos", y = "Costos (USD)") +
  theme_minimal() + theme(legend.position = "none")

ggplotly(p5)

Hijos: no se ve una tendencia clara. Los costos son parecidos sin importar el número de hijos.

3.5 Matriz de correlación

La correlación mide qué tanto se mueven juntas dos variables. Va de -1 a 1: cerca de 1 es una relación positiva fuerte, cerca de 0 es que casi no hay relación. Para incluir “Fuma” la convertimos en 1 = fuma y 0 = no fuma.

# Matriz de correlaciones con cor() base de R
matriz_cor <- base_limpia %>%
  mutate(Fuma = ifelse(Fuma == "Sí", 1, 0)) %>%
  select(Costos, Edad, IMC, Hijos, Fuma) %>%
  cor(use = "complete.obs")

round(matriz_cor, 3) %>%
  kable(caption = "Tabla 10. Matriz de correlaciones") %>%
  kable_styling(bootstrap_options = c("striped", "bordered"), full_width = FALSE)
Tabla 10. Matriz de correlaciones
Costos Edad IMC Hijos Fuma
Costos 1.000 0.298 0.198 0.067 0.787
Edad 0.298 1.000 0.109 0.042 -0.026
IMC 0.198 0.109 1.000 0.013 0.004
Hijos 0.067 0.042 0.013 1.000 0.007
Fuma 0.787 -0.026 0.004 0.007 1.000
p6 <- ggcorrplot(matriz_cor,
                 type = "lower",
                 lab = TRUE,
                 lab_size = 3,
                 colors = c("#d73027", "white", "#1a9850"),
                 title = "Correlación entre las variables")

ggplotly(p6)

¿Qué vemos?

  • Fumar es lo que más se relaciona con los costos (0.79). Después vienen la edad (0.3) y el IMC (0.2). La relación con los hijos es muy débil (0.07).
  • Entre las variables explicativas, las correlaciones son todas muy bajas, cerca de 0. Esto es bueno: significa que no “repiten” la misma información, y se confirmará con el VIF más adelante.

4. Resultados del Modelo

4.1 Estimación

modelo <- lm(Costos ~ Edad + Sexo + IMC + Hijos + Fuma + Region, data = base_limpia)

datasummary <- as.data.frame(summary(modelo)$coefficients)
colnames(datasummary) <- c("Estimado", "Error estándar", "t valor", "p-valor")
datasummary$Significativo <- ifelse(datasummary$`p-valor` < 0.05, "Sí", "No")

datasummary %>%
  mutate(across(where(is.numeric), ~ round(.x, 4))) %>%
  kable(caption = "Tabla 11. Coeficientes del modelo (variable dependiente: Costos en USD)") %>%
  kable_styling(bootstrap_options = c("striped", "bordered"), full_width = TRUE)
Tabla 11. Coeficientes del modelo (variable dependiente: Costos en USD)
Estimado Error estándar t valor p-valor Significativo
(Intercept) -11936.5575 988.2274 -12.0788 0.0000 Sí
Edad 256.7646 11.9122 21.5547 0.0000 Sí
SexoHombre -129.4815 333.1952 -0.3886 0.6976 No
IMC 339.2504 28.6113 11.8572 0.0000 Sí
Hijos 474.8205 137.8969 3.4433 0.0006 Sí
FumaSí 23847.3288 413.3479 57.6931 0.0000 Sí
RegionNoroeste -349.2265 476.8238 -0.7324 0.4641 No
RegionSureste -1035.2656 478.8670 -2.1619 0.0308 Sí
RegionSuroeste -960.0814 478.1059 -2.0081 0.0448 Sí

La ecuación estimada queda así (valores redondeados):

\[ \widehat{\text{Costos}} = -11.937 + 256,8\,\text{Edad} -129,5\,\text{Hombre} + 339,3\,\text{IMC} + 474,8\,\text{Hijos} + 23.847,3\,\text{Fuma} -349,2\,\text{Noroeste} -1.035,3\,\text{Sureste} -960,1\,\text{Suroeste} \]

4.2 Interpretación de los coeficientes

Decimos que una variable es significativa cuando su p-valor es menor a 0,05. Eso quiere decir que es muy poco probable que su efecto se deba a la casualidad. Todas las interpretaciones son “manteniendo todo lo demás igual”: comparamos dos personas que solo se diferencian en esa característica.

  • Fuma (23.847 USD, p = <0.0000000000000002): es el factor más fuerte. Una persona que fuma tiene, en promedio, unos 23.847 dólares más de costos médicos al año que alguien que no fuma (la referencia). Es significativo.
  • Edad (256,8 USD, p = <0.0000000000000002): por cada año más de edad, los costos suben en promedio 257 dólares. Por ejemplo, una persona de 60 años cuesta unos 10.271 dólares más que una de 20. Es significativo.
  • IMC (339,3 USD, p = <0.0000000000000002): por cada punto más de IMC, los costos suben unos 339 dólares. Pasar de un IMC de 25 (normal) a 35 (obesidad) se asocia con unos 3.393 dólares más. Es significativo.
  • Hijos (474,8 USD, p = 0.000593): cada hijo adicional se asocia con unos 475 dólares más al año. Es un efecto pequeño, pero significativo.
  • Sexo – Hombre (-129,5 USD, p = 0.698): los hombres tendrían unos 129 dólares menos que las mujeres (la referencia), pero la diferencia NO es significativa. Es decir, cuando ya tenemos en cuenta la edad, el IMC, si fuma, etc., ser hombre o mujer no cambia los costos de forma importante.
  • Región (referencia: Noreste):
    • Noroeste: -349 USD, p = 0.464, no significativo.
    • Sureste: -1.035 USD, p = 0.0308, significativo.
    • Suroeste: -960 USD, p = 0.0448, significativo.
    En general, vivir en el sur se asocia con costos algo menores que en el Noreste, unos 1.000 dólares menos. Es una diferencia pequeña comparada con la de fumar.
  • Intercepto (-11.937 USD): sería el costo de una persona de 0 años, con IMC 0, etc. Esa persona no existe, así que este número no tiene una interpretación práctica. Solo sirve como punto de partida de la fórmula.

4.3 Bondad de ajuste y significancia global

data.frame(
  Medida = c("R²", "R² ajustado", "Estadístico F", "p-valor (prueba F)", "Error estándar residual (USD)"),
  Valor  = c(round(ajuste$r.squared, 4), round(ajuste$adj.r.squared, 4),
             round(ajuste$statistic, 2), format.pval(ajuste$p.value, digits = 4),
             round(ajuste$sigma, 2))
) %>%
  kable(caption = "Tabla 12. Medidas de ajuste del modelo") %>%
  kable_styling(bootstrap_options = c("striped", "bordered"), full_width = FALSE)
Tabla 12. Medidas de ajuste del modelo
Medida Valor
R² 0.7507
R² ajustado 0.7492
Estadístico F 499.99
p-valor (prueba F) < 0.00000000000000022
Error estándar residual (USD) 6064.3
  • R² = 0.751: el modelo explica alrededor del 75.1% de las diferencias en los costos médicos entre las personas. Es un resultado bueno para datos de personas reales.
  • R² ajustado = 0.749: es casi igual al R². Eso indica que no estamos “inflando” el modelo con variables inútiles.
  • Prueba F (p-valor <0.0000000000000002): como el p-valor es prácticamente 0, el modelo en su conjunto sí sirve para explicar los costos. Al menos una de las variables tiene un efecto real.
  • Error estándar residual (6.064 USD): en promedio, las predicciones del modelo se equivocan en unos 6.064 dólares. Es un error considerable, como veremos en el diagnóstico.

5. Diagnóstico del Modelo

Para que las conclusiones de una regresión sean confiables, el modelo debe cumplir algunos supuestos (condiciones). Los revisamos uno por uno.

5.1 Linealidad

Este supuesto pide que la relación entre los costos y cada variable sea más o menos una línea recta. Lo revisamos con el gráfico de residuos vs valores ajustados. Los residuos son la diferencia entre el costo real y el que predice el modelo. Si el supuesto se cumple, los puntos deberían verse como una nube sin forma, alrededor de la línea en 0.

plot(modelo, which = 1)

# Gráficos de componentes + residuos para las variables cuantitativas
crPlots(modelo, terms = ~ Edad + IMC + Hijos, layout = c(1, 3))

Resultado: los residuos no forman una nube sin forma. Se ven grupos separados y una curva. Esto indica que la relación no es del todo lineal. La razón principal la vimos en los gráficos de dispersión: el IMC afecta mucho más a los fumadores que a los no fumadores, y el modelo actual trata ese efecto como si fuera igual para todos. En los gráficos de componentes, la edad y el IMC sí muestran una tendencia razonablemente recta por sí solas.

5.2 Normalidad de los residuos

Los errores del modelo deberían repartirse como una campana (distribución normal). Lo revisamos con la prueba de Shapiro-Wilk y con el gráfico Q-Q.

  • H0: los residuos son normales.
  • H1: los residuos no son normales.
sw <- shapiro.test(residuals(modelo))

datashapiro <- data.frame(
  `W`       = round(sw$statistic, 4),
  `p-valor` = format(sw$p.value, scientific = TRUE, digits = 4),
  check.names = FALSE
)

datashapiro %>%
  kable(caption = "Tabla 13. Prueba de normalidad de Shapiro-Wilk") %>%
  kable_styling(bootstrap_options = c("striped", "bordered"), full_width = FALSE)
Tabla 13. Prueba de normalidad de Shapiro-Wilk
W p-valor
W 0.8991 8.936e-29
plot(modelo, which = 2)

Resultado: el p-valor (8.94e-29) es mucho menor que 0,05, así que rechazamos que los residuos sean normales. En el gráfico Q-Q los puntos se salen de la línea en el extremo derecho: hay personas con costos mucho más altos de lo que el modelo predice, sobre todo fumadores con obesidad. Como la muestra es grande (1337 personas), los coeficientes siguen siendo una buena estimación. Pero los p-valores deben leerse con algo de precaución.

5.3 Homocedasticidad

Este supuesto pide que el tamaño de los errores sea parecido para todas las personas, es decir, que el modelo no se equivoque mucho más con unos grupos que con otros. Usamos la prueba de Breusch-Pagan.

  • H0: la varianza de los errores es constante (homocedasticidad).
  • H1: la varianza no es constante (heterocedasticidad).
bp <- bptest(modelo)

databp <- data.frame(
  `Estadístico BP` = round(bp$statistic, 4),
  gl               = bp$parameter,
  `p-valor`        = format(bp$p.value, scientific = TRUE, digits = 4),
  check.names      = FALSE
)

databp %>%
  kable(caption = "Tabla 14. Prueba de homocedasticidad (Breusch-Pagan)") %>%
  kable_styling(bootstrap_options = c("striped", "bordered"), full_width = FALSE)
Tabla 14. Prueba de homocedasticidad (Breusch-Pagan)
Estadístico BP gl p-valor
BP 121.5712 8 1.57e-22
plot(modelo, which = 3)

Resultado: el p-valor (1.57e-22) es menor que 0,05, así que hay heterocedasticidad. El modelo se equivoca mucho más con las personas de costos altos (fumadores) que con las de costos bajos. Una forma de corregir los errores estándar es usar errores “robustos”, que se ajustan a este problema:

coeftest(modelo, vcov = hccm(modelo, type = "hc1")) %>%
  tidy() %>%
  mutate(across(where(is.numeric), ~ round(.x, 4))) %>%
  kable(caption = "Tabla 15. Coeficientes con errores estándar robustos",
        col.names = c("Término", "Estimado", "Error estándar robusto", "t valor", "p-valor")) %>%
  kable_styling(bootstrap_options = c("striped", "bordered"), full_width = TRUE)
Tabla 15. Coeficientes con errores estándar robustos
Término Estimado Error estándar robusto t valor p-valor
(Intercept) -11936.5575 1045.7363 -11.4145 0.0000
Edad 256.7646 11.9266 21.5287 0.0000
SexoHombre -129.4815 333.8105 -0.3879 0.6982
IMC 339.2504 31.7028 10.7010 0.0000
Hijos 474.8205 130.3548 3.6425 0.0003
FumaSí 23847.3288 574.9920 41.4742 0.0000
RegionNoroeste -349.2265 485.3925 -0.7195 0.4720
RegionSureste -1035.2656 501.2939 -2.0652 0.0391
RegionSuroeste -960.0814 461.1243 -2.0820 0.0375

Con los errores robustos, las variables importantes (fumar, edad, IMC e hijos) siguen siendo significativas, así que las conclusiones principales se mantienen.

5.4 Multicolinealidad (VIF)

La multicolinealidad ocurre cuando dos o más variables explicativas dicen casi lo mismo. Así el modelo no puede separar el efecto de cada una. El VIF mide este problema: valores menores a 5 se consideran aceptables, y mayores a 10, preocupantes.

vif_valores <- vif(modelo)

datavif <- data.frame(
  Variable       = rownames(vif_valores),
  GVIF           = round(vif_valores[, 1], 4),
  Df             = vif_valores[, 2],
  `GVIF^(1/2Df)` = round(vif_valores[, 3], 4),
  check.names    = FALSE,
  row.names      = NULL
)

datavif %>%
  kable(caption = "Tabla 16. Prueba de multicolinealidad (VIF)") %>%
  kable_styling(bootstrap_options = c("striped", "bordered"), full_width = FALSE)
Tabla 16. Prueba de multicolinealidad (VIF)
Variable GVIF Df GVIF^(1/2Df)
Edad 1.0168 1 1.0084
Sexo 1.0089 1 1.0045
IMC 1.1067 1 1.0520
Hijos 1.0040 1 1.0020
Fuma 1.0121 1 1.0060
Region 1.0990 3 1.0159

Resultado: todos los valores están muy cerca de 1, así que no hay multicolinealidad. Cada variable aporta información diferente.

5.5 Independencia de las observaciones

Cada fila es una persona distinta medida una sola vez. No hay datos en el tiempo (no es una serie de años o meses), así que no hay dependencia temporal. Aun así, aplicamos la prueba de Durbin-Watson como revisión:

dw <- dwtest(modelo)
data.frame(`Estadístico DW` = round(dw$statistic, 4),
           `p-valor` = round(dw$p.value, 4), check.names = FALSE) %>%
  kable(caption = "Tabla 17. Prueba de independencia (Durbin-Watson)") %>%
  kable_styling(bootstrap_options = c("striped", "bordered"), full_width = FALSE)
Tabla 17. Prueba de independencia (Durbin-Watson)
Estadístico DW p-valor
DW 2.089 0.9482

Resultado: el estadístico está cerca de 2 (2.09), lo que indica que no hay dependencia entre observaciones. Una posible limitación es que no sabemos si algunas personas son de la misma familia, porque la base no trae esa información.

5.6 Resumen del diagnóstico

data.frame(
  Supuesto = c("Linealidad", "Normalidad", "Homocedasticidad", "Multicolinealidad", "Independencia"),
  Prueba   = c("Gráfico residuos vs ajustados", "Shapiro-Wilk", "Breusch-Pagan", "VIF", "Durbin-Watson"),
  `¿Se cumple?` = c("Parcialmente", "No", "No", "Sí", "Sí"),
  check.names = FALSE
) %>%
  kable(caption = "Tabla 18. Resumen de la evaluación de supuestos") %>%
  kable_styling(bootstrap_options = c("striped", "bordered"), full_width = FALSE)
Tabla 18. Resumen de la evaluación de supuestos
Supuesto Prueba ¿Se cumple?
Linealidad Gráfico residuos vs ajustados Parcialmente
Normalidad Shapiro-Wilk No
Homocedasticidad Breusch-Pagan No
Multicolinealidad VIF Sí
Independencia Durbin-Watson Sí

5.7 Extensión: un modelo mejorado

Los problemas de linealidad, normalidad y homocedasticidad tienen una causa común: el efecto de la obesidad es mucho mayor en los fumadores. Para comprobarlo, probamos un segundo modelo. Agregamos una variable Obeso (1 si el IMC es 30 o más) y su combinación con Fuma (interacción). Esto le permite al modelo decir que “ser fumador y obeso” tiene un costo extra.

base_limpia <- base_limpia %>% mutate(Obeso = ifelse(IMC >= 30, 1, 0))

modelo2 <- lm(Costos ~ Edad + Sexo + IMC + Hijos + Fuma * Obeso + Region, data = base_limpia)

tidy(modelo2) %>%
  mutate(across(where(is.numeric), ~ round(.x, 4))) %>%
  kable(caption = "Tabla 19. Coeficientes del modelo con interacción Fuma × Obeso",
        col.names = c("Término", "Estimado", "Error estándar", "t valor", "p-valor")) %>%
  kable_styling(bootstrap_options = c("striped", "bordered"), full_width = TRUE)
Tabla 19. Coeficientes del modelo con interacción Fuma × Obeso
Término Estimado Error estándar t valor p-valor
(Intercept) -4741.9577 960.5522 -4.9367 0.0000
Edad 263.2030 8.8150 29.8585 0.0000
SexoHombre -490.3948 246.7497 -1.9874 0.0471
IMC 114.9331 34.5841 3.3233 0.0009
Hijos 520.1092 102.0277 5.0977 0.0000
FumaSí 13402.3402 444.0748 30.1804 0.0000
Obeso -862.9655 426.3241 -2.0242 0.0431
RegionNoroeste -265.2591 352.8096 -0.7518 0.4523
RegionSureste -825.0073 354.9321 -2.3244 0.0203
RegionSuroeste -1224.3081 353.8156 -3.4603 0.0006
FumaSí:Obeso 19793.9119 610.3739 32.4292 0.0000
bind_rows(
  glance(modelo)  %>% mutate(Modelo = "1. Modelo original"),
  glance(modelo2) %>% mutate(Modelo = "2. Con interacción Fuma × Obeso")
) %>%
  transmute(Modelo,
            `R²` = round(r.squared, 4),
            `R² ajustado` = round(adj.r.squared, 4),
            `Error estándar residual (USD)` = round(sigma, 2)) %>%
  kable(caption = "Tabla 20. Comparación de los dos modelos") %>%
  kable_styling(bootstrap_options = c("striped", "bordered"), full_width = FALSE)
Tabla 20. Comparación de los dos modelos
Modelo R² R² ajustado Error estándar residual (USD)
  1. Modelo original
0.7507 0.7492 6064.30
  1. Con interacción Fuma × Obeso
0.8638 0.8628 4486.47

Resultado: al incluir la interacción, el R² sube de 0.751 a 0.864 y el error promedio baja de 6.064 a 4.486 dólares. El coeficiente de la interacción indica que un fumador con obesidad tiene unos 19.794 dólares adicionales de costos, además de lo que ya suma por fumar. Este hallazgo es muy importante para entender los costos médicos.

6. Conclusiones

Respuesta a la pregunta de investigación:

  1. Fumar es, por lejos, el factor más asociado con los costos médicos. Un fumador tiene en promedio unos 23.847 dólares más de costos al año que un no fumador con las mismas características.
  2. La edad y el IMC también importan. Cada año de edad suma unos 257 dólares y cada punto de IMC unos 339 dólares.
  3. El número de hijos tiene un efecto pequeño, y el sexo no tiene un efecto significativo. La región tiene diferencias pequeñas: el sur tiene costos algo menores que el Noreste.
  4. La combinación de fumar y tener obesidad es especialmente costosa. El modelo con interacción mostró que estas personas tienen un costo extra muy grande.

Importante: estos resultados muestran asociaciones, no causas. Los datos son una “foto” de personas en un solo momento, no un experimento. Por eso no podemos afirmar, por ejemplo, que “dejar de fumar bajará los costos exactamente en esa cantidad”. Lo que sí podemos decir es que los fumadores, en estos datos, cuestan mucho más.

¿El modelo logró responder al objetivo de la investigación?

Sí, en buena medida. El modelo identificó con claridad qué factores se relacionan con los costos y cuánto pesa cada uno. Además explica cerca del 75% de las diferencias entre personas, y la multicolinealidad no es un problema. Sin embargo, no cumple todos los supuestos: los residuos no son normales, su varianza no es constante y la relación no es del todo lineal. Esto significa que el modelo básico se equivoca bastante con las personas de costos altos y que sus p-valores deben tomarse con cuidado. Al agregar la interacción entre fumar y obesidad, el ajuste mejoró mucho (R² de 0.864). Esto muestra que el objetivo se cumple mejor con ese modelo ampliado.

Limitaciones:

  • La base no dice el año ni de qué aseguradora vienen los datos, y la variable región es muy general.
  • Faltan variables que seguramente influyen, como enfermedades previas, tipo de póliza, ingresos, actividad física o consumo de alcohol.
  • “Fuma” solo dice sí o no. No sabemos cuánto fuma la persona ni hace cuánto tiempo.
  • Los costos de unas pocas personas son muy altos, y eso afecta los supuestos del modelo.

Recomendaciones y extensiones:

  • Usar el modelo con interacción Fuma × Obeso como modelo principal.
  • Probar el logaritmo de los costos como variable dependiente, para reducir el efecto de los valores muy altos y mejorar la normalidad.
  • Probar la edad al cuadrado, porque los costos podrían crecer más rápido en edades avanzadas.
  • Conseguir datos con más variables de salud para mejorar las predicciones.
  • Desde el punto de vista práctico, los programas para dejar de fumar y controlar el peso podrían ser los más útiles para reducir costos en salud. Para confirmarlo harían falta estudios que sigan a las mismas personas en el tiempo.

7. Bibliografía