Una empresa inmobiliaria líder en una gran ciudad está buscando comprender en profundidad el mercado de viviendas urbanas para tomar decisiones estratégicas más informadas. La empresa posee una base de datos extensa que contiene información detallada sobre diversas propiedades residenciales disponibles en el mercado. Se requiere realizar un análisis holístico de estos datos para identificar patrones, relaciones y segmentaciones relevantes que permitan mejorar la toma de decisiones en cuanto a la compra, venta y valoración de propiedades.
Realice un filtro a la base de datos e incluya sólo las ofertas de apartamentos. Presente los primeros 3 registros de las bases y algunas tablas que comprueben la consulta.
Listar columnas del dataset
# Ver los nombres de las columnas
colnames(datos_inmobiliarios)
[1] "id" "zona" "piso" "estrato" "preciom"
[6] "areaconst" "parqueaderos" "banios" "habitaciones" "tipo"
[11] "barrio" "longitud" "latitud"
Verificar valores únicos para el atributo tipo
# validar los valores únicos para el atributo tipo
unique(datos_inmobiliarios$tipo)
[1] "Casa" "Apartamento" NA
Verificar valores faltantes en el atributo tipo
# verificar los valores NA del atributo tipo
na_tipo <- datos_inmobiliarios[is.na(datos_inmobiliarios$tipo),]
na_tipo
# A tibble: 3 × 13
id zona piso estrato preciom areaconst parqueaderos banios habitaciones
<dbl> <chr> <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 NA <NA> <NA> NA NA NA NA NA NA
2 NA <NA> <NA> NA NA NA NA NA NA
3 NA <NA> <NA> NA 330 NA NA NA NA
# ℹ 4 more variables: tipo <chr>, barrio <chr>, longitud <dbl>, latitud <dbl>
Se filtra el dataset por tipo Apartamento y se presentan los 3 primeros registros.
# Filtrar el dataset por apartamento
datos_aptos <- datos_inmobiliarios[datos_inmobiliarios$tipo == "Apartamento" & !is.na(datos_inmobiliarios$tipo),]
head(datos_aptos,3)
# A tibble: 3 × 13
id zona piso estrato preciom areaconst parqueaderos banios habitaciones
<dbl> <chr> <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 1212 Zona N… 01 5 260 90 1 2 3
2 1724 Zona N… 01 5 240 87 1 3 3
3 2326 Zona N… 01 4 220 52 2 2 3
# ℹ 4 more variables: tipo <chr>, barrio <chr>, longitud <dbl>, latitud <dbl>
Dimensión del dataset filtrado
dim(datos_aptos)
[1] 5100 13
Se cuenta con 5100 registros y 13 atributos
Valores faltantes
colSums(is.na(datos_aptos))
id zona piso estrato preciom areaconst
0 0 1381 0 0 0
parqueaderos banios habitaciones tipo barrio longitud
869 0 0 0 0 0
latitud
0
En esta sección se realizará el análisis de los atributos piso y parqueaderos para determinar los valores para la imputación de los datos faltantes.
Se realizará una revisión de la variable piso para identificar posibles estrategias para su imputación basado en el estrato.
Convertir piso a una variable numérica
Para poder calcular medidas de tendencia central se convierte a númerico el tipo de la variable piso.
datos_aptos$piso <- as.numeric(datos_aptos$piso)
Medidas de tendencia para piso
# función para calcular moda
get_mode <- function(x) {
# Eliminar NAs
x <- x[!is.na(x)]
# Calcular la moda
ux <- unique(x)
if (length(ux) == 0) {
return(NA) # Si todos los valores son NA, retornar NA
} else {
return(ux[which.max(tabulate(match(x, ux)))])
}
}
# Calcular media, mediana y moda por estrato
results <- datos_aptos %>%
group_by(estrato) %>%
summarise(
media = mean(piso, na.rm=TRUE),
mediana = median(piso, na.rm=TRUE),
moda= get_mode(piso)
)
#Mostrar resultados
print(results)
# A tibble: 4 × 4
estrato media mediana moda
<dbl> <dbl> <dbl> <dbl>
1 3 3.43 3 5
2 4 4.53 4 5
3 5 4.91 4 3
4 6 4.95 4 3
Tabla de distribución de piso por estrato
# Crear una tabla de frecuencia cruzada entre 'piso' y 'estrato'
tabla_frecuencia <- table(datos_aptos$piso, datos_aptos$estrato)
# Mostrar la tabla de frecuencias
print(tabla_frecuencia)
3 4 5 6
1 68 130 141 91
2 76 146 169 121
3 82 150 212 129
4 79 160 191 115
5 121 174 153 116
6 2 55 103 83
7 2 63 84 51
8 7 71 80 53
9 0 44 53 49
10 2 37 58 31
11 2 14 39 29
12 0 12 45 26
Imputación variable piso
# Definir la imputación con moda para estrato 3 y mediana para los estratos restantes
imputacion_piso_x_estrato <- c("3" = 5, "4" = 4, "5" = 4, "6" = 4)
# Imputar los valores faltantes con la moda correspondiente al estrato
datos_aptos <- datos_aptos %>%
mutate(piso = ifelse(is.na(piso), imputacion_piso_x_estrato[as.character(estrato)], piso))
cat("Faltantes en variable piso: ", sum(is.na(datos_aptos$piso)))
Faltantes en variable piso: 0
Tabla de distribución de parqueaderos por estrato
table(datos_aptos$parqueaderos, datos_aptos$estrato)
3 4 5 6
1 280 921 971 126
2 14 120 640 810
3 2 3 30 217
4 0 2 16 70
5 0 0 0 4
6 0 0 0 2
7 0 0 1 0
10 0 1 1 0
Datos de parqueaderos faltantes por estrato
# Contar la cantidad de valores faltantes por 'estrato'
na_parqueaderos_por_estrato <- datos_aptos %>%
group_by(estrato) %>%
summarise(faltantes_parqueaderos = sum(is.na(parqueaderos)))
# Mostrar los resultados
print(na_parqueaderos_por_estrato)
# A tibble: 4 × 2
estrato faltantes_parqueaderos
<dbl> <int>
1 3 343
2 4 357
3 5 107
4 6 62
Medidas de tendencia para parqueaderos
# Calcular media, mediana y moda por estrato
results <- datos_aptos %>%
group_by(estrato) %>%
summarise(
media = mean(parqueaderos, na.rm=TRUE),
mediana = median(parqueaderos, na.rm=TRUE),
moda= get_mode(parqueaderos)
)
#Mostrar resultados
print(results)
# A tibble: 4 × 4
estrato media mediana moda
<dbl> <dbl> <dbl> <dbl>
1 3 1.06 1 1
2 4 1.13 1 1
3 5 1.46 1 1
4 6 2.20 2 2
Imputación de datos para la variable parqueaderos
# Definir la imputación con mediana
imputacion_parqueadero_x_estrato <- c("3" = 1, "4" = 1, "5" = 1, "6" = 2)
# Imputar los valores faltantes con la moda correspondiente al estrato
datos_aptos <- datos_aptos %>%
mutate(parqueaderos = ifelse(is.na(parqueaderos), imputacion_parqueadero_x_estrato[as.character(estrato)], parqueaderos))
Verificación de datos faltantes
colSums(is.na(datos_aptos))
id zona piso estrato preciom areaconst
0 0 0 0 0 0
parqueaderos banios habitaciones tipo barrio longitud
0 0 0 0 0 0
latitud
0
En este punto se cuenta con un dataset ajustado sin datos faltantes y filtrado por Apartamentos.
Tabla zona
table(datos_aptos$zona)
Zona Centro Zona Norte Zona Oeste Zona Oriente Zona Sur
24 1198 1029 62 2787
Tabla piso
table(datos_aptos$piso)
1 2 3 4 5 6 7 8 9 10 11 12
430 512 573 1728 762 243 200 211 146 128 84 83
Tabla estrato
table(datos_aptos$estrato)
3 4 5 6
639 1404 1766 1291
Tabla baños
table(datos_aptos$banios)
0 1 2 3 4 5 6 7 8
14 400 2502 1200 635 301 39 8 1
Tabla habitaciones
table(datos_aptos$habitaciones)
0 1 2 3 4 5 6 7 9
21 49 859 3384 714 63 8 1 1
Tanto en la variable baños como en la variable habitaciones tenemos 14 y 21 viviendas respectivamente con el valor 0, si bien esto no es de esperarse se dejarán dichos datos para el estudio.
Resumen estadístico de los atributos
summary(datos_aptos)
id zona piso estrato
Min. : 3 Length:5100 Min. : 1.000 Min. :3.000
1st Qu.:2180 Class :character 1st Qu.: 3.000 1st Qu.:4.000
Median :4158 Mode :character Median : 4.000 Median :5.000
Mean :4284 Mean : 4.501 Mean :4.727
3rd Qu.:6556 3rd Qu.: 5.000 3rd Qu.:6.000
Max. :8317 Max. :12.000 Max. :6.000
preciom areaconst parqueaderos banios
Min. : 58.0 Min. : 35.0 Min. : 1.000 Min. :0.000
1st Qu.: 175.0 1st Qu.: 68.0 1st Qu.: 1.000 1st Qu.:2.000
Median : 279.0 Median : 90.0 Median : 1.000 Median :2.000
Mean : 366.9 Mean :112.8 Mean : 1.483 Mean :2.617
3rd Qu.: 430.0 3rd Qu.:130.0 3rd Qu.: 2.000 3rd Qu.:3.000
Max. :1950.0 Max. :932.0 Max. :10.000 Max. :8.000
habitaciones tipo barrio longitud
Min. :0.000 Length:5100 Length:5100 Min. :-76.59
1st Qu.:3.000 Class :character Class :character 1st Qu.:-76.54
Median :3.000 Mode :character Mode :character Median :-76.53
Mean :2.971 Mean :-76.53
3rd Qu.:3.000 3rd Qu.:-76.52
Max. :9.000 Max. :-76.46
latitud
Min. :3.334
1st Qu.:3.380
Median :3.419
Mean :3.419
3rd Qu.:3.453
Max. :3.498
Identificación de la estructura del dataset y los tipos de datos
str(datos_aptos)
tibble [5,100 × 13] (S3: tbl_df/tbl/data.frame)
$ id : num [1:5100] 1212 1724 2326 4386 7497 ...
$ zona : chr [1:5100] "Zona Norte" "Zona Norte" "Zona Norte" "Zona Norte" ...
$ piso : num [1:5100] 1 1 1 1 2 3 3 3 4 5 ...
$ estrato : num [1:5100] 5 5 4 5 6 4 5 3 3 6 ...
$ preciom : num [1:5100] 260 240 220 310 520 320 385 100 175 820 ...
$ areaconst : num [1:5100] 90 87 52 137 98 108 103 49 80 377 ...
$ parqueaderos: num [1:5100] 1 1 2 2 2 2 2 1 1 1 ...
$ banios : num [1:5100] 2 3 2 3 2 3 2 1 2 4 ...
$ habitaciones: num [1:5100] 3 3 3 4 2 3 3 2 3 4 ...
$ tipo : chr [1:5100] "Apartamento" "Apartamento" "Apartamento" "Apartamento" ...
$ barrio : chr [1:5100] "acopi" "acopi" "acopi" "acopi" ...
$ longitud : num [1:5100] -76.5 -76.5 -76.5 -76.5 -76.5 ...
$ latitud : num [1:5100] 3.46 3.37 3.43 3.38 3.44 ...
El dataset depurado presenta:
El id es numérica pero no ofrece información relevante para el caso de estudio. El barrio es categórico, pero no está normalizado y no será tenido en cuenta en el estudio, en su lugar se hará uso de latitud y longitud para determinar ubicación del inmueble, además de contar también con la zona.
Eliminación de variables no requeridas en el estudio
# Eliminar latitud, longitud y barrio
df_aptos <- subset(datos_aptos, select = -c(id, barrio))
head(df_aptos,3)
# A tibble: 3 × 11
zona piso estrato preciom areaconst parqueaderos banios habitaciones tipo
<chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <chr>
1 Zona N… 1 5 260 90 1 2 3 Apar…
2 Zona N… 1 5 240 87 1 3 3 Apar…
3 Zona N… 1 4 220 52 2 2 3 Apar…
# ℹ 2 more variables: longitud <dbl>, latitud <dbl>
Examinamos los valores de cada registro de la base de datos con su tipo de variable, en la siguiente tabla
glimpse(df_aptos)
Rows: 5,100
Columns: 11
$ zona <chr> "Zona Norte", "Zona Norte", "Zona Norte", "Zona Norte", "…
$ piso <dbl> 1, 1, 1, 1, 2, 3, 3, 3, 4, 5, 5, 5, 5, 5, 6, 4, 5, 4, 4, …
$ estrato <dbl> 5, 5, 4, 5, 6, 4, 5, 3, 3, 6, 6, 4, 5, 6, 5, 5, 3, 5, 5, …
$ preciom <dbl> 260, 240, 220, 310, 520, 320, 385, 100, 175, 820, 450, 29…
$ areaconst <dbl> 90, 87, 52, 137, 98, 108, 103, 49, 80, 377, 103, 96, 124,…
$ parqueaderos <dbl> 1, 1, 2, 2, 2, 2, 2, 1, 1, 1, 2, 1, 3, 2, 1, 1, 1, 1, 1, …
$ banios <dbl> 2, 3, 2, 3, 2, 3, 2, 1, 2, 4, 3, 2, 3, 3, 1, 3, 1, 4, 2, …
$ habitaciones <dbl> 3, 3, 3, 4, 2, 3, 3, 2, 3, 4, 4, 3, 3, 4, 2, 3, 5, 4, 2, …
$ tipo <chr> "Apartamento", "Apartamento", "Apartamento", "Apartamento…
$ longitud <dbl> -76.51350, -76.51700, -76.51974, -76.53105, -76.54999, -7…
$ latitud <dbl> 3.45891, 3.36971, 3.42627, 3.38296, 3.43505, 3.40770, 3.4…
Realice un análisis exploratorio de datos enfocado en la correlación entre la variable respuesta (precio de la casa) en función del área construida, estrato, numero de baños, numero de habitaciones y zona donde se ubica la vivienda. Use gráficos interactivos con el paquete plotly e interprete los resultados.
# Definir la paleta de colores para las zonas
pal <- colorFactor(palette = c( "black","blue", "purple", "green","red"),
domain = df_aptos$zona)
leaflet(data = df_aptos) %>%
addTiles() %>%
setView(lng = -76.51595234451665, lat = 3.436834062816008, zoom = 10) %>%
addCircleMarkers(lng = df_aptos$longitud,
lat = df_aptos$latitud,
stroke = FALSE,
fillOpacity = 0.7,
radius = 4,
color = ~pal(zona), # Usar la paleta para asignar colores
label = ~as.character(zona)) # Opcional: agregar etiquetas para cada zona
Considerando que se busca analizar las dinámicas de las ofertas en diferentes partes de la ciudad y su relación con el precio y el estrato socioeconómico, se continuará con el análisis exploratorio de estas relacciones para tener una idea de su comportamiento.
Distribución de precio de inmuebles por zona
# Crear el gráfico de violín con plotly
fig <- plot_ly(df_aptos, x = ~zona, y = ~preciom, type = "violin",
box = list(visible = TRUE), # Mostrar un boxplot dentro del violín
meanline = list(visible = TRUE), # Mostrar la línea de la media
points = "all", # Mostrar todos los puntos
jitter = 0.3, # Ajustar el nivel de dispersión de los puntos
pointpos = -0.5, # Ajustar la posición de los puntos
color = ~zona, # Usar 'zona' para asignar colores
colors = c("cyan", "orange", "green", "purple"))
# Personalizar el gráfico
fig <- fig %>% layout(
title = list(
text = "Distribución del Precio por Zona (Violin Plot Interactivo)",
y = 0.95,
x = 0.5,
xanchor = "center",
yanchor = "top"
),
xaxis = list(title = "Zona"),
yaxis = list(title = "Precio (Mill)")
)
# Mostrar el gráfico
fig
# Crear un gráfico de barras con plotly
df_aggregated <- aggregate(preciom ~ zona, data = df_aptos, FUN = mean)
fig <- plot_ly(df_aggregated, x = ~zona, y = ~preciom, type = "bar",
marker = list(color = c("cyan", "orange", "green", "purple")))
# Personalizar el gráfico
fig <- fig %>% layout(
title = list(
text = "Promedio del Precio por Zona (Bar Plot Interactivo)",
y = 0.95,
x = 0.5,
xanchor = "center",
yanchor = "top"
),
xaxis = list(title = "Zona"),
yaxis = list(title = "Precio Promedio (Mill)")
)
# Mostrar el gráfico
fig
Diagrama de estrato en relación al precio
# Crear el gráfico de caja con plotly
# tratar estrato como un factor
df_aptos_graph <- df_aptos
df_aptos_graph$estrato <- as.factor(df_aptos$estrato)
fig <- plot_ly(df_aptos_graph, x = ~estrato, y = ~preciom, type = "box",
boxpoints = "all", # Muestra todos los puntos
jitter = 0.3, # Ajusta el nivel de dispersión de los puntos
pointpos = -1.8, # Ajusta la posición de los puntos
color = ~estrato, # Usar 'zona' para asignar colores
colors = c( "cyan", "orange", "green", "purple"),
line = list(color = "black"), # Color del borde de las cajas
marker = list(color = "pink"))
# Personalizar el gráfico
fig <- fig %>% layout(
title = list(
text = "Precio por Estrato (Interactivo)",
y = 0.95, # Ajustar el margen superior del título (1 es el tope superior, 0 es el inferior)
x = 0.5, # Centrar el título horizontalmente
xanchor = "center",
yanchor = "top"
),
xaxis = list(title = "Estrato"),
yaxis = list(title = "Precio (Mill)"),
showlegend = TRUE
)
# Mostrar el gráfico
fig
Diagrama de Precio en relación al area construida
# Crear el diagrama de dispersión
fig <- plot_ly(df_aptos_graph, x = ~areaconst, y = ~preciom, type = "scatter",
mode = "markers", # Modo de marcador para un diagrama de dispersión
color = ~areaconst, # Usar 'areaconst' para asignar colores
colors = c("cyan", "orange", "green", "purple"), # Especificar los colores
marker = list(size = 8, opacity = 0.6)) %>%
layout( title = list(
text = "Diagrama de Dispersión: Precio vs Área)",
y = 0.95, # Ajustar el margen superior
x = 0.5, # Centrar el título horizontalmente
xanchor = "center",
yanchor = "top"
),
xaxis = list(title = "Área"),
yaxis = list(title = "Precio (Mill)"))
# Mostrar el gráfico
fig
Precio por número de baños
# Crear categorías de baños
df_aptos_graph <- df_aptos_graph %>%
mutate(banios_cat = cut(banios, breaks = 5, labels = c("1-2", "2-3", "3-4", "4-5", "5+")))
fig <- plot_ly(df_aptos_graph, x = ~banios_cat, y = ~preciom, type = "box",
boxpoints = "all", # Muestra todos los puntos
color = ~banios_cat, # Colorear los boxplots por intervalo de baños
colors = c("cyan", "orange", "green", "purple", "gray"),
line = list(color = "black"), # Color del borde de las cajas
marker = list(color = "gray")) %>%
layout(
title = list(
text = "Distribución del Precio por Intervalo de Número de Baños",
y = 0.95, # Ajustar el margen superior
x = 0.5, # Centrar el título horizontalmente
xanchor = "center",
yanchor = "top"
),
xaxis = list(title = "Número de Baños (Categorías)"),
yaxis = list(title = "Precio (Mill)"))
fig
Precio por número de habitaciones
fig <- plot_ly(df_aptos_graph, x = ~factor(habitaciones), y = ~preciom,
type = "box",
boxpoints = "all", # Muestra todos los puntos dentro de las cajas
jitter = 0.3, # Ajusta el nivel de dispersión de los puntos
pointpos = -1.8, # Ajusta la posición de los puntos
color = ~factor(habitaciones), # Colorear las cajas por número de habitaciones
colors = c("cyan", "orange", "green", "purple", "red", "blue"), # Cambia los colores según sea necesario
line = list(color = "black"), # Color del borde de las cajas
marker = list(color = "gray")) %>%
layout(
title = list(
text = "Distribución del Precio por Número de Habitaciones",
y = 0.95, # Ajustar el margen superior
x = 0.5, # Centrar el título horizontalmente
xanchor = "center",
yanchor = "top"
),
xaxis = list(title = "Número de Habitaciones"),
yaxis = list(title = "Precio (Mill)"),
showlegend = FALSE) # Opcional: Desactivar la leyenda si es innecesaria
fig
Tabla de frecuencias de estrato y número de habitaciones
# Crear una tabla de distribución con estratos en las filas y conteo de habitaciones en las columnas
tabla_distribucion <- df_aptos %>%
group_by(estrato, habitaciones) %>%
summarise(count = n(), .groups = 'drop') %>%
pivot_wider(names_from = habitaciones, values_from = count, values_fill = list(count = 0))
# Imprimir la tabla
print(tabla_distribucion)
# A tibble: 4 × 10
estrato `0` `1` `2` `3` `4` `5` `6` `9` `7`
<dbl> <int> <int> <int> <int> <int> <int> <int> <int> <int>
1 3 2 4 100 500 23 7 2 1 0
2 4 7 5 371 922 94 3 2 0 0
3 5 9 24 248 1192 264 27 1 0 1
4 6 3 16 140 770 333 26 3 0 0
Precio por número de parqueaderos
fig <- plot_ly(df_aptos, x = ~factor(parqueaderos), y = ~preciom,
type = "box",
boxpoints = "all", # Muestra todos los puntos dentro de las cajas
jitter = 0.3, # Ajusta el nivel de dispersión de los puntos
pointpos = -1.8, # Ajusta la posición de los puntos
color = ~factor(parqueaderos), # Colorear las cajas por número de parqueaderos
colors = c("cyan", "orange", "green", "purple", "red", "blue"), # Cambia los colores según sea necesario
line = list(color = "black"), # Color del borde de las cajas
marker = list(color = "gray")) %>%
layout(
title = list(
text = "Distribución del Precio por Número de Parqueaderos",
y = 0.95, # Ajustar el margen superior
x = 0.5, # Centrar el título horizontalmente
xanchor = "center",
yanchor = "top"
),
xaxis = list(title = "Número de Parqueaderos"),
yaxis = list(title = "Precio (Mill)"),
showlegend = FALSE) # Opcional: Desactivar la leyenda si es innecesaria
fig
variables_numericas <- df_aptos %>%
dplyr::select(preciom, areaconst, banios, habitaciones, parqueaderos, piso, estrato)
# Calcular la matriz de correlación
matriz_correlacion <- cor(variables_numericas, use = "complete.obs")
# Visualizar la matriz de correlación
corrplot(matriz_correlacion, method = "circle", type = "lower",
col = colorRampPalette(c("blue", "white", "red"))(200),
addCoef.col = "black", # Añadir los coeficientes en la gráfica
tl.col = "black", # Color del texto de las etiquetas
diag = FALSE) # No mostrar la diagonal
library(GGally)
variables_interes <- df_aptos %>%
dplyr::select(preciom, areaconst, banios, habitaciones, parqueaderos, piso, estrato)
# Suprimir las advertencias durante la generación del gráfico
suppressWarnings({
# Crear el gráfico de pares
pair_plot <- ggpairs(variables_interes)
})
# Mostrar el gráfico de pares
print(pair_plot)
Correlación con respecto al precio
correlacion_precio <- matriz_correlacion["preciom", ]
print(correlacion_precio)
preciom areaconst banios habitaciones parqueaderos piso
1.0000000 0.8287437 0.7404732 0.2974940 0.7512461 0.1387852
estrato
0.6672717
Estime un modelo de regresión lineal múltiple con las variables del punto anterior (precio = f(área construida, estrato, número de cuartos, número de parqueaderos, número de baños ) ) e interprete los coeficientes si son estadísticamente significativos. Las interpretaciones deber están contextualizadas y discutir si los resultados son lógicos. Adicionalmente interprete el coeficiente R2 y discuta el ajuste del modelo e implicaciones (que podrían hacer para mejorarlo)
Primero se revisarán las variables y sus tipos.
str(df_aptos)
tibble [5,100 × 11] (S3: tbl_df/tbl/data.frame)
$ zona : chr [1:5100] "Zona Norte" "Zona Norte" "Zona Norte" "Zona Norte" ...
$ piso : num [1:5100] 1 1 1 1 2 3 3 3 4 5 ...
$ estrato : num [1:5100] 5 5 4 5 6 4 5 3 3 6 ...
$ preciom : num [1:5100] 260 240 220 310 520 320 385 100 175 820 ...
$ areaconst : num [1:5100] 90 87 52 137 98 108 103 49 80 377 ...
$ parqueaderos: num [1:5100] 1 1 2 2 2 2 2 1 1 1 ...
$ banios : num [1:5100] 2 3 2 3 2 3 2 1 2 4 ...
$ habitaciones: num [1:5100] 3 3 3 4 2 3 3 2 3 4 ...
$ tipo : chr [1:5100] "Apartamento" "Apartamento" "Apartamento" "Apartamento" ...
$ longitud : num [1:5100] -76.5 -76.5 -76.5 -76.5 -76.5 ...
$ latitud : num [1:5100] 3.46 3.37 3.43 3.38 3.44 ...
Codificación estrato
Se utilizará one-hot encoding para codificar la variable estrato, dejando como categoría de referencia el estrato 3.
df_aptos_clean <- df_aptos
df_aptos_clean$E4 <- as.numeric(df_aptos_clean$estrato==4)
df_aptos_clean$E5 <- as.numeric(df_aptos_clean$estrato==5)
df_aptos_clean$E6 <- as.numeric(df_aptos_clean$estrato==6)
head(df_aptos_clean,5)
# A tibble: 5 × 14
zona piso estrato preciom areaconst parqueaderos banios habitaciones tipo
<chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <chr>
1 Zona N… 1 5 260 90 1 2 3 Apar…
2 Zona N… 1 5 240 87 1 3 3 Apar…
3 Zona N… 1 4 220 52 2 2 3 Apar…
4 Zona N… 1 5 310 137 2 3 4 Apar…
5 Zona N… 2 6 520 98 2 2 2 Apar…
# ℹ 5 more variables: longitud <dbl>, latitud <dbl>, E4 <dbl>, E5 <dbl>,
# E6 <dbl>
df_aptos_clean$tipo <- NULL
head(df_aptos_clean,5)
# A tibble: 5 × 13
zona piso estrato preciom areaconst parqueaderos banios habitaciones
<chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 Zona Norte 1 5 260 90 1 2 3
2 Zona Norte 1 5 240 87 1 3 3
3 Zona Norte 1 4 220 52 2 2 3
4 Zona Norte 1 5 310 137 2 3 4
5 Zona Norte 2 6 520 98 2 2 2
# ℹ 5 more variables: longitud <dbl>, latitud <dbl>, E4 <dbl>, E5 <dbl>,
# E6 <dbl>
Se dejará como categoría de referencia la Zona Centro
df_aptos_clean$Z1 <- as.numeric(df_aptos_clean$zona=="Zona Sur")
df_aptos_clean$Z2 <- as.numeric(df_aptos_clean$zona=="Zona Norte")
df_aptos_clean$Z3 <- as.numeric(df_aptos_clean$zona=="Zona Oeste")
df_aptos_clean$Z4 <- as.numeric(df_aptos_clean$zona=="Zona Oriente")
head(df_aptos_clean,5)
# A tibble: 5 × 17
zona piso estrato preciom areaconst parqueaderos banios habitaciones
<chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 Zona Norte 1 5 260 90 1 2 3
2 Zona Norte 1 5 240 87 1 3 3
3 Zona Norte 1 4 220 52 2 2 3
4 Zona Norte 1 5 310 137 2 3 4
5 Zona Norte 2 6 520 98 2 2 2
# ℹ 9 more variables: longitud <dbl>, latitud <dbl>, E4 <dbl>, E5 <dbl>,
# E6 <dbl>, Z1 <dbl>, Z2 <dbl>, Z3 <dbl>, Z4 <dbl>
str(df_aptos_clean)
tibble [5,100 × 17] (S3: tbl_df/tbl/data.frame)
$ zona : chr [1:5100] "Zona Norte" "Zona Norte" "Zona Norte" "Zona Norte" ...
$ piso : num [1:5100] 1 1 1 1 2 3 3 3 4 5 ...
$ estrato : num [1:5100] 5 5 4 5 6 4 5 3 3 6 ...
$ preciom : num [1:5100] 260 240 220 310 520 320 385 100 175 820 ...
$ areaconst : num [1:5100] 90 87 52 137 98 108 103 49 80 377 ...
$ parqueaderos: num [1:5100] 1 1 2 2 2 2 2 1 1 1 ...
$ banios : num [1:5100] 2 3 2 3 2 3 2 1 2 4 ...
$ habitaciones: num [1:5100] 3 3 3 4 2 3 3 2 3 4 ...
$ longitud : num [1:5100] -76.5 -76.5 -76.5 -76.5 -76.5 ...
$ latitud : num [1:5100] 3.46 3.37 3.43 3.38 3.44 ...
$ E4 : num [1:5100] 0 0 1 0 0 1 0 0 0 0 ...
$ E5 : num [1:5100] 1 1 0 1 0 0 1 0 0 0 ...
$ E6 : num [1:5100] 0 0 0 0 1 0 0 0 0 1 ...
$ Z1 : num [1:5100] 0 0 0 0 0 0 0 0 0 0 ...
$ Z2 : num [1:5100] 1 1 1 1 1 1 1 1 1 1 ...
$ Z3 : num [1:5100] 0 0 0 0 0 0 0 0 0 0 ...
$ Z4 : num [1:5100] 0 0 0 0 0 0 0 0 0 0 ...
Ahora el dataset está preparado para realizar el entrenamiento del modelo de regresión lineal
Se realizará el entrenamiento de un modelo de regresión logística tomando como variables predictoras el área, el estrato, el número de baños y el número de parqueaderos y con la variable dependiente precio.
Resultados del modelo
set.seed(42)
model_aptos <- lm(preciom ~ areaconst + E4 +E5 + E6 + parqueaderos + banios , data = df_aptos_clean)
summary(model_aptos)
Call:
lm(formula = preciom ~ areaconst + E4 + E5 + E6 + parqueaderos +
banios, data = df_aptos_clean)
Residuals:
Min 1Q Median 3Q Max
-1554.49 -46.19 0.22 39.55 979.69
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) -142.86989 6.56302 -21.769 < 2e-16 ***
areaconst 1.87724 0.04184 44.871 < 2e-16 ***
E4 33.22883 6.24342 5.322 1.07e-07 ***
E5 55.14327 6.33231 8.708 < 2e-16 ***
E6 195.44643 7.93213 24.640 < 2e-16 ***
parqueaderos 87.11570 3.79822 22.936 < 2e-16 ***
banios 34.83798 2.69424 12.931 < 2e-16 ***
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Residual standard error: 128.6 on 5093 degrees of freedom
Multiple R-squared: 0.8026, Adjusted R-squared: 0.8024
F-statistic: 3452 on 6 and 5093 DF, p-value: < 2.2e-16
Qué hacer para mejorar el modelo?
Para mejorar el modelo podemos plantear quitar o evaluar algunas variables que puedan explicar de mejor manera el modelo, por ejemplo, solo dejar las relaciones fuertes en este caso precio Vs area + baños y con eso determinar si obtenemos un mejor resultado de \(R^2\).
La división aleatoria de datos en entrenamiento y prueba es una práctica estándar y válida, pero puede ser mejorada mediante estratificación para asegurar que la distribución de variables importantes (como precios) esté bien representada en ambos conjuntos.
La validación cruzada es otra técnica recomendada para obtener una evaluación más exhaustiva del rendimiento del modelo.
Realice la validación de supuestos del modelo e interprete los resultados (no es necesario corregir en caso de presentar problemas, solo realizar sugerencias de que se podría hacer).
Linealidad
Supuesto -> La relación entre las variables independientes y la variable dependiente debe ser lineal.
Validación -> Gráfico de Residuos vs Valores ajustados. En este gráfico, se evalúa si los residuos se distribuyen de manera aleatoria alrededor de la línea horizontal en 0.
Homocedasticidad
Supuesto -> La varianza de los errores (residuos) es constante en todos los niveles de las variables independientes.
Validación -> Gráfico de Residuos vs Valores ajustados. Los residuos deben tener una dispersión constante a lo largo de todos los valores ajustados.
# Gráfico de residuos vs. valores ajustados
plot(model_aptos, which = 1)
# Gráfico de residuos vs. variables independientes
plot(model_aptos, which = 3)
Prueba de Breush-Pagan
bptest(model_aptos)
studentized Breusch-Pagan test
data: model_aptos
BP = 1456.9, df = 6, p-value < 2.2e-16
Sugerencias
Supuesto -> Los errores (ϵ) deben ser independientes entre sí. Esto significa que no debe haber correlación entre los residuos a lo largo de las observaciones.
Validación
# Calcular el Durbin-Watson Statistic
dw_statistic <- durbinWatsonTest(model_aptos) # `model` es el modelo ajustado
print(dw_statistic) # Mostrar el resultado del test
lag Autocorrelation D-W Statistic p-value
1 0.147408 1.704885 0
Alternative hypothesis: rho != 0
El Durbin-Watson Statistic varía entre 0 y 4.
En este caso, el valor de 1.704885 está algo por debajo de 2, lo que sugiere la posibilidad de autocorrelación positiva, aunque no es una indicación fuerte por sí sola.
El p-value asociado es 0, lo que indica que la hipótesis alternativa (que existe autocorrelación) es muy significativa. En este contexto, el p-value bajo sugiere que hay evidencia significativa para rechazar la hipótesis nula de que no hay autocorrelación (rho = 0).
Conclusión
Sugerencias
Supuesto -> Los errores deben seguir una distribución normal. Esto es importante para hacer inferencias estadísticas válidas.
Validación
Gráfico 2 - Q-Q Residuals
# Gráfico Q-Q de los residuos
qqnorm(resid(model_aptos))
qqline(resid(model_aptos))
Shapiro-Test
set.seed(123) # Para reproducibilidad
muestra_residuos <- sample(model_aptos$residuals, 5000) # Selecciona una muestra de 5000 residuos
shapiro.test(muestra_residuos)
Shapiro-Wilk normality test
data: muestra_residuos
W = 0.82536, p-value < 2.2e-16
Realice una partición en los datos de forma aleatoria donde 70% sea un set para entrenar el modelo y 30% para prueba. Estime el modelo con la muestra del 70%. Muestre los resultados
Creación del dataset de entrenamiento y prueba
# opcion con estratificacion
set.seed(42)
train_index <- createDataPartition(df_aptos_clean$preciom, p = 0.7, list = FALSE)
train_data <- df_aptos_clean[train_index, ]
test_data <- df_aptos_clean[-train_index, ]
Entrenamiento del modelo
set.seed(42)
n <- nrow(df_aptos)
train_indices <- sample(1:n, size = 0.7 * n)
train_data <- df_aptos_clean[train_indices, ] # 70% training
test_data <- df_aptos_clean[-train_indices, ] # 30% test
modelo_train <- lm(preciom ~ areaconst + E4 +E5 + E6 + parqueaderos + banios , data = df_aptos_clean)
summary(modelo_train)
Call:
lm(formula = preciom ~ areaconst + E4 + E5 + E6 + parqueaderos +
banios, data = df_aptos_clean)
Residuals:
Min 1Q Median 3Q Max
-1554.49 -46.19 0.22 39.55 979.69
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) -142.86989 6.56302 -21.769 < 2e-16 ***
areaconst 1.87724 0.04184 44.871 < 2e-16 ***
E4 33.22883 6.24342 5.322 1.07e-07 ***
E5 55.14327 6.33231 8.708 < 2e-16 ***
E6 195.44643 7.93213 24.640 < 2e-16 ***
parqueaderos 87.11570 3.79822 22.936 < 2e-16 ***
banios 34.83798 2.69424 12.931 < 2e-16 ***
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Residual standard error: 128.6 on 5093 degrees of freedom
Multiple R-squared: 0.8026, Adjusted R-squared: 0.8024
F-statistic: 3452 on 6 and 5093 DF, p-value: < 2.2e-16
Realice predicciones con el modelo anterior usando los datos de prueba (30%)
predicciones_test <- predict(modelo_train, newdata = test_data)
ggplot(data = test_data, aes(x = preciom, y = predicciones_test)) +
geom_point() +
geom_abline(intercept = 0, slope = 1, color = "red") +
labs(x = "Valores Reales", y = "Valores Predichos", title = "Valores Reales vs. Valores Predichos")
Calcule el error cuadrático medio, el error absoluto medio y el R2, interprete.
predicciones_test <- predict(modelo_train, newdata = test_data)
#Cálculo de MSE (Error cuadrático medio)
mse <- mean((test_data$preciom - predicciones_test)^2)
# Cálculo del MAE (Error Absoluto medio)
mae <- mean(abs(test_data$preciom - predicciones_test))
# Cálculo del R^2
ss_total <- sum((test_data$preciom - mean(test_data$preciom))^2)
ss_residual <- sum((test_data$preciom - predicciones_test)^2)
r_squared <- 1 - (ss_residual / ss_total)
# Presentar resultados
cat("El Error Cuadrático Medio (MSE) es: ", mse, "\n")
El Error Cuadrático Medio (MSE) es: 17095.25
cat("El Error Absoluto Medio (MAE) es: ", mae, "\n")
El Error Absoluto Medio (MAE) es: 77.3163
cat("El Coeficiente de determinación (R^2) es: ", r_squared)
El Coeficiente de determinación (R^2) es: 0.7882089
Se evaluará la capacidad de generalización del modelo mediante la validación cruzada simple (K-Fold = 10)
# Definir el control de entrenamiento usando validación cruzada
control <- trainControl(method = "cv", number = 10) # 10-fold cross-validation
# Definir la fórmula del modelo
formula <- preciom ~ areaconst + E4 + E5 + E6 + parqueaderos + banios
# Entrenar el modelo usando validación cruzada
modelo_cv <- train(formula, data = df_aptos_clean, method = "lm", trControl = control)
# Revisar los resultados
print(modelo_cv)
Linear Regression
5100 samples
6 predictor
No pre-processing
Resampling: Cross-Validated (10 fold)
Summary of sample sizes: 4590, 4589, 4590, 4589, 4591, 4591, ...
Resampling results:
RMSE Rsquared MAE
128.356 0.8039358 76.41331
Tuning parameter 'intercept' was held constant at a value of TRUE
Conclusiones
R² -> La validación cruzada arroja un R² de 0.8039, levemente superior al 0.7882 obtenido con el método 70/30, lo que sugiere que la validación cruzada puede estar proporcionando una evaluación más precisa de la capacidad del modelo para explicar la variabilidad en los datos.
RMSE y MAE -> Los valores de RMSE y MAE son muy similares entre ambos métodos, con la validación cruzada mostrando valores ligeramente menores. Esto indica que el modelo mantiene una precisión consistente en ambas evaluaciones, con un posible ajuste general mejor en la validación cruzada.
MSE vs. RMSE -> El MSE de 17095.25 del método 70/30 se traduce en un RMSE(raiz cuadrada de MSE) aproximado de 130.76, lo que es comparable al RMSE de la validación cruzada. Esto sugiere que ambos métodos están evaluando el modelo de manera consistente.
Robustez del Modelo -> La validación cruzada, al utilizar todas las muestras para entrenar y validar, es generalmente más robusta, reduciendo las posibilidades de sobreajuste o subajuste que pueden ser más comunes en una simple división de entrenamiento/prueba como la de 70/30.
Error Cuadrático Medio (MSE):
Error Absoluto Medio (MAE):
Coeficiente de Determinación (R²):
Precisión del Modelo:
Incluir Más Variables
Transformaciones y Ajustes
Validación de Supuestos: