# 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) Práctica 3
Clasificación y Regresión
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
0. Dominio del problema
¿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.
¿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.
¿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.
¿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.
¿El objetivo es predecir, segmentar, …?
Nuestro objetivo principal es predecir (regresión y clasificación).
¿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
Lujosaes 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): Simetros_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 conglimpse()ysummary(). - 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 (
naiveBayesdel paquetee1071):- Entrenar el modelo usando todas las variables independientes.
- Evaluar con matriz de confusión y calcular: Accuracy, Precision, Recall y F1 Score.
- Generar un gráfico de barras con las métricas obtenidas.
- Árbol de Decisión (
rpart):- Entrenar el árbol de decisión para clasificar
activo_oms. - Visualizar el árbol generado con
rpart.plot(). - Evaluar con las mismas métricas que Naive Bayes.
- Comparar ambos clasificadores e indicar cuál tiene mejor desempeño y por qué.
- Entrenar el árbol de decisión para clasificar
Etapa 3 — Regresión: predecir indice_bienestar
Aplicar regresión lineal múltiple para predecir el índice de bienestar del usuario:
- Crear el modelo con
lm()usando todas las variables independientes numéricas normalizadas. - Mostrar el resumen del modelo con
summary()e interpretar los coeficientes más relevantes. - Evaluar el modelo calculando MSE, RMSE y \(R^2\).
- 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_omssegú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% |