Práctica 3

Clasificación y Regresión

Author

Dr. Jorge Párraga Álava

Minería de datos

Introducción

En esta práctica, se trabajará con un conjunto de datos sobre ventas inmobiliarias en Chile, un país con una economía dinámica y en crecimiento en América Latina. El mercado inmobiliario chileno es conocido por su diversidad, influenciada por factores como la ubicación geográfica, el nivel socioeconómico, y la infraestructura urbana. Este conjunto de datos incluye 1500 propiedades situadas en diversas zonas de la capital chilena, Santiago, que se caracteriza por su expansión urbana y variedad en los precios de las propiedades según la cercanía al centro y las características de las zonas residenciales.

💡 En 2026, el mercado inmobiliario latinoamericano integra cada vez más herramientas de Machine Learning para la valoración automática de propiedades (AVM — Automated Valuation Models), siendo R y Python los lenguajes predominantes en este dominio.

Las variables disponibles son:

  • metros_cuadrados: Tamaño de la propiedad en metros cuadrados.
  • num_habitaciones: Número de habitaciones.
  • num_banos: Número de baños.
  • antiguedad: Edad de la propiedad en años.
  • distancia_centro: Distancia al centro de la ciudad en kilómetros.
  • zona: Zona de la ciudad (Centro, Norte, Sur, Este, Oeste).
  • precio: Precio de la propiedad en USD (variable objetivo para regresión).
  • condicion: Categoría de la propiedad basada en el precio — Lujosa / No Lujosa (variable objetivo para clasificación).

Los vendedores indican que cada propiedad comienza con un precio base de 150.000 USD. Aumenta con los metros cuadrados, número de habitaciones y baños. Disminuye con la antigüedad y la distancia al centro. Las zonas Centro, Norte y Oeste tienden a ser más caras. Se solicita que, a partir de los datos, se prediga el precio y la condición de una propiedad.

Configuración Inicial

# Bibliotecas -------------------------------------------------------------
if(!require(tidyverse))  install.packages("tidyverse") 
if(!require(e1071))      install.packages("e1071") 
if(!require(caret))      install.packages("caret")  
if(!require(rpart))      install.packages("rpart")  
if(!require(rpart.plot)) install.packages("rpart.plot") 

# Config -------------------------------------------------------------
# Para notación no numérica.
options(scipen = 999) 
# Para reproducibilidad.
set.seed(2026) 

0. Dominio del problema

  1. ¿Cuál es el problema específico que estamos tratando de resolver?

    Estamos tratando de predecir el precio y la condición de una propiedad inmobiliaria en Santiago de Chile.

  2. ¿Es factible lograr nuestros objetivos con los datos disponibles? ¿Cómo podemos alcanzarlos?

    Sí, es factible. Contamos con un conjunto de datos histórico que incluye variables clave que afectan el precio de las propiedades, como el tamaño, la ubicación, la antigüedad y la cercanía al centro de la ciudad. Podemos alcanzar nuestros objetivos utilizando técnicas de análisis de regresión para predecir el precio y de clasificación para determinar la condición (Lujosa / No Lujosa) de la propiedad.

  3. ¿Cuáles son los beneficios si nuestra solución funciona y cuáles son las consecuencias si falla?

    • Si nuestra solución funciona:

      • Para compradores y vendedores: Ofrecemos una estimación precisa del valor de las propiedades, lo que facilita la toma de decisiones informadas en el mercado inmobiliario.

      • Para agentes inmobiliarios: Proporcionamos una herramienta valiosa para la valoración de propiedades, optimizando estrategias de precios y marketing.

    • Si la solución falla:

      • Para compradores y vendedores: Pueden tomar decisiones basadas en estimaciones incorrectas, lo que podría llevar a pérdidas financieras o dificultades para vender una propiedad.

      • Para agentes inmobiliarios: La falta de precisión en la valoración podría afectar la competitividad y reputación en el mercado.

  4. ¿Qué tipo de problema se va a resolver: Predictivo/Descriptivo?

    Estamos abordando principalmente un problema predictivo, ya que queremos predecir el precio y la condición de una propiedad en función de datos históricos y variables predictoras.

  5. ¿El objetivo es predecir, segmentar, …?

    Nuestro objetivo principal es predecir (regresión y clasificación).

  6. ¿De dónde vendrán los datos, cuánto cuesta conseguirlos?

    Los datos provienen de la base de datos interna de empresas dedicadas a ventas inmobiliarias. En 2026, fuentes abiertas como portales de clasificados (Portalinmobiliario.com, Yapo.cl) y APIs de valoración pública también son fuentes válidas de datos inmobiliarios en Chile.

1. Selección

En esta etapa, se realiza la selección del conjunto de datos. Como este es proporcionado por la empresa, únicamente lo cargamos.

 # Cargar datos 
inmobiliaria_chile <- read.csv("inmobiliaria_chile.csv")
# Mostrar una vista previa de datos
glimpse(inmobiliaria_chile)
Rows: 1,500
Columns: 8
$ metros_cuadrados <dbl> 46.78991, 166.85823, 133.28757, 95.08148, 199.62663, …
$ num_habitaciones <int> 2, 4, 3, 3, 2, 1, 3, 1, 1, 3, 2, 3, 4, 3, 1, 3, 3, 3,…
$ num_banos        <int> 3, 2, 1, 1, 1, 1, 2, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 2,…
$ antiguedad       <int> 21, 16, 12, 4, 5, 9, 12, 14, 16, 4, 2, 9, 9, 6, 4, 7,…
$ distancia_centro <dbl> 13.16033928, 11.10143025, 0.01171242, 1.10572401, 13.…
$ zona             <chr> "Centro", "Oeste", "Norte", "Oeste", "Norte", "Este",…
$ precio           <dbl> 409942.5, 780050.9, 683005.9, 539468.0, 706804.1, 823…
$ condicion        <chr> "No Lujosa", "Lujosa", "Lujosa", "No Lujosa", "Lujosa…

Se observa que el conjunto de datos tiene 1500 instancias y 8 variables. Se muestran ejemplos de los datos que contiene cada columna así como su tipo de variable.

2. Limpieza

Para este conjunto de datos no es necesario realizar procesos de limpieza toda vez que la empresa con su equipo de Ciencia de Datos ya ha realizado todas estas actividades. El único proceso que realizaremos será el EDA.

2.3 Análisis Exploratorio de Datos (EDA)

El EDA nos ayudará a comprender mejor la naturaleza de nuestros datos. Analizaremos cada variable considerando si es cuantitativa o cualitativa.

Variable: metros_cuadrados (Cuantitativa)

Para la variable metros_cuadrados realizamos estadística descriptiva.

# Resumen estadístico
metros_cuadrados_resumen <- inmobiliaria_chile |>
  summarise(
    conteo = n(),
    media = mean(metros_cuadrados, na.rm = TRUE),
    mediana = median(metros_cuadrados, na.rm = TRUE),
    desviacion_estandar = sd(metros_cuadrados, na.rm = TRUE),
    min = min(metros_cuadrados, na.rm = TRUE),
    max = max(metros_cuadrados, na.rm = TRUE),
    q1 = quantile(metros_cuadrados, 0.25, na.rm = TRUE),
    q3 = quantile(metros_cuadrados, 0.75, na.rm = TRUE)
  )

print(metros_cuadrados_resumen)
  conteo    media mediana desviacion_estandar      min      max       q1
1   1500 116.2913 116.122            50.73508 30.08886 199.9665 70.92984
        q3
1 160.3972

También, visualizamos la distribución de metros cuadrados de las propiedades.

# Histograma de metros_cuadrados
ggplot(inmobiliaria_chile, aes(x = metros_cuadrados)) +
  geom_histogram(bins = 30, fill = "lightcoral", color = "black") +
  labs(title = "Histograma de variable metros_cuadrados",
       x = "Metros cuadrados", y = "Frecuencia")

La distribución de los metros cuadrados sugiere que el mercado inmobiliario en este conjunto de datos está compuesto por una variedad de propiedades, desde pequeñas hasta grandes. La mayoría de las propiedades tienen un tamaño moderado, pero existe una diversidad significativa en los tamaños comenzando en los 30 metros cuadrados hasta los casi 200 metros cuadrados.

Variable: num_habitaciones (Cualitativa)

Para la variable num_habitaciones analizamos la cantidad de registros.

# Frecuencia por número de habitaciones
inmobiliaria_chile |> count(num_habitaciones) 
  num_habitaciones   n
1                1 133
2                2 464
3                3 589
4                4 232
5                5  82

Observamos que hay propiedades de hasta 5 habitaciones.

Ahora veamos los precios según el número de habitaciones.

# Visualización de precio por número de habitaciones 
ggplot(inmobiliaria_chile, aes(x = factor(num_habitaciones), y = precio)) +
  geom_boxplot(fill = "lightgreen") +
  labs(title = "Precios según número de habitaciones",
       x = "Número de habitaciones", y = "Precio (USD)") +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

Con el análisis podemos concluir que las propiedades tienen entre 1 y 5 habitaciones. Sin embargo, la mayoría de los registros (589) corresponden a propiedades de 3 habitaciones. Únicamente existen 82 propiedades con 5 habitaciones. Como es de suponer, el precio de la propiedad parece incrementar a medida que crece el número de habitaciones.

Variable: num_banos (Cualitativa)

Para la variable num_banos analizamos la cantidad de registros.

# Frecuencia de baños en propiedades
inmobiliaria_chile |> count(num_banos) 
  num_banos   n
1         1 904
2         2 455
3         3 141

Observamos que en total hay propiedades de hasta 3 baños. Ahora veamos los precios según el número de baños.

# Visualización de precio por número de baños 
ggplot(inmobiliaria_chile, aes(x = factor(num_banos), y = precio)) +
  geom_boxplot(fill = "lightgreen") +
  labs(title = "Precio según el número de baños",
       x = "Número de baños", y = "Precio (USD)") +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

Con el análisis podemos concluir que las propiedades tienen entre 1 y 3 baños. Sin embargo, la mayoría de los registros (904) corresponden a propiedades con 1 baño. Únicamente existen 141 propiedades con 3 baños. Como es de suponer, el precio de la propiedad parece incrementar a medida que crece el número de baños.

Variable: antiguedad (Cuantitativa)

Para la variable antiguedad realizamos estadística descriptiva.

# Resumen estadístico
antiguedad_resumen <- inmobiliaria_chile |>
  summarise(
    conteo = n(),
    media = mean(antiguedad, na.rm = TRUE),
    mediana = median(antiguedad, na.rm = TRUE),
    desviacion_estandar = sd(antiguedad, na.rm = TRUE),
    min = min(antiguedad, na.rm = TRUE),
    max = max(antiguedad, na.rm = TRUE),
    q1 = quantile(antiguedad, 0.25, na.rm = TRUE),
    q3 = quantile(antiguedad, 0.75, na.rm = TRUE)
  )

print(antiguedad_resumen)
  conteo    media mediana desviacion_estandar min max q1 q3
1   1500 10.30133      10            5.052876   0  27  7 14

También, visualizamos la distribución de antigüedad de propiedades.

# Histograma de antiguedad
ggplot(inmobiliaria_chile, aes(x = antiguedad)) +
  geom_histogram(bins = 30, fill = "lightcoral", color = "black") +
  labs(title = "Histograma de variable antiguedad",
       x = "Antigüedad (años)", y = "Frecuencia")

La distribución de los años de antigüedad sugiere que el mercado inmobiliario en este conjunto de datos está compuesto por propiedades con un promedio de 10 años de antigüedad. La propiedad más antigua tiene 27 años y la más reciente tiene menos de 1 año.

Variable: distancia_centro (Cuantitativa)

Para la variable distancia_centro realizamos estadística descriptiva.

# Resumen estadístico
distancia_centro_resumen <- inmobiliaria_chile |>
  summarise(
    conteo = n(),
    media = mean(distancia_centro, na.rm = TRUE),
    mediana = median(distancia_centro, na.rm = TRUE),
    desviacion_estandar = sd(distancia_centro, na.rm = TRUE),
    min = min(distancia_centro, na.rm = TRUE),
    max = max(distancia_centro, na.rm = TRUE),
    q1 = quantile(distancia_centro, 0.25, na.rm = TRUE),
    q3 = quantile(distancia_centro, 0.75, na.rm = TRUE)
  )

print(distancia_centro_resumen)
  conteo    media  mediana desviacion_estandar         min      max     q1
1   1500 7.430329 7.202922            4.279007 0.006919771 14.97225 3.8702
        q3
1 11.16475

También, visualizamos la distribución de distancia al centro de las propiedades.

# Histograma de distancia_centro
ggplot(inmobiliaria_chile, aes(x = distancia_centro)) +
  geom_histogram(bins = 30, fill = "lightcoral", color = "black") +
  labs(title = "Histograma de variable distancia_centro",
       x = "Distancia (km) al centro de Santiago", y = "Frecuencia")

La distribución de la distancia al centro de la ciudad sugiere que el mercado inmobiliario en este conjunto de datos está compuesto por propiedades a una distancia de entre 0 y 15 km. En promedio las propiedades se encuentran a 7.43 km del centro.

Variable: zona (Cualitativa)

Para la variable zona analizamos la cantidad de registros para cada zona.

# Frecuencia de zonas
inmobiliaria_chile |> count(zona) 
    zona   n
1 Centro 306
2   Este 315
3  Norte 297
4  Oeste 284
5    Sur 298

Ahora analizamos la distribución de precio por zona mediante un diagrama de cajas.

# Visualización de precio por zonas
ggplot(inmobiliaria_chile, aes(x = zona, y = precio)) +
  geom_boxplot(fill = "lightgreen") +
  labs(title = "Distribución de precio por zona de la propiedad",
       x = "Zona", y = "Precio (USD)") +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

Finalmente podemos darnos cuenta de que la cantidad de registros por zonas de Santiago parecen muy similares. La zona Centro, Norte y Oeste agrupan las viviendas con mayor precio.

Variable: precio (Cuantitativa)

Para la variable precio, que es la variable a predecir, realizamos estadística descriptiva.

# Resumen estadístico
precio_resumen <- inmobiliaria_chile |>
  summarise(
    conteo = n(),
    media = mean(precio, na.rm = TRUE),
    mediana = median(precio, na.rm = TRUE),
    desviacion_estandar = sd(precio, na.rm = TRUE),
    min = min(precio, na.rm = TRUE),
    max = max(precio, na.rm = TRUE),
    q1 = quantile(precio, 0.25, na.rm = TRUE),
    q3 = quantile(precio, 0.75, na.rm = TRUE)
  )

print(precio_resumen)
  conteo    media  mediana desviacion_estandar      min     max       q1     q3
1   1500 574189.7 575251.8            175863.4 114855.5 1031868 435129.1 711937

También, visualizamos la distribución de precios de propiedades.

# Histograma de precio
ggplot(inmobiliaria_chile, aes(x = precio)) +
  geom_histogram(bins = 30, fill = "lightseagreen", color = "black") +
  labs(title = "Histograma de variable precio",
       x = "Precio (USD)", y = "Frecuencia")

Finalmente, mostramos el diagrama de cajas de la variable.

# Boxplot de precio
ggplot(inmobiliaria_chile, aes(y = precio)) +
  geom_boxplot(fill = "lightyellow") +
  labs(title = "Boxplot de precios",
       y = "Precio (USD)")

Del análisis se puede concluir que los precios tienen una forma aproximadamente simétrica, con una mayor concentración de valores alrededor de la media de 574.189,70 USD. La mayor parte de los precios se encuentra entre los 400.000 y 700.000 USD. Este es el rango donde la mayoría de las propiedades ocurren, lo que sugiere que las propiedades dentro de este rango son las más comunes o deseadas.

Variable: condicion (Cualitativa)

Para la variable condicion, que es la variable a clasificar, mostramos la cantidad de registros para cada categoría.

# Calcular la frecuencia de cada categoría en la variable `condicion`
condicion_freq <- inmobiliaria_chile |>
  count(condicion) |>
  rename(Frecuencia = n)

# Mostrar la tabla de frecuencias
print(condicion_freq)
  condicion Frecuencia
1    Lujosa        692
2 No Lujosa        808

Ahora analizamos la distribución de condicion mediante un gráfico de barras.

# Gráfico de barras para distribución de `condicion`
ggplot(inmobiliaria_chile, aes(x = condicion)) +
  geom_bar(fill = "lightseagreen") +
  labs(title = "Distribución variable condicion",
       x = "Condición de la propiedad", y = "Frecuencia") +
  theme_minimal()

A partir del análisis observamos que cada clase presenta las siguientes situaciones:

  • Lujosa: Con 692 registros, la clase Lujosa es la menos frecuente, representando la clase minoritaria en el conjunto de datos.

  • No Lujosa: Con 808 registros corresponden a propiedades en la categoría No Lujosa. Esto sugiere que esta clase está mayoritariamente representada en el conjunto de datos.

Correlación entre variables y precio

Ahora verificamos si existe correlación entre las variables independientes cuantitativas y la variable precio.

# Calcular la matriz de correlación excluyendo las variables categóricas
correlaciones <- inmobiliaria_chile |>
  select(-c(zona, condicion)) |>
  cor()
 
print(correlaciones)
                 metros_cuadrados num_habitaciones    num_banos    antiguedad
metros_cuadrados       1.00000000      -0.01000659 0.0296860419  0.0318323801
num_habitaciones      -0.01000659       1.00000000 0.0183523337 -0.0235523045
num_banos              0.02968604       0.01835233 1.0000000000  0.0001829542
antiguedad             0.03183238      -0.02355230 0.0001829542  1.0000000000
distancia_centro      -0.06740082       0.02027301 0.0102157038  0.0115061560
precio                 0.88589340       0.15138564 0.1015090458  0.0074642040
                 distancia_centro       precio
metros_cuadrados      -0.06740082  0.885893399
num_habitaciones       0.02027301  0.151385639
num_banos              0.01021570  0.101509046
antiguedad             0.01150616  0.007464204
distancia_centro       1.00000000 -0.289938727
precio                -0.28993873  1.000000000

La matriz de correlación entre las variables evidencia lo siguiente:

  • Metros cuadrados con el precio (0.886): Existe una fuerte correlación positiva entre el tamaño de la propiedad en metros cuadrados y el precio. Esto sugiere que las propiedades más grandes tienden a ser más caras.

  • Número de habitaciones con el precio (0.151): La correlación positiva pero débil indica que las propiedades con más habitaciones tienden a ser un poco más caras, pero el impacto no es tan fuerte como el tamaño en metros cuadrados.

  • Número de baños con el precio (0.102): La correlación positiva entre el número de baños y el precio también es débil. Aunque las propiedades con más baños suelen ser más caras, esta relación no es tan significativa.

  • Antigüedad con el precio (0.007): La correlación entre la antigüedad de la propiedad y su precio es prácticamente nula. Esto sugiere que la antigüedad no es un factor determinante en el precio de las propiedades en este conjunto de datos.

  • Distancia al centro con el precio (-0.290): Hay una correlación negativa moderada entre la distancia al centro y el precio. Esto indica que las propiedades más alejadas del centro tienden a ser más baratas, lo que es consistente con la expectativa de que la proximidad al centro suele aumentar el valor de la propiedad.

En resumen, la variable que más influye en el precio de las propiedades es el tamaño en metros cuadrados, seguido por la distancia al centro.

Correlación entre variables y condicion

Ahora verificamos si existe correlación entre las variables independientes cuantitativas y la variable condicion.

# Convertir variables categóricas a numéricas para calcular correlación
df <- inmobiliaria_chile |>
  mutate(
    condicion = as.numeric(factor(condicion)),
    zona = as.numeric(factor(zona))
  )

# Calcular la correlación de cada variable numérica con la variable objetivo
cor_objetivo <- df |>
  select(-c(condicion, precio)) |>
  cor(df$condicion) |>
  as.data.frame() |>
  rownames_to_column(var = "Variable") |>
  rename(cor_objetivo = 2)

# Mostrar las correlaciones
cor_objetivo
          Variable cor_objetivo
1 metros_cuadrados  -0.79177213
2 num_habitaciones  -0.09834388
3        num_banos  -0.07476733
4       antiguedad  -0.01892413
5 distancia_centro   0.20416491
6             zona   0.05785389

En resumen, la mayoría de las variables numéricas no tienen una relación fuerte con la variable categórica binaria condicion, excepto por metros_cuadrados y distancia_centro, que muestran correlaciones más relevantes.

3. Transformación

Para esta práctica únicamente realizaremos la numerización de las variables cualitativas.

Numerización de atributos categóricos

Convertiremos algunas variables categóricas en numéricas para poder realizar el proceso predictivo.

# Convertir variables categóricas en numéricas
inmobiliaria_chile_transformado <- inmobiliaria_chile |>
  mutate(
    zona_numerica = as.numeric(factor(zona))
  )

# Verificamos el resultado
glimpse(inmobiliaria_chile_transformado)
Rows: 1,500
Columns: 9
$ metros_cuadrados <dbl> 46.78991, 166.85823, 133.28757, 95.08148, 199.62663, …
$ num_habitaciones <int> 2, 4, 3, 3, 2, 1, 3, 1, 1, 3, 2, 3, 4, 3, 1, 3, 3, 3,…
$ num_banos        <int> 3, 2, 1, 1, 1, 1, 2, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 2,…
$ antiguedad       <int> 21, 16, 12, 4, 5, 9, 12, 14, 16, 4, 2, 9, 9, 6, 4, 7,…
$ distancia_centro <dbl> 13.16033928, 11.10143025, 0.01171242, 1.10572401, 13.…
$ zona             <chr> "Centro", "Oeste", "Norte", "Oeste", "Norte", "Este",…
$ precio           <dbl> 409942.5, 780050.9, 683005.9, 539468.0, 706804.1, 823…
$ condicion        <chr> "No Lujosa", "Lujosa", "Lujosa", "No Lujosa", "Lujosa…
$ zona_numerica    <dbl> 1, 4, 3, 4, 3, 2, 1, 3, 2, 5, 1, 5, 1, 3, 4, 1, 5, 5,…

Normalización de atributos numéricos

Finalmente, normalizamos todas las variables independientes.

# Normalización de atributos numéricos
inmobiliaria_chile_transformado <- inmobiliaria_chile_transformado |>
  mutate(
    metros_normalizadas       = as.vector(scale(metros_cuadrados)),
    habitaciones_normalizadas = as.vector(scale(num_habitaciones)),
    banos_normalizadas        = as.vector(scale(num_banos)),
    antiguedad_normalizadas   = as.vector(scale(antiguedad)),
    distancia_normalizadas    = as.vector(scale(distancia_centro)),
    zona_normalizadas         = as.vector(scale(zona_numerica))
  )

# Verificamos el resultado
glimpse(inmobiliaria_chile_transformado)
Rows: 1,500
Columns: 15
$ metros_cuadrados          <dbl> 46.78991, 166.85823, 133.28757, 95.08148, 19…
$ num_habitaciones          <int> 2, 4, 3, 3, 2, 1, 3, 1, 1, 3, 2, 3, 4, 3, 1,…
$ num_banos                 <int> 3, 2, 1, 1, 1, 1, 2, 1, 1, 1, 1, 2, 1, 1, 1,…
$ antiguedad                <int> 21, 16, 12, 4, 5, 9, 12, 14, 16, 4, 2, 9, 9,…
$ distancia_centro          <dbl> 13.16033928, 11.10143025, 0.01171242, 1.1057…
$ zona                      <chr> "Centro", "Oeste", "Norte", "Oeste", "Norte"…
$ precio                    <dbl> 409942.5, 780050.9, 683005.9, 539468.0, 7068…
$ condicion                 <chr> "No Lujosa", "Lujosa", "Lujosa", "No Lujosa"…
$ zona_numerica             <dbl> 1, 4, 3, 4, 3, 2, 1, 3, 2, 5, 1, 5, 1, 3, 4,…
$ metros_normalizadas       <dbl> -1.36988821, 0.99668577, 0.33500041, -0.4180…
$ habitaciones_normalizadas <dbl> -0.7818768, 1.2298131, 0.2239681, 0.2239681,…
$ banos_normalizadas        <dbl> 2.2790228, 0.7684023, -0.7422182, -0.7422182…
$ antiguedad_normalizadas   <dbl> 2.1173422, 1.1278066, 0.3361782, -1.2470786,…
$ distancia_normalizadas    <dbl> 1.33909820, 0.85793306, -1.73372377, -1.4780…
$ zona_normalizadas         <dbl> -1.38846660, 0.72738159, 0.02209886, 0.72738…

4 y 5. Modelado y validación

Para el modelado consideramos el conjunto de datos de calidad:

# Usar el conjunto de datos de calidad
inmobiliaria_final <- inmobiliaria_chile_transformado |> 
  select(metros_normalizadas,
         habitaciones_normalizadas,
         banos_normalizadas,
         antiguedad_normalizadas,
         distancia_normalizadas,
         zona_normalizadas,
         precio,
         condicion)

# Verificamos el resultado
glimpse(inmobiliaria_final)
Rows: 1,500
Columns: 8
$ metros_normalizadas       <dbl> -1.36988821, 0.99668577, 0.33500041, -0.4180…
$ habitaciones_normalizadas <dbl> -0.7818768, 1.2298131, 0.2239681, 0.2239681,…
$ banos_normalizadas        <dbl> 2.2790228, 0.7684023, -0.7422182, -0.7422182…
$ antiguedad_normalizadas   <dbl> 2.1173422, 1.1278066, 0.3361782, -1.2470786,…
$ distancia_normalizadas    <dbl> 1.33909820, 0.85793306, -1.73372377, -1.4780…
$ zona_normalizadas         <dbl> -1.38846660, 0.72738159, 0.02209886, 0.72738…
$ precio                    <dbl> 409942.5, 780050.9, 683005.9, 539468.0, 7068…
$ condicion                 <chr> "No Lujosa", "Lujosa", "Lujosa", "No Lujosa"…

Clasificación con Naive Bayes

Usaremos el método de Naive Bayes para clasificar la variable condicion, esto es, la categoría de la propiedad: Lujosa o No Lujosa. Primero dividimos el dataset con 70% para entrenamiento y 30% para prueba.

# Dividir los datos en conjunto de entrenamiento y prueba
indices_entrenamiento <- createDataPartition(inmobiliaria_final$condicion, p = 0.70, list = FALSE)
datos_entrenamiento <- inmobiliaria_final[indices_entrenamiento, ]
datos_prueba        <- inmobiliaria_final[-indices_entrenamiento, ]

Luego se entrena y prueba el algoritmo de Naive Bayes:

# Entrenar el modelo Bayesiano Ingenuo
modelo_nb <- naiveBayes(condicion ~ 
                          metros_normalizadas + 
                          habitaciones_normalizadas + 
                          banos_normalizadas + 
                          antiguedad_normalizadas + 
                          distancia_normalizadas + 
                          zona_normalizadas,
                        data = datos_entrenamiento)

# Realizar predicciones en el conjunto de prueba
predicciones <- predict(modelo_nb, datos_prueba)

Finalmente, se evalúa el rendimiento del algoritmo con métricas de calidad:

# Calcular la matriz de confusión
matriz_confusion <- table(Prediccion = predicciones, Real = datos_prueba$condicion)
print("Matriz de Confusión:")
[1] "Matriz de Confusión:"
print(matriz_confusion)
           Real
Prediccion  Lujosa No Lujosa
  Lujosa       182        22
  No Lujosa     25       220
# Calcular métricas — "Lujosa" es la clase positiva
VP <- matriz_confusion["Lujosa", "Lujosa"]
FP <- matriz_confusion["Lujosa", "No Lujosa"]
VN <- matriz_confusion["No Lujosa", "No Lujosa"]
FN <- matriz_confusion["No Lujosa", "Lujosa"]

accuracy  <- (VP + VN) / sum(matriz_confusion)
precision <- VP / (VP + FP)
recall    <- VP / (VP + FN)
f1_score  <- 2 * (precision * recall) / (precision + recall)

print(paste("Accuracy:",   round(accuracy,  4)))
[1] "Accuracy: 0.8953"
print(paste("Precision:",  round(precision, 4)))
[1] "Precision: 0.8922"
print(paste("Recall:",     round(recall,    4)))
[1] "Recall: 0.8792"
print(paste("F1 Score:",   round(f1_score,  4)))
[1] "F1 Score: 0.8856"

Los resultados provienen de una evaluación de un modelo de clasificación binaria para predecir si un inmueble es “Lujoso” o “No Lujoso”:

  • Accuracy (Exactitud): 0.8998 — El modelo clasificó correctamente el 89.98% de los inmuebles.

  • Precision (Precisión): 0.8857 — Cuando predice “Lujosa”, acierta el 88.57% de las veces.

  • Recall (Sensibilidad): 0.8986 — El modelo identificó correctamente el 89.86% de los inmuebles realmente lujosos.

  • F1 Score: 0.8921 — Media armónica entre precisión y recall, indica un buen equilibrio entre ambas métricas.

También se pueden calcular métricas con funciones de R y generar visualizaciones:

# Métricas más detalladas usando caret
metricas <- confusionMatrix(predicciones, factor(datos_prueba$condicion))
print(metricas)
Confusion Matrix and Statistics

           Reference
Prediction  Lujosa No Lujosa
  Lujosa       182        22
  No Lujosa     25       220
                                             
               Accuracy : 0.8953             
                 95% CI : (0.8632, 0.9221)   
    No Information Rate : 0.539              
    P-Value [Acc > NIR] : <0.0000000000000002
                                             
                  Kappa : 0.7891             
                                             
 Mcnemar's Test P-Value : 0.7705             
                                             
            Sensitivity : 0.8792             
            Specificity : 0.9091             
         Pos Pred Value : 0.8922             
         Neg Pred Value : 0.8980             
             Prevalence : 0.4610             
         Detection Rate : 0.4053             
   Detection Prevalence : 0.4543             
      Balanced Accuracy : 0.8942             
                                             
       'Positive' Class : Lujosa             
                                             
# Visualización de las métricas
metricas_df <- data.frame(
  Metrica = c("Accuracy", "Precision", "Recall", "F1 Score"),
  Valor   = c(accuracy, precision, recall, f1_score)
)

ggplot(metricas_df, aes(x = Metrica, y = Valor)) +
  geom_bar(stat = "identity", fill = "skyblue") +
  ylim(0, 1) +
  geom_text(aes(label = round(Valor, 3)), vjust = -0.3) +
  labs(title = "Métricas de Evaluación — Naive Bayes",
       y = "Valor") +
  theme_minimal()

El análisis muestra que el modelo tiene un rendimiento robusto. Con una exactitud del 89.98%, el modelo clasifica correctamente la mayoría de los inmuebles. La métrica Kappa de 0.7985 refleja una buena concordancia entre predicciones y categorías reales. La sensibilidad del 89.86% y la especificidad del 90.08% muestran un buen equilibrio en la identificación de ambas clases.

Clasificación con Árboles de Decisión

Usaremos el método de árboles de decisión para clasificar la variable condicion: Lujosa o No Lujosa.

# Entrenar el modelo de árbol de decisión
modelo_arbol <- rpart(condicion ~ 
                        metros_normalizadas + 
                        habitaciones_normalizadas + 
                        banos_normalizadas + 
                        antiguedad_normalizadas + 
                        distancia_normalizadas + 
                        zona_normalizadas,
                      data = datos_entrenamiento, method = "class")

# Visualizar el árbol
rpart.plot(modelo_arbol, type = 5, extra = 106, under = TRUE, cex = 0.8)

El árbol de decisión representa un modelo para clasificar propiedades como “Lujosa” o “No Lujosa” basándose principalmente en dos variables: metros_normalizadas y distancia_normalizadas. El árbol se interpreta de la siguiente forma:

  • Nodo Raíz (metros_normalizadas): Si metros_normalizadas ≥ 0.22 va a la izquierda; si es menor, clasifica directamente como “No Lujosa” (55% de los casos, 89% de probabilidad de ser correcto).

  • Rama izquierda nivel 1: Se hace otra división sobre metros_normalizadas. Si ≥ 0.66 clasifica como “Lujosa” (29% de los casos). Si es menor, pasa al siguiente nivel.

  • Rama nivel 2 (distancia_normalizadas): Si < 1.1 clasifica como “Lujosa” (14% de los casos). Si ≥ 1.1 clasifica como “No Lujosa” (2% de los casos, 78% de probabilidad de ser correcto).

Luego, se evalúa el rendimiento del algoritmo con métricas de calidad:

# Realizar predicciones en el conjunto de prueba
predicciones <- predict(modelo_arbol, datos_prueba, type = "class")

# Calcular la matriz de confusión
matriz_confusion <- table(Prediccion = predicciones, Real = datos_prueba$condicion)
print("Matriz de Confusión:")
[1] "Matriz de Confusión:"
print(matriz_confusion)
           Real
Prediccion  Lujosa No Lujosa
  Lujosa       168        16
  No Lujosa     39       226
# Calcular métricas — "Lujosa" es la clase positiva
VP <- matriz_confusion["Lujosa", "Lujosa"]
FP <- matriz_confusion["Lujosa", "No Lujosa"]
VN <- matriz_confusion["No Lujosa", "No Lujosa"]
FN <- matriz_confusion["No Lujosa", "Lujosa"]

accuracy  <- (VP + VN) / sum(matriz_confusion)
precision <- VP / (VP + FP)
recall    <- VP / (VP + FN)
f1_score  <- 2 * (precision * recall) / (precision + recall)

print(paste("Accuracy:",   round(accuracy,  4)))
[1] "Accuracy: 0.8775"
print(paste("Precision:",  round(precision, 4)))
[1] "Precision: 0.913"
print(paste("Recall:",     round(recall,    4)))
[1] "Recall: 0.8116"
print(paste("F1 Score:",   round(f1_score,  4)))
[1] "F1 Score: 0.8593"

Los resultados del árbol de decisión muestran que:

  • Accuracy: 0.87 — El modelo clasificó correctamente el 87% de los inmuebles.

  • Precision: 0.91 — Cuando predice “Lujosa”, acierta el 91% de las veces.

  • Recall: 0.81 — El modelo identificó correctamente el 81% de los inmuebles realmente lujosos.

  • F1 Score: 0.85 — Media armónica de precisión y recall, indicando un buen desempeño general.

Predicción con Regresión Lineal Múltiple

Ahora aplicaremos regresión múltiple para predecir la variable precio a partir de las variables independientes. El modelo de regresión inicial sería:

\[ \hat{precio}= \beta_{0} + \beta_{1} \cdot metros + \beta_{2} \cdot habitaciones + \beta_{3} \cdot banos + \beta_{4} \cdot antiguedad + \beta_{5} \cdot distancia + \beta_{6} \cdot zona \]

Aplicamos esto en R:

# Crear el modelo de regresión múltiple
modelo_regresion <- lm(precio ~ 
                         metros_normalizadas +
                         habitaciones_normalizadas +
                         banos_normalizadas +
                         antiguedad_normalizadas +
                         distancia_normalizadas +
                         zona_normalizadas, 
                       data = datos_entrenamiento)

# Resumen del modelo
summary(modelo_regresion)

Call:
lm(formula = precio ~ metros_normalizadas + habitaciones_normalizadas + 
    banos_normalizadas + antiguedad_normalizadas + distancia_normalizadas + 
    zona_normalizadas, data = datos_entrenamiento)

Residuals:
    Min      1Q  Median      3Q     Max 
-198047  -42159    1099   41520  190992 

Coefficients:
                          Estimate Std. Error t value             Pr(>|t|)    
(Intercept)                 573018       1824 314.207 < 0.0000000000000002 ***
metros_normalizadas         155070       1824  85.033 < 0.0000000000000002 ***
habitaciones_normalizadas    28886       1849  15.622 < 0.0000000000000002 ***
banos_normalizadas           13677       1814   7.540    0.000000000000102 ***
antiguedad_normalizadas      -4760       1833  -2.597              0.00955 ** 
distancia_normalizadas      -41693       1839 -22.672 < 0.0000000000000002 ***
zona_normalizadas           -23130       1832 -12.626 < 0.0000000000000002 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 59080 on 1044 degrees of freedom
Multiple R-squared:  0.8903,    Adjusted R-squared:  0.8897 
F-statistic:  1412 on 6 and 1044 DF,  p-value: < 0.00000000000000022

El modelo resultante encontrado por la regresión es:

\[ precio = 573018 + 155070 \cdot metros + 28886 \cdot habitaciones + 13677 \cdot banos - 4760 \cdot antiguedad - 41693 \cdot distancia - 23130 \cdot zona \]

Ahora realizamos la validación del modelo:

# Realizar predicciones
predicciones <- predict(modelo_regresion, newdata = datos_prueba)

# Calcular métricas de validación
mse  <- mean((datos_prueba$precio - predicciones)^2)
rmse <- sqrt(mse)
r2   <- summary(modelo_regresion)$r.squared

cat("MSE:",        mse,  "\n")
MSE: 3502750687 
cat("RMSE:",       rmse, "\n")
RMSE: 59184.04 
cat("R-cuadrado:", r2,   "\n")
R-cuadrado: 0.8903059 

El resultado de la regresión muestra un rendimiento sólido del modelo. El RMSE de 59184 indica que en promedio las predicciones se desvían del valor real por esa cantidad en USD. El \(R^2\) de 0.89 significa que el modelo explica el 89% de la variabilidad en el precio de los inmuebles, lo que indica un buen ajuste general.

Finalmente, visualizamos el resultado.

ggplot(datos_prueba, aes(x = precio, y = predicciones)) +
  geom_point(alpha = 0.5) +
  geom_abline(intercept = 0, slope = 1, color = "red", linetype = "dashed") +
  labs(x = "Precio real", y = "Precio predicho",
       title = "Comparación entre precio real y predicho") +
  theme_minimal()

En la gráfica la mayoría de los puntos se alinean a lo largo de la línea diagonal roja (trazada en 45 grados), lo que indica que los precios predichos por el modelo están bastante cercanos a los precios reales. Sin embargo, existen algunos casos en los que la precisión disminuye, especialmente en los extremos del rango de precios, lo que podría ser un área de mejora para el modelo.

Actividad calificada

Contexto

En 2026, los wearables y las apps de salud como Fitbit, Apple Health, Google Fit y Garmin Connect generan millones de registros diarios de actividad física, sueño y bienestar. Empresas de salud preventiva, aseguradoras y gimnasios utilizan estos datos para identificar usuarios en riesgo, personalizar planes de bienestar y predecir métricas de salud clave.

En esta actividad trabajarás con el dataset usuarios_vitaltrack.csv, que contiene registros de usuarios de una app ecuatoriana de salud y bienestar llamada VitalTrack. Tu tarea es construir modelos de clasificación para predecir si un usuario cumple con el nivel recomendado de actividad física según la OMS, y de regresión para predecir el índice de bienestar calculado por la app.

Dataset

El archivo usuarios_vitaltrack.csv es proporcionado por el docente y debe colocarse en la carpeta Datos/ del proyecto. Contiene las siguientes variables:

Variable Tipo Descripción
edad Entero Edad del usuario en años
pasos_diarios Entero Promedio de pasos caminados por día
horas_sueno Numérico Promedio de horas de sueño por noche
frecuencia_ejercicio Entero Días a la semana que realiza ejercicio (0–7)
calorias_quemadas Numérico Promedio de calorías quemadas por día
tipo_actividad Categórica Caminata, Running, Ciclismo, Natación, Gimnasio
dispositivo Categórica Smartwatch, Smartphone, Banda fitness
provincia Categórica Pichincha, Guayas, Manabí, Azuay, Loja
nivel_estres Entero Nivel de estrés autoreportado (1–10)
activo_oms Categórica Si / No — cumple el nivel recomendado de actividad OMS (objetivo para clasificación)
indice_bienestar Numérico Índice de bienestar calculado por la app (0–100) (objetivo para regresión)

⚠️ El archivo es proporcionado por el docente. No debe ser generado por los estudiantes.

Instrucciones

1. Configuración del proyecto

Crear un nuevo proyecto en RStudio con el nombre MD_VitalTrack. Organizar las carpetas:

  • Scripts/ — scripts de código R.
  • Datos/ — dataset de la actividad.
  • Resultados/ — gráficos y tablas exportadas.

2. Pipeline de modelado

Dentro de Scripts/, crear un script R llamado modelos_vitaltrack.R con las siguientes etapas:

Etapa 1 — Carga y preprocesamiento

  • Cargar el dataset con read.csv() y explorar con glimpse() y summary().
  • Convertir las variables categóricas (tipo_actividad, dispositivo, provincia, activo_oms) a factor.
  • Normalizar las variables numéricas independientes con scale().
  • Dividir el dataset en 80% entrenamiento y 20% prueba usando createDataPartition().

Etapa 2 — Clasificación: predecir activo_oms

Aplicar los dos algoritmos de clasificación vistos en la práctica para predecir si el usuario cumple el nivel recomendado de actividad física (Si / No):

  • Naive Bayes (naiveBayes del paquete e1071):
    1. Entrenar el modelo usando todas las variables independientes.
    2. Evaluar con matriz de confusión y calcular: Accuracy, Precision, Recall y F1 Score.
    3. Generar un gráfico de barras con las métricas obtenidas.
  • Árbol de Decisión (rpart):
    1. Entrenar el árbol de decisión para clasificar activo_oms.
    2. Visualizar el árbol generado con rpart.plot().
    3. Evaluar con las mismas métricas que Naive Bayes.
    4. Comparar ambos clasificadores e indicar cuál tiene mejor desempeño y por qué.

Etapa 3 — Regresión: predecir indice_bienestar

Aplicar regresión lineal múltiple para predecir el índice de bienestar del usuario:

  1. Crear el modelo con lm() usando todas las variables independientes numéricas normalizadas.
  2. Mostrar el resumen del modelo con summary() e interpretar los coeficientes más relevantes.
  3. Evaluar el modelo calculando MSE, RMSE y \(R^2\).
  4. Generar el gráfico de valores reales vs predichos.

Etapa 4 — Interpretación

Al final del script, responder en comentarios las siguientes preguntas:

  • ¿Qué variable tiene mayor influencia en la predicción de activo_oms según el árbol de decisión?
  • ¿El nivel de estrés y las horas de sueño son predictores significativos del índice de bienestar según la regresión?
  • ¿Cuál de los dos clasificadores recomendarías implementar en la app VitalTrack y por qué?

3. Documentación

  • Incluir al inicio del script un encabezado con: nombre del curso, práctica, integrantes del grupo, fecha.
  • Comentar cada sección del código explicando qué se hace y por qué.

Entrega

Comprimir el proyecto en formato .zip con el nombre MD_Practica_3_GrupoX, donde X es el número de grupo. Subir en el plazo indicado en el aula virtual.

Criterios de Evaluación

Criterio Porcentaje
Estructura y organización del proyecto 10%
Carga, preprocesamiento y división del dataset 15%
Clasificación (Naive Bayes + Árbol de Decisión) 35%
Regresión Lineal Múltiple 25%
Interpretación y documentación 15%