Enunciado

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.

Retos:

  1. 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.
  2. 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.
  3. 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).
  4. 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).
  5. 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.
  6. Realice predicciones con el modelo anterior usando los datos de prueba (30%).
  7. Calcule el error cuadrático medio, el error absoluto medio y el R2, interprete.

Punto 1

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.

1.1 Entendimiento datos

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>
  • Los valores para el atributo Tipo están normalizados.
  • Existen 3 valores NA para el atributo tipo, se pueden ignorar para hacer el filtro por apartamento.
  • Se debe incluir en el filtro los NA, ya que R toma los valores NA como TRUE y los mantiene en el dataset

1.2 Filtrar por Apartamento

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 el atributo piso hay 1381 datos faltantes
  • En el atributo parqueaderos hay 869 datos faltantes

1.3 Transformación

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.

1.3.1 Análisis de la variable piso

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
  • Se puede apreciar cierta multimodalidad en los estratos 4, 5 y 6 por lo que la moda puede no ser tan representativa, además, considerando que también se aprecia una asimetría a la derecha, se opta por usar la mediana como valor para la imputación de los datos de piso por estrato.
  • Se hará una excepción con el estrato 3, donde si es claro que la moda captura la tendencia más común en este estrato, se imputará con la moda en el estrato 3.

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

1.3.2 Análisis Parqueaderos

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
  • Se usará la mediana como valor para la imputación de datos para parqueaderos por estrato.
  • Si bien la ausencia del valor 0 para parqueadero puede reflejar que no existen parqueaderos disponibles para la vivienda, no se realizará esta imputación dado el sesgo que puede generar en ciertos estratos, por ejemplo 3 y 4 que tienen una cantidad significativa de datos faltantes.

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.

1.4 Tablas para Identificar algunos datos

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  
  • Aquí se pueden observar las medidas de centralidad de las variables, además de las medidas de dispersión.

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:

  • 3 variables categóricas -> zona, tipo y barrio
  • 10 variables numéricas -> id, piso, estrato, preciom, areaconst, parqueaderos, banios, habitaciones, longitud y latitud

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

  • La variable barrio y la variable id serán retiradas del dataset para 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…

Punto 2

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.

2.1 EDA - Ubicación de los Apartamentos

  • La distribución de apartamentos en Cali por zonas se presenta en el siguiente mapa. La oferta se distribuye de la siguiente manera, de mayor a menor: sur, norte, oeste, oriente y centro.
  • También se observan algunos puntos mal ubicados en las zonas norte y sur. Esto podría ser debido a una clasificación incorrecta de los inmuebles al publicarlos en el portal. En otros casos, puede deberse a razones comerciales, como la inclusión de ubicaciones de oficinas de inmobiliarias en lugar de la ubicación real del inmueble.
# 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

2.2 Relación de variables con respecto al precio

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
  • Esta gráfica de Violín muestra además del diagrama de cajas, la distribución del precio por estrato. Se puede ver que en las zonas oeste, norte y sur se encuentran los inmuebles con mayor precio y además con un número significativo de outliers, mientras que en la zona centro y zona oriente se ven los precios más estables con menor número de outliers.
  • Particularmente la zona Oeste presenta una distribución platicurtica con un sesgo a la derecha y un número significativo de outliers. Mientras que las zonas Centro y Oriente presentan una distribución más parecida a una leptocúrtica y menos valores atípicos.
# 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
  • En esta gráfica se puede apreciar el precio promedio por zona, donde se ve una semejanza en el precio promedio de la oferta de vivienda en la zona sur y norte.
  • De igual manera ocurre con las zonas centro y oriente.

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
  • Con respecto a la relación de precio y estrato solo el estrato 6 considera como datos válidos viviendas alrededor de los 1000 millones mientras que para los demás estratos esta oferta por encima de los 1000 millones es considerado como un dato atípico, a pesar que es significativo el número de datos atípicos en dichos estratos (3,4,5).

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
  • Alta concentracion de ofertas de apartamentos por debajo de los 200m cuadrados, sin embargo, entre los 200 y 300 metros tambien se aprecia una oferta significativa aunque con menor concentración.

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
  • En esta gráfica se puede apreciar que el precio aumenta a medida que aumenta el número de baños, siendo de 2 a 5 los valores más comunes.

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
  • Hay una mayor concentración de oferta de apartamentos con 2 y 3 habitaciones en todos los estratos.
  • La oferta de apartamentos con 1 habitación supera en promedio el precio del apartamento con 2 habitaciones y en ocasiones con 3 habitaciones, esto sugiere una oferta interesante de apartaestudios con alto precio, ya que están concentradas en estratos 5 y 6.

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
  • La mayoría de los inmuebles cuentan con 1 o 2 parqueaderos y con precios por debajo de 500M y 1000M respectivamente.
  • Apartamentos con 3 parqueaderos o más corresponde a apartamentos con precios superiores a 1000M
  • Se aprecian algunos outliers con 6 o más parqueaderos.

2.3 Matriz de correlación

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)

  • Hay una fuerte correlación positiva entre el precio y el área de los apartamentos 0.83 indicando que a medida que aumenta el área el precio también tiende a aumentar.
  • Hay una correlación moderada positiva entre el precio y el número de baños 0.74, sugiere que Apartamentos con más baños tienden a tener precios más elevados.
  • El número de habitaciones tiene una correlación baja con el precio, lo que sugiere menos influencia en el precio.
  • Hay una correlación moderada positiva entre el precio y el número de parqueaderos 0.75, propiedades con más parqueaderos tienden a ser más caras.
  • El piso no tiene influencia signficativa sobre el precio.
  • La correlación entre el estrato y el precio se considera moderada positiva 0.67 sugiere que estratos más altos tienden a tener precios más altos.

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 

Punto 3

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)

3.1 Codificación de variables categóricas

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>
  • Eliminar la variable tipo
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>
  • Codificación zona

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

3.2 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

3.2.1 Observaciones del entrenamiento

  • En cuanto a los residuales se puede ver que la mediana está cerca a cero, lo que sugiere que el modelo hace una buena predicción, sin embargo, la diferencia entre max y min sugiere que existen algunos puntos donde el modelo no ajusta bien, esto puede ser debido a la cantidad importante de outliers.
  • Con respecto a los coeficientes se revisará la variación de cada predictora considerando que las demás se mantienen constantes, se tienen los siguientes resultados:
    • Por cada aumento de 1 unidad en el area, el precio se incrementa en 1.88
    • Considerando el estrato 3 como referencia, pasar al estrato 4 implica un aumento en el precio de aprox. 33.22883 unidades
    • Si el cambio es al estrato 5, el precio aumenta 55.14327 unidades
    • Si el cambio es al estrato 6, el precio aumenta 195.44643 unidades
    • Cada parqueadero adicional está asociado con un aumento de 87.1157 unidades en el precio
    • Cada baño adicional aumenta el precio en aprox. 34.83798 unidades
    • Los p-values bajos, menores que 0.001, indican que todas las variables predictoras son importantes para predecir el precio de los apartamentos, y que podemos confiar en que cada una de las variables tiene una relación real y significativa con el precio.
  • Con respecto al Error residual estándar, se tiene que para las predicciones del modelo existe un error promedio de 128.6 unidades en comparación con los valores reales. Este valor puede parecer alto dado que está en millones, pero considerando el rango de precios en el dataset y los outliers, podría ser aceptable.
  • El \(R^2\) múltiple es 0.8026 lo que significa que aproximadamente el 80.26% de la variabilidad en el precio puede explicarse por las variables predictoras del modelo.
  • El \(R^2\) ajustado es 80.24 practicamente igual a \(R^2\), esto significa que la mayoría de las variables en el modelo son relevantes y están contribuyendo a explicar la variabilidad de la variable dependiente. En otras palabras, el modelo no tiene variables innecesarias.
  • Estadístico F y p-value
    • El valor de f es muy alto 3452 lo que indica que al menos una de las variables está relacionada con el precio
    • El p-value es muy bajo indicando que se rechaza la hipótesis nula y que el modelo es estadísticamente significativo.

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.

Punto 4

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).

4.1 Validación de supuestos

4.1.1 Supuestos de Linealidad y Homocedasticidad

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)

  • Se puede observar que los residuos se distribuyen alrededor del cero, lo cual indica que la distribucion parece no seguir una distribución normal, podríamos decir que existe un patrón de heterocedasticidad (variabilidad no constante) dado que a medida que se avanza a lo largo de la línea los datos se vuelven más dispersos lo que puede indicar que la varianza de los errores no es constante.
  • Aunque el \(R^2\) es alto, esto no necesariamente significa que el modelo esté capturando correctamente todos los patrones en los datos, especialmente en presencia de outliers, pueden existir potencialmente relaciones no lineales que no están siendo capturadas por el modelo.
  • Sin embargo no se observa un patrón específico y dada la cantidad de outliers, podría considerarse que no se viola completamente el supuesto de linealidad.

Prueba de Breush-Pagan

  • De las gráficas se sugiere que hay presencia de heterocedasticidad, con el fin de confirmar esta hipótesis se aplicará la prueba de Breush-Pagan.
bptest(model_aptos)

    studentized Breusch-Pagan test

data:  model_aptos
BP = 1456.9, df = 6, p-value < 2.2e-16
  • Se confirma la heterocedasticidad, dado que la Estadística BP es alta 1456.9
  • Además el p-value bajo rechaza la hipótesis nula, que en este caso es que la varianza de los errores es constante (homocedasticidad).

Sugerencias

  • Es útil realizar pruebas adicionales o considerar transformaciones en las variables para verificar y mejorar la linealidad del modelo.

4.1.2 Supuesto de independencia de los errores

  • 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

    • Prueba Durbin-Watson -> Proporciona información sobre la autocorrelación de los residuos en el modelo de regresió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.

    • Un valor cercano a 2 sugiere que no hay autocorrelación en los residuos.
    • Un valor cercano a 0 indica autocorrelación positiva.
    • Un valor cercano a 4 indica autocorrelación negativa.
  • 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

  • Autocorrelación Positiva: El valor de la estadística Durbin-Watson y el p-value bajo sugieren que hay autocorrelación positiva en los residuos del modelo. Esto implica que los residuos no son independientes y podrían estar correlacionados entre sí.

Sugerencias

  • La autocorrelación puede indicar que el modelo no está capturando adecuadamente algunas relaciones en los datos. - Considera revisar si hay variables importantes que faltan o si el modelo puede ser mejorado.
    • Transformaciones o Ajustes de variables para reducir correlaciones.

4.1.3 Supuesto de Normalidad de los Errores

  • Supuesto -> Los errores deben seguir una distribución normal. Esto es importante para hacer inferencias estadísticas válidas.

  • Validación

    • QQ Plot -> Gráfico que compara la distribución de los residuos con una distribución normal.
    • Shapiro-Wilk Test -> Test estadístico que verifica si los residuos siguen una distribución normal.

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
  • El estadístico W mide la similitud entre la distribución de los residuos y una distribución normal. Un valor de W cercano a 1 indica que los residuos están cerca de una distribución normal.
  • El test sugiere que los residuos no siguen una distribución normal. La violación del supuesto de normalidad no necesariamente invalida el modelo de regresión, pero puede afectar la validez de los intervalos de confianza y las pruebas de hipótesis para los coeficientes del modelo.

Punto 5

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

5.1 Entrenamiento modelo 70/30

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

Punto 6

Realice predicciones con el modelo anterior usando los datos de prueba (30%)

6.1 Predicciones del modelo

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")

  • Se puede ver que para Apartamentos por debajo de los 500M el modelo logra hacer una estimación aceptable, sin embargo, para propiedades por encima de este valor, no es capaz de hacer tan buenas estimaciones y es evidente el impacto de los outliers sobre los 1000M donde el modelo tiende a subestimar el precio.

Punto 7

Calcule el error cuadrático medio, el error absoluto medio y el R2, interprete.

7.1 Cálculo de Errores

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

7.1.1 Validación cruzada

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

  • -> 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.

7.2 Interpretación de errores del modelo 70/30

  • Error Cuadrático Medio (MSE):

    • Valor -> 17095.25
    • Conclusión -> El MSE alto indica que hay una variabilidad significativa en los errores de predicción, sugiriendo que el modelo podría tener dificultades para predecir con precisión en ciertos casos.
  • Error Absoluto Medio (MAE):

    • Valor -> 77.3163
    • Conclusión -> Un MAE de 77.32 unidades monetarias (Millones) sugiere que, en promedio, el modelo se desvía del precio real en esa cantidad. Este error puede ser significativo dependiendo del contexto del mercado inmobiliario.
  • Coeficiente de Determinación (R²):

    • Valor -> 0.7882
    • Conclusión -> El R² de 78.82% indica que el modelo explica una buena parte de la variabilidad en los precios. Sin embargo, también sugiere que el modelo no captura toda la variabilidad, dejando espacio para otros factores no incluidos.

Precisión del Modelo:

  • Conclusión: A pesar de un alto R², el elevado MSE y MAE indican que el modelo puede tener errores significativos en algunas predicciones, lo que podría ser problemático si el rango de precios es limitado.

7.2.1 Posibles Mejoras

Incluir Más Variables

  • Recomendación -> Considerar variables adicionales que podrían afectar el precio, como la antigüedad del inmueble, características del vecindario o proximidad a servicios. Esto puede ayudar a capturar mejor la variabilidad en los precios.

Transformaciones y Ajustes

  • Recomendación -> Evaluar transformaciones en las variables independientes (como logarítmicas o polinómicas) para mejorar el ajuste del modelo. Esto puede ayudar a manejar no linealidades y mejorar la precisión de las predicciones.

Validación de Supuestos:

  • Recomendación -> Realizar una validación exhaustiva de los supuestos del modelo, incluyendo la normalidad de los residuos y la homocedasticidad. Asegurarse de que los supuestos del modelo lineal se cumplen puede validar la adecuación del modelo y mejorar su confiabilidad.