Julian Andres Ospina Cuesta

Maestría en Ciencia de Datos - Universidad Javeriana Cali
Cali, Colombia
Código: 8984933 +57 3043912471
Autorizo que el documento sea publicado con el resto de mis compañeros de la Universidad Javeriana
  • Cargar librerias requeridas
#install.packages("readxl")
#devtools::install_github("dgonxalex80/paqueteMETODOS")
#library(paqueteMETODOS)
library(readxl)
library(knitr)
library(ggplot2)
library(gridExtra)
library(lmtest)
library(GGally)
library(dplyr)
library(mice)
library(psych)
library(wordcloud)
library(factoextra)
library(fpc)
library(FactoMineR)
library(ca)
library(cluster)
library(dbscan)
library(mclust)
library(gplots)
  • Cargar dataset
ruta_del_archivo <- "/Users/Julian/Documents/Material Estudio/MDS Javeriana/Sem 2/[006]EstadTomaDeci/vivienda.xlsx"
data <- read_excel(ruta_del_archivo)

1.Introducción

El presente documento constituye un análisis de las ofertas dinmobiliarias urbanas. El objetivo fundamental es 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.

Se nos pide, un analisis integral y multidimensional empleando entre diversas tecnicsas, las siguientes:

  • Análisis de Componentes Principales

  • Análisis de Conglomerados

  • Análisis de Correspondencia: Examinando la relación entre las variables categóricas (tipo de vivienda, zona y barrio)

2. Entendimiento de los datos

A continuación veremos los primeros registros de nuestra base de datos

kable(do.call(data.frame, head(data)),
      format = "markdown",  
      caption = "**Tabla de los primeros registros base vivienda**",
      align = "c", escape = FALSE,
      row.names = FALSE,
      booktabs = TRUE)
Tabla de los primeros registros base vivienda
id zona piso estrato preciom areaconst parqueaderos banios habitaciones tipo barrio longitud latitud
1147 Zona Oriente NA 3 250 70 1 3 6 Casa 20 de julio -76.51168 3.43382
1169 Zona Oriente NA 3 320 120 1 2 3 Casa 20 de julio -76.51237 3.43369
1350 Zona Oriente NA 3 350 220 2 2 4 Casa 20 de julio -76.51537 3.43566
5992 Zona Sur 02 4 400 280 3 5 3 Casa 3 de julio -76.54000 3.43500
1212 Zona Norte 01 5 260 90 1 2 3 Apartamento acopi -76.51350 3.45891
1724 Zona Norte 01 5 240 87 1 3 3 Apartamento acopi -76.51700 3.36971

Observamos que tenemos 12 campos que constituyen los campos de la base, alguno de ellos categoricos y otros númericos.

2.1 El estado de los campos

Antes de dar inicio a los puntos que nos facilitaron para el analisis, veamos como vienen constituidos cada uno de los campos de nuestra base de datos:

  • Diagnostico inicial
resumen_data <- list(
  num_filas = nrow(data),
  num_col= ncol(data),
  vacios_totales = sum(is.na(data)),
  porc_vacios = sprintf('%.2f%%',sum(is.na(data))/(nrow(data)*ncol(data))*100),
  filas_con_mas_un_vacio = sum(apply(is.na(data), 1, any)),
  filas_all_vacios = sum(apply(is.na(data), 1, all)),
  col_all_vacios = sum(colSums(is.na(data)) == nrow(data)),
  filas_duplicadas= nrow(data[duplicated(data), ])
)

kable(do.call(data.frame, resumen_data),
      format = "markdown", 
      caption = "**Tabla n°1 Resumen de dataset antes de tratamientos**",
      align = "c", escape = FALSE,
      row.names = FALSE,
      booktabs = TRUE)
Tabla n°1 Resumen de dataset antes de tratamientos
num_filas num_col vacios_totales porc_vacios filas_con_mas_un_vacio filas_all_vacios col_all_vacios filas_duplicadas
8322 13 4275 3.95% 3514 2 0 1

Se ha identificado la existencia de dos filas vacías y nueve registros duplicados en el conjunto de datos. Estos elementos, por varias razones, no parecen ser esenciales para los objetivos de este estudio:

  1. En relación con el total de 8,322 observaciones, la presencia de estos 1 registros duplicados parece no ser representativa ni influyente.
  2. Se tiene 2 registros con todas las filas vacias.
  3. Tenemos un dataset con 3.95% de datos faltantes distribuidos entre 4275 registros y con 3514 registros con mas de 1 faltante.

Concluyendo que debemos hacer algun tipo de tratamiento de los faltantes antes de iniciar el modelado

2.2 Tipo de datos de los campos

col_originales = names(data)
str(data)
## tibble [8,322 × 13] (S3: tbl_df/tbl/data.frame)
##  $ id          : num [1:8322] 1147 1169 1350 5992 1212 ...
##  $ zona        : chr [1:8322] "Zona Oriente" "Zona Oriente" "Zona Oriente" "Zona Sur" ...
##  $ piso        : chr [1:8322] NA NA NA "02" ...
##  $ estrato     : num [1:8322] 3 3 3 4 5 5 4 5 5 5 ...
##  $ preciom     : num [1:8322] 250 320 350 400 260 240 220 310 320 780 ...
##  $ areaconst   : num [1:8322] 70 120 220 280 90 87 52 137 150 380 ...
##  $ parqueaderos: num [1:8322] 1 1 2 3 1 1 2 2 2 2 ...
##  $ banios      : num [1:8322] 3 2 2 5 2 3 2 3 4 3 ...
##  $ habitaciones: num [1:8322] 6 3 4 3 3 3 3 4 6 3 ...
##  $ tipo        : chr [1:8322] "Casa" "Casa" "Casa" "Casa" ...
##  $ barrio      : chr [1:8322] "20 de julio" "20 de julio" "20 de julio" "3 de julio" ...
##  $ longitud    : num [1:8322] -76.5 -76.5 -76.5 -76.5 -76.5 ...
##  $ latitud     : num [1:8322] 3.43 3.43 3.44 3.44 3.46 ...

Se observa 4 campos categoricos y resto de campos numericos. Dentro los campos categoricos observamos que:

  • campo piso se considera un campo categorico ordinal, dado que existe una jerarquia entre pisos.

  • campo ‘Tipo’ se asemeja a un campo binomial

2.3 Tipo de datos de los campos Tratamiento de registros vacios y duplicados

  • Se procede a revisar la distribución de los campos faltantes en nuestro dataset
grafico_vacios <-md.pattern(data, rotate.names = TRUE)

Se observa que los siguientes registros que deseamos eliminar:

  • 1 registro duplicado
  • 2 registro que tienen vacios todas los campos
  • 1 registro que solo tiene el precio (puede ser un total )

Nota: Tanto ‘parqueadores’ como ‘piso’ son campos que no se sabria como inputar(completar). Parcialmente el piso en una casa podria ser valor 1, pero existen cassa de mas de 1 piso, y podria ser referencia a dicho atributo. Se decide remover los campos del analisis, dado que su proporción es mas del 30% de faltantes en estos dos campos. Nota 2 El campo ‘id’ se trata de un número concecutivo que no aporta valor a nuestro analisis. Nota 3 Los campos ‘latitud’ y ‘longitud’ al ser caracteristicas espaciales, puede ser variables de dificil interpretación, se puede considerar mas adelante si se desean incluir en el analisis.

2.4 removiendo campos faltante no representativos

#REMOVIENDO FILAS FALTANTES
#Se remueve los registros llenos de campos sin datos
data <- data[rowSums(is.na(data)) < ncol(data), ]
#Se remueve los duplicados
data <- data[!duplicated(data), ]
#Se remueve el campo que solo tiene el precio (se saca por id, es mas facil de identificar)
data <- data[!is.na(data$id), ]

#REMOVIENDO COLUMNAS
data <- select(data, -id, -parqueaderos,-piso)

resumen_data <- list(
 num_filas = nrow(data),
 num_col= ncol(data),
 vacios_totales = sum(is.na(data)), #sum(is.na(data), na.rm = TRUE),
 porc_vacios = sprintf("%.2f%%",sum(is.na(data))/(nrow(data)*ncol(data))*100),
 filas_con_mas_un_vacio = sum(apply(is.na(data), 1, any)),
 filas_all_vacios = sum(apply(is.na(data), 1, all)),
 col_all_vacios = sum(colSums(is.na(data)) == nrow(data)),
 filas_duplicadas= nrow(data[duplicated(data), ])
)


kable(do.call(data.frame, resumen_data),
      format = "markdown",  
      caption = "**Tabla n°2 Resumen de dataset con tratamientos y registros mayormente completados**",
      align = "c", escape = FALSE,
      row.names = FALSE,
      booktabs = TRUE)
Tabla n°2 Resumen de dataset con tratamientos y registros mayormente completados
num_filas num_col vacios_totales porc_vacios filas_con_mas_un_vacio filas_all_vacios col_all_vacios filas_duplicadas
8319 10 0 0.00% 0 0 0 87

Se obtiene la Tabla n°2 Resumen de dataset con tratamientos donde se aprecia el total de registros con el que se procederan a realizar las siguientes fases.

2.5 Analisis por tipo de variable

Se inicia con una clasificación de cada uno de los campos entre cuantitativos y cualitativos

  • Separación de campos entre tipos de variables
resumen_df <- as.data.frame.matrix(summary(data))
resumen_df <- t(resumen_df)

types <- sapply(data, class)
resumen_cuantitativa <- resumen_df[types %in% c("numeric", "integer"), ]
resumen_cuantitativa <- rownames(resumen_cuantitativa)
resumen_cuantitativa <- gsub(" ", "", resumen_cuantitativa) # para quitar los espacios en blanco

resumen_cualitativa <- resumen_df[!(types %in% c("numeric", "integer")), ]
resumen_cualitativa <- rownames(resumen_cualitativa)
resumen_cualitativa <- gsub(" ", "", resumen_cualitativa) # para quitar los espacios en blanco

Variables cuantitativas

Algunas variables directamente no se ven representantes para nuestro estudio, como es el caso de: ‘id’

estrato
col <- 'estrato'
frecuencias <- table(data$estrato)
porcentajes <- round(frecuencias / sum(frecuencias) * 100, 2)
valores_ordenados <- factor(names(sort(frecuencias, decreasing = TRUE)), levels = names(frecuencias))

ggplot(data = data.frame(valores = valores_ordenados, frecuencias), aes(x = valores_ordenados, y = frecuencias)) +
  geom_bar(stat = "identity", fill = "#0576FF") +
  geom_text(aes(label = paste(porcentajes, "%")), vjust = -0.5) +
  labs(title = paste("Grafico de Barras",col), x = paste("Valores de ",col), y = "Frecuencia") +
  theme_minimal()+
  theme(panel.grid.major = element_blank(), panel.grid.minor = element_blank())
## Don't know how to automatically pick scale for object of type <table>.
## Defaulting to continuous.

describe(data$estrato)
##    vars    n mean   sd median trimmed  mad min max range  skew kurtosis   se
## X1    1 8319 4.63 1.03      5    4.67 1.48   3   6     3 -0.18    -1.11 0.01

Vemos que no estan estrato 1 y 2,(nicho de mercado nuestro son cliente con poder adquisitivo medio alto). El gráfico indica que los registros estan algo sesgados a la derecha, aunque no esta muy concentrado y con una media en alrededor del estrato 4.63 y desviacion estandar de 1.03.

preciom
col<-'preciom'
ggplot(data = data, aes(x = !!as.name(col))) +
  geom_histogram(bins = 30, color = "#515354", fill = "#0576FF") +
  geom_vline(aes(xintercept = mean(!!as.name(col))), color = "#FF0576", linetype = "dashed", size = 1) +
  geom_vline(aes(xintercept = median(!!as.name(col))), color = "#50AC05", linetype = "dashed", size = 1) +
  theme_bw() +
  labs(title = paste('Histograma de', col), x = col, y = "Frecuencia")+
  theme_minimal()+
  theme(panel.grid.major = element_blank(), panel.grid.minor = element_blank())

describe(data$preciom)
##    vars    n  mean     sd median trimmed    mad min  max range skew kurtosis
## X1    1 8319 433.9 328.67    330  374.48 207.56  58 1999  1941 1.85     3.67
##     se
## X1 3.6

El gráfico indica que los registros estan sesgados a la izquierda, con una media en 433.9 millones y desviación de 328.67 millones muy alta para ser el campo mas representativo para el estudio. Vemos tambien, una kurtosis de 3.67 muy por arriba del 0; indicandonos que tenemos un precio muy concentrado y punteagudo en el grafico.

areaconst
col<-'areaconst'
ggplot(data = data, aes(x = !!as.name(col))) +
  geom_histogram(bins = 30, color = "#515354", fill = "#0576FF") +
  geom_vline(aes(xintercept = mean(!!as.name(col))), color = "#FF0576", linetype = "dashed", size = 1) +
  geom_vline(aes(xintercept = median(!!as.name(col))), color = "#50AC05", linetype = "dashed", size = 1) +
  theme_bw() +
  labs(title = paste('Histograma de', col), x = col, y = "Frecuencia")+
  theme_minimal()+
  theme(panel.grid.major = element_blank(), panel.grid.minor = element_blank())

describe(data$areaconst)
##    vars    n   mean     sd median trimmed   mad min  max range skew kurtosis
## X1    1 8319 174.93 142.96    123  149.15 84.51  30 1745  1715 2.69    12.91
##      se
## X1 1.57

El gráfico ind ica que los registros estan sesgados a la izquierda, con una media de 174.93 y desviación de 142.96 muy alto, casi igual a la media, su kurtosis es muy alta 12.91 dando una distribución muy punteaguda.

habitaciones
col <- 'habitaciones'
frecuencias <- table(data$habitaciones)
porcentajes <- round(frecuencias / sum(frecuencias) * 100, 2)
valores_ordenados <- factor(names(sort(frecuencias, decreasing = TRUE)), levels = names(frecuencias))

ggplot(data = data.frame(valores = valores_ordenados, frecuencias), aes(x = valores_ordenados, y = frecuencias)) +
  geom_bar(stat = "identity", fill = "#0576FF") +
  geom_text(aes(label = paste(porcentajes, "%")), vjust = -0.5) +
  labs(title = paste("Grafico de Barras",col), x = paste("Valores de ",col), y = "Frecuencia") +
  theme_minimal()+
  theme(panel.grid.major = element_blank(), panel.grid.minor = element_blank())
## Don't know how to automatically pick scale for object of type <table>.
## Defaulting to continuous.

describe(data$habitaciones)
##    vars    n mean   sd median trimmed  mad min max range skew kurtosis   se
## X1    1 8319 3.61 1.46      3    3.41 1.48   0  10    10 1.63     3.98 0.02

El gráfico revela que los registros muestran un sesgo hacia la derecha. media en 3.61 con desviacion de 1.46 muy alta para ser habitaciones. NOTA vemos que aparecen casas con 0 habitaciones de 1.66% , pueden tratarse de error o de apartaestudios con un solo ambiente. no obstante es un valor poco representativo dentro del estudio, y creemos que no se debe tener encuenta.

banios
col <- 'banios'
frecuencias <- table(data$banios)
porcentajes <- round(frecuencias / sum(frecuencias) * 100, 2)
valores_ordenados <- factor(names(sort(frecuencias, decreasing = TRUE)), levels = names(frecuencias))

ggplot(data = data.frame(valores = valores_ordenados, frecuencias), aes(x = valores_ordenados, y = frecuencias)) +
  geom_bar(stat = "identity", fill = "#0576FF") +
  geom_text(aes(label = paste(porcentajes, "%")), vjust = -0.5) +
  labs(title = paste("Grafico de Barras",col), x = paste("Valores de ",col), y = "Frecuencia") +
  theme_minimal()+
  theme(panel.grid.major = element_blank(), panel.grid.minor = element_blank())
## Don't know how to automatically pick scale for object of type <table>.
## Defaulting to continuous.

describe(data$banios)
##    vars    n mean   sd median trimmed  mad min max range skew kurtosis   se
## X1    1 8319 3.11 1.43      3    2.99 1.48   0  10    10 0.93     1.13 0.02

El gráfico no muestra un comportamiento de los datos, de hecho llama la atención su skew de 0.93, muy cercana al cero. no obstante la media en 3.11 con su desviacion de 1.41 todavia algo a revisar. NOTA vemos que aparecen casas con 0 baños 0.85% , pueden tratarse de error o de apartaestudios con baños comunitarios algo improbable para la ciudad de Cali no obstante es un valor poco representativo dentro del estudio y creemos que no se debe tener encuenta.

longitud y latitud
ggplot(data=data) +
  geom_point(aes(x=longitud,y=latitud, col=tipo),
             col='#50AC05',
             size=3, alpha = 1/5    )+
  theme_minimal()+
  theme(panel.grid.major = element_blank(), panel.grid.minor = element_blank())

El gráfico representa la ciudad de Cali; sin embargo, seria bueno graficarlo separando por zonas, para identificar si el dato es confiable

ggplot(data=data) +
  geom_point(aes(x=longitud,y=latitud, col=zona),
             size=3, alpha = 1/5    )+
  theme_minimal()+
  theme(panel.grid.major = element_blank(), panel.grid.minor = element_blank())

Vemos que si se define en gran manera las zonas, pero muchos puntos fuera de las zonas en donde se deberian demarcar, lo que nos hace un dato que no es 100% confiable. Y al ser una variable que puede ser de dificil interpretación, se opta por la decisión de removerlo del dataset a analizar.

data<- select(data, -longitud, -latitud)

Variables cualitativas

lista_graficos_cuali <- list()
for (col in resumen_cualitativa) {
 frecuencia_palabras_temp = aggregate(data[[col]], list(data[[col]]), FUN=length)
 colnames(frecuencia_palabras_temp)=c("Palabra","Frecuencia")
 lista_graficos_cuali[[col]] <- as.data.frame(frecuencia_palabras_temp)
}

tipo
frecuencia_palabras_temp = lista_graficos_cuali[['tipo']]
wordcloud(words = frecuencia_palabras_temp$Palabra,
                     freq = frecuencia_palabras_temp$Frecuencia,
                     min.freq = 2,
                     max.words = 60,
                     colors =  c("#515354","#50AC05","#0576FF", "#FF0576"),
                     random.order = F,
                     random.color = F,
                     scale = c(4 ,0.5),
                     rot.per = 0.25)

???

zona
frecuencia_palabras_temp = lista_graficos_cuali[['zona']]
wordcloud(words = frecuencia_palabras_temp$Palabra,
                     freq = frecuencia_palabras_temp$Frecuencia,
                     min.freq = 2,
                     max.words = 60,
                     colors =  c("#515354","#50AC05","#0576FF", "#FF0576"),
                     random.order = F,
                     random.color = F,
                     scale = c(4 ,0.5),
                     rot.per = 0.25)

Es evidente la distinción de cinco zonas, siendo la zona Sur la más destacada, seguida por las zonas Norte y Oeste en términos de participación de registros.

barrio
frecuencia_palabras_temp = lista_graficos_cuali[['barrio']]
wordcloud(words = frecuencia_palabras_temp$Palabra,
                     freq = frecuencia_palabras_temp$Frecuencia,
                     min.freq = 2,
                     max.words = 60,
                     colors =  c("#515354","#50AC05","#0576FF", "#FF0576"),
                     random.order = F,
                     random.color = F,
                     scale = c(4 ,0.5),
                     rot.per = 0.25)

Se puede observar que hay barrios con una falta de estandarización en los datos. Ejemplos de esto incluyen el uso inconsistente de mayúsculas, abreviaciones, puntuaciones y símbolos, lo que indica problemas en el manejo de la normalización de la información.

Limpieza de algunos campos

-Se estandariza el campo barrio

reemplazar_caracteres <- function(texto) {
  #Función empleada para hacer limpieza de los caracteres encontrados en la base de datos
  #**NOTA**: No se trata de una función genérica, sino de una función personalizada 
  #construida específicamente para estos datos.
  #
  #@texto(string): el texto que vamos a procesar
  #@return(string): el texto previamente tratado
  
  caractares_remplazar <- c('Á','É','Í','Ó','Ú','√©','√∫')
  caracteres_remplazado <- c('A','E','I','O','U','E','U')
  texto <- toupper(texto)
  for (i in 1:length(caractares_remplazar)){
    texto <- gsub(caractares_remplazar[i],caracteres_remplazado[i],texto)
  }
  return (texto)
}

data$barrio <- sapply(data$barrio, reemplazar_caracteres)

2.6 Correlacion entre datos

2.6.1 Adaptación de algunos campos categoricos ordinales o númericos

Se desea realizar un analisis exploratorio con tecnicas de aproximación que piden tener valores númericos, es por ello que se realizara alguna transformación en algunos de ellos:

crear_variables_dummy <- function(data, columna) {
  dummy_matrix <- model.matrix(~ . - 1, data = data[, columna, drop = FALSE])
  dummy_df <- as.data.frame(dummy_matrix)
  data <- cbind(data, dummy_df)
  data <- data[, -which(names(data) == columna)]
  return(data)
}
data_num <- data

Zona

Los valores de zona al no tener un orden inherente; realizaremos el tratamiento de Codificación Dummy (One-Hot Encoding)

unique (data_num$zona)
## [1] "Zona Oriente" "Zona Sur"     "Zona Norte"   "Zona Oeste"   "Zona Centro"
data_num <- crear_variables_dummy(data_num, "zona")

tipo

Los valores de tipo de vivienda, se identifican que no tienen un orden inherente; se les realizará una Codificación Dummy (One-Hot Encoding)

unique (data_num$tipo)
## [1] "Casa"        "Apartamento"
data_num <- crear_variables_dummy(data_num, "tipo")

2.6.2 Correlación entre campos númericos y transformados

#Recordemos que barrio es un campo categorico 
data_num <- select(data_num, -barrio)
str(data_num)
## 'data.frame':    8319 obs. of  12 variables:
##  $ estrato         : num  3 3 3 4 5 5 4 5 5 5 ...
##  $ preciom         : num  250 320 350 400 260 240 220 310 320 780 ...
##  $ areaconst       : num  70 120 220 280 90 87 52 137 150 380 ...
##  $ banios          : num  3 2 2 5 2 3 2 3 4 3 ...
##  $ habitaciones    : num  6 3 4 3 3 3 3 4 6 3 ...
##  $ zonaZona Centro : num  0 0 0 0 0 0 0 0 0 0 ...
##  $ zonaZona Norte  : num  0 0 0 0 1 1 1 1 1 1 ...
##  $ zonaZona Oeste  : num  0 0 0 0 0 0 0 0 0 0 ...
##  $ zonaZona Oriente: num  1 1 1 0 0 0 0 0 0 0 ...
##  $ zonaZona Sur    : num  0 0 0 1 0 0 0 0 0 0 ...
##  $ tipoApartamento : num  0 0 0 0 1 1 1 1 0 0 ...
##  $ tipoCasa        : num  1 1 1 1 0 0 0 0 1 1 ...
grafico_n1 <- corPlot(cor(data_num), 
   cex = 0.7,
   xsrt = 75,
   main = "Matriz de correlación")

Se concluye los siguientes enunciados con base en al correlación:

  • areaconst y preciom : 0.69 positiva a mayor area construida mayor el precio,

  • baños y precio : 0.67 positiva a mayor cantidad de baños mayor el precio

  • baños y area contruida : 0.65 positiva a mayor cantidad de baños mayor el área construida

  • preciom y estrato : 0.61 positiva a mayor estrato mayor el precio.

  • areacons/habitaciones y tipo y : 0.55 negativa para apartamento y positiva para casa, una relación bastante llamativa que no afirma , pero tamposo resuelve sospechas.

  • habitaciones y baños: 0.59 positiva a mayor cantidad de habitaciones más baños

3. Análisis de Componentes Principales (ACP)

Aplicar el Análisis de Componentes Principales a nuestros datos

# Aplicar el Análisis de Componentes Principales
resultado_acp <- prcomp(data_num, scale = TRUE)

3.1 Elección de número de componentes principales

fviz_eig(resultado_acp,addlabels = TRUE)

En este caso el primer componente principal explica el 27.4% de la variabilidad contenida en la base de datos y entre los tres primeros se casi el 63.6% de los datos, es decir con estos 3 componentes se esta obteniendo una combinación lineal de las variables que puede resumir gran parte de la variabilidad que contiene la base de datos.

fviz_contrib(resultado_acp,axes=1:3,choice="var")

Se identifica los campos mas representativos en estos 3 componentes son: Zona Sur, ambos tipos de casa, latitud, preciom, area construc, baños y el estrato.

3.2 Visualizar las variables en el espacio de las componentes principales

fviz_pca_var(resultado_acp,
  col.var = "contrib",
  gradient.cols = c("#FF7F00",  "#034D94"),
  repel = TRUE)

Partiendo del precio, se observa que el número de baños, áreas de contrucción , habitaciones son directamente proporcionales. y que todos los anteriores son directamente proporcional en una Casa. Caso contrario, es el apartamento, en donde para el precio es casi indeferente, pero para el área de contrucción y habitaciones, son opuestos en dirección.

Llama la atención de estrato. pues se hace una dirección paralela a la casa y al apartamento, y justo son son las de mayor distribución en el reporte.

3.3 Biplot de atributos

fviz_pca_biplot(resultado_acp,
                repel=FALSE,
                col.var='#2e9fdf',
                col.ind='#ffff00')

Al contrastar las observaciones con las direcciones de las variables, da la impresión que las variables de alta contriunción de tipo de casa/apartamento estan generando una división entre los datos y justo el resto de los demas atributos disperzan los datos a lo ancho de los datos. para corroborar esta apreciación, emplearemos un analisis indivisual de PCA pero haciendo la diferenciación entre el tipo de apartamento.

3.4. Individuals PCA

colores <- function(vec){
col <- rainbow(length(unique(vec)))
return(col[as.numeric(as.factor(vec))])
} # Función para el color

plot(resultado_acp$x[,1:2], col = colores(data$tipo), 
     pch = 19, 
     xlab = "Dim1", 
     ylab = "Dim2") 

Se observa una clara distinción de los resultados de pca an contrastarlos frente al tipo de apartamento, este comportamiento se puede corroborar si vemos las cotribuciones de cada variable por las dimensiones de la PCA creadas:

get_pca_var(resultado_acp)$contrib[,1:3]
##                        Dim.1      Dim.2       Dim.3
## estrato           2.11512168 28.7238409  0.20537736
## preciom          12.86122322 13.7419769  0.62062367
## areaconst        19.15372595  0.6275313  0.54577977
## banios           18.01398317  3.1823677  0.01241309
## habitaciones     13.70329281  3.2834131  0.15786622
## zonaZona Centro   0.07404437  2.3040615  0.59612599
## zonaZona Norte    0.59707941  2.9757001 26.75161079
## zonaZona Oeste    0.01104357 16.1897482 11.84361335
## zonaZona Oriente  0.17922098  9.2420309  1.80669159
## zonaZona Sur      0.11862002  0.0486055 57.35347392
## tipoApartamento  16.58632241  9.8403619  0.05321212
## tipoCasa         16.58632241  9.8403619  0.05321212

Se observa que en las principales dimensiones 1 y 2 ambos tipos de casa tiene un valor igual y representativo en los datos ratificando lo que apreciabamos con el grafico individual de PCA con separación de tipo de vivienda

3.5 Ejercicio de PCA por tipo de apartamento

Dado el fuerte componente de tipo de vivienda que tenemos en los datos, vamos a segregar los datos y a realizar un PCA segun el tipo, para tener una comparación mas detallada.

Dado el fuerte componente de tipo de vivienda que tenemos en los datos, vamos a segregar los datos y a realizar un PCA segun el tipo, para tener una comparación mas detallada.

Tipo de vivienda = ‘Apartamento’

data_filtrada_apartamento <- data_num[data_num$tipoApartamento == 1,-which(names(data_num) %in% c("tipoApartamento", "tipoCasa"))]
pca_apartamento<- prcomp(data_filtrada_apartamento, scale = TRUE)
3.5.1.1 Elección de componentes principales - Apartamento
fviz_eig(pca_apartamento,addlabels = TRUE)

Se observa que solo con las dos primeras dimensiones tenemos el 54% de la explicción de la variación

fviz_contrib(pca_apartamento,axes=1:2,choice="var")

Este 54% corresponde a los campos zonaSur, latitud (relacionada a la zona), preciom, area de construcción, banios,zonaNorte y estrato. Una composición algo distinta a la obtenida inicialmente al incluir tipo de apartamento. pero con mayor precensia de la zonaNorte como componente para el tipo de Apartamento.

3.5.1.2 Visualizar las variables- Apartamento
fviz_pca_var(pca_apartamento,
  col.var = "contrib",
  gradient.cols = c("#FF7F00",  "#034D94"),
  repel = TRUE)

El grafico ratifica lo mencionado en el grafico de contribución, donde se destaca el contraste que tenemos por zonaNorte y zonaSur. se identifica que areaconst, baños, precio y estrato esas muy relacionados para el tipo Apartamento.

Tipo de vivienda = ‘Casa’

data_filtrada_casa <- data_num[data_num$tipoCasa == 1,-which(names(data_num) %in% c("tipoApartamento", "tipoCasa"))]
pca_casa<- prcomp(data_filtrada_casa, scale = TRUE)
3.5.2.1 Elección de componentes principales - Casa
fviz_eig(pca_casa,addlabels = TRUE)

Se observa que en casa las primeras 3 dimensiones son el 60% de la explicación de la variación

fviz_contrib(pca_casa,axes=1:3,choice="var")

Este 57% corresponde a los campos zonaSur, zonaNorte, latitud, precio, banios y estrato. Una composición algo distinta a la obtenida inicialmente al incluir tipo de apartamento y al de solo tipo ‘Apartamento’. nuevamente observamos la presencia de la zonaNorte y la salida del área de construccuión dentro de estas dimensiones.

3.5.2.2 Visualizar las variables- Casa
fviz_pca_var(pca_casa,
  col.var = "contrib",
  gradient.cols = c("#FF7F00",  "#034D94"),
  repel = TRUE)

El grafico muestra, el contraste que se da entre zonaSu y Norte en tipo en ‘Casa’ y llama la atención la salida del área de contrucción dentro de elementos mas representativos.

3.6 Conclusiones: Análisis de Componentes Principales (ACP)

El mercado de inmuebles de la ciudad de Cali se caracteriza principalmente por el tipo de vivienda en la cual se desea analizar:

  • Los Apartamento: Se destacan en dos zonas de la ciudad Sur y Norte, y su área de construcción,estrato y baños estan relacionados con el precio de la vivienda.

  • Las casa: Se destacan en dos zonas de la ciudad Sur y Norte e igualmente destacan en estrato, baños relacionados con el precio de la vivienda. se ausenta importancia al área de contrucción y se aporta importancia al estrato.

4 Análisis de Conglomerados (Clustering)

Para alizar este nuevo analisis, requerimos que nuestros datos esten estandarizados

data_z =scale(data_num)
data_z = as.data.frame(data_z)

4.1 Identificando el número de clustering

4.1.1 Método de codo

fviz_nbclust(data_z,kmeans,method ="wss")+
  labs(subtitle="Elbow Method")+geom_vline(xintercept=6)

El metodo de codo, nos recomienda armar con nuestra data de 5 a 6 cluster. recordemos que este metodo es muy visual, y queda muy subjetivo el identificar cual número de cluster es el ideal.

4.1.2 Método de la Silueta

fviz_nbclust(data_z,kmeans,method="silhouette")+
  labs(subtitle = "Silhouette method") 

Empleando el metodo de la silueta, observamos que la sugerencia es 6 cluster. Muy similar a la recomendación que teniamos con el metodo de codo.

4.1.3 Empleando metodo Hierarchical Clustering

Se procede a calcular la distancia de los puntos al centroide de cada cluster, y el metodo elegido es el ‘Euclidiano’

dist_data <- dist(data_z, method = 'euclidean')

hc_data <- hclust(dist_data, method = 'complete') #complete, average

cluster_assigments_hclust <- cutree(hc_data, k = 6)

4.1.3.1 Empleando el eso del arbol HC

plot(hc_data$height[8300:8330],type='b')

El gráfico se dispuso para mostrar los últimos pesos del metodo Hierarchical Clustering, en donde nos dan como conclusión 5 a 6 clustering

4.2 Evaluando varios metodos de cluster

k6 <- kmeans(data_z, centers = 6)
k6$betweenss/k6$totss
## [1] 0.6023816

Validando la propoción sinificativa con 6 cluster, encontramos con un 0.602 . un valor que nos dice que tenemos una buena expicación, pero que tiene mucha posibilidad para mejorar la variabilidad total.

4.2.1 variando el metodo de centroide kmean

Se desea explorar diversas algoritmos de asociar los centroides por kmeans, para ello exploraremos cada uno de ellos y sus resultados

k6 <- kmeans(data_z, centers = 6 , algorithm = "Lloyd")
k6$betweenss/k6$totss
## [1] 0.5414488
k6 <- kmeans(data_z, centers = 6 , algorithm = "Hartigan-Wong")
k6$betweenss/k6$totss
## [1] 0.5361003
k6 <- kmeans(data_z, centers = 6 , algorithm = "Forgy")
k6$betweenss/k6$totss
## [1] 0.5731432
k6 <- kmeans(data_z, centers = 6 , algorithm = "MacQueen")
k6$betweenss/k6$totss
## [1] 0.5653691

Observamos que el metodo de centroide que brinda mayor explicación a la variación, es la brindada por el metodo por defecto.

4.3 Aplicando cluster

Empleamos el número de 6 cluster para armar nuestro analisis y encontramos lo siguiente:

num_conglomerados <- 6
resultado_kmeans <- kmeans(data_z, centers = num_conglomerados)
data_z_v2 <- data.frame(data_z,
                             resultado_kmeans$cluster) 

4.3.1 Caracterización de los cluster

kable(do.call(data.frame,as.data.frame( table(data_z_v2$resultado_kmeans.cluster))),
      format = "markdown",  
      caption = "**Tabla n°? Resumen de dataset con tratamientos y registros mayormente completados**",
      align = "c", escape = FALSE,
      row.names = FALSE,
      booktabs = TRUE)
Tabla n°? Resumen de dataset con tratamientos y registros mayormente completados
Var1 Freq
1 1282
2 2227
3 1093
4 1875
5 595
6 1247

Encontramos 6 cluster con mucha participación en los ultimos 3 cluster , y muy pocos datos en el primero y tercer cluster. A continuación vamos a ver dos formas de como ver la caracterización, uno seria con solo la mediana de los datos

resultado_aggregate <- aggregate(data_num,
          by = list(data_z_v2$resultado_kmeans.cluster),
          FUN = median) # Medianas

kable(do.call(data.frame, as.data.frame(resultado_aggregate)),
      format = "markdown",  
      caption = "**Tabla n°? Resumen de dataset con tratamientos y registros mayormente completados**",
      align = "c", escape = FALSE,
      row.names = FALSE,
      booktabs = TRUE)
Tabla n°? Resumen de dataset con tratamientos y registros mayormente completados
Group.1 estrato preciom areaconst banios habitaciones zonaZona.Centro zonaZona.Norte zonaZona.Oeste zonaZona.Oriente zonaZona.Sur tipoApartamento tipoCasa
1 5 750 350 5 5 0 0 0 0 1 0 1
2 4 225 77 2 3 0 0 0 0 1 1 0
3 6 570 154 3 3 0 0 1 0 0 1 0
4 4 320 176 3 4 0 0 0 0 0 0 1
5 6 570 148 4 3 0 0 0 0 1 1 0
6 4 230 79 2 3 0 1 0 0 0 1 0

El otro metodo es desde rangos (lo aplicaremos solo a campos númericos), pero en estos momentos esta generando problemas con los calculos, no genera las medias simialres al anerior,

#require(table1)
#table1(~estrato+preciom+areaconst+banios+habitaciones|resultado_kmeans$cluster,data=data_z_v2)

Al observar ambas aproximaciones de caracterización, resulta muy interesante:

  • Los 3 cluster iniciales tiene preferencias por casa, donde se diferencia por estrato y precio promedio. donde varian por la zonas.

  • Los 3 cluster finales tiene preferencias por apartamentos, donde el precio promedio, estrato y área de contrucción , no son equivalentes en magnitud,

  • Se puede apreciar que apesar de que el promedio de estrato en un cluster es inferior que otro, puede tener mayor metros cuadrados y menor precio.es decir, se estan traslapando entre rango de diverentes cluster

colores_contrastantes <- c( "#7F8C8D", "#FC4E07","#FF6600","#5E5E5E","#F4D03F", "#BA4A00" )
numero_de_columnas <- ncol(data_z_v2)


fviz_cluster(list(data = data_z_v2[1:(numero_de_columnas-1)], 
                  cluster = data_z_v2$resultado_kmeans.cluster),
             palette = colores_contrastantes,
             ellipse.type = "convex",repel = F, 
             show.clust.cent = FALSE, ggtheme = theme_minimal())

Nota: Los colores calidos se dejaron para agrupación de tipo Apartamento y colores frios para Casa

Partiendo de la comparación de colores calidos y frios que se implemento para los cluster por tipo de vivienda, encontramos que si existe una tendencia a grupar muy similar al grafico de de 3.4 Invididual PCA. en donde los apartamentos tenian una área en la parte superior de la grafica y las casas una sección en la parte inferior.

4.4 Calcular el coeficiente de Silhouette

cluster_assignments <- resultado_kmeans$cluster
sil <- silhouette(cluster_assignments, dist(data_z))
sil_avg <- mean(sil[,3])

cat("Coeficiente de Silhouette promedio KMEAN k=6 : ", sil_avg)
## Coeficiente de Silhouette promedio KMEAN k=6 :  0.3135149
sil <- silhouette(cluster_assigments_hclust, dist(data_z))
sil_avg <- mean(sil[,3])

cat("Coeficiente de Silhouette promedio Hierarchical Clustering  k=5 : ", sil_avg)
## Coeficiente de Silhouette promedio Hierarchical Clustering  k=5 :  0.2661317

5 Análisis de Correspondencia

Realizar analisis de tipo de vivienda, zona y barrio Nota: los valores no requieren ser tratados a númericos Nota 2: recordemos que ya se realizo una limpieza del campo barrio en el paso 2.5 / variable cualitativas /barrio

grupo <- as.factor(data$tipo)

Multiple: tipo de vivienda, zona, barrio

tabla <- data[, c("zona", "tipo", "barrio")]
par(mfrow = c(1, 1), mar = c(4, 4, 2, 1), oma = c(0, 0, 0, 0), mgp = c(2, 1, 0), las = 1)
resultados_mca <- MCA(tabla, graph = FALSE)

plot(resultados_mca, invisible = "ind", cex = 0.5)

plot(resultados_mca, invisible = "var", cex = 0.5)

plot(resultados_mca, invisible = "quanti", cex = 0.5)

fviz_mca_var(resultados_mca, label='none')

Partiendo de los graficos de multiple correspondencia, no se alcanza apreciar una relación muy significativa entre las diversas variables. procedemos a compararlas entre ellas

tipo de vivienda, zona

tabla <- table(data$zona, data$tipo)

balloonplot(t(tabla))

chisq.test(tabla)
## 
##  Pearson's Chi-squared test
## 
## data:  tabla
## X-squared = 690.93, df = 4, p-value < 2.2e-16

Hay evidencia significativa para rechazar la hipótesis nula de que no hay diferencia entre las frecuencias observadas y esperadas en la tabla de contingencia. La relación entre las variables es estadísticamente significativa.

resultados_ac <- CA(tabla)
valores_prop <-resultados_ac$eig
print(valores_prop)
##       eigenvalue percentage of variance cumulative percentage of variance
## dim 1 0.08305442                    100                               100

Se aprecia que el porcentaje de varianza esta explicado en 100% con una dimensió que tiene un valor propio de tan solo 0.0830. al igual,que esta indicando la información que esta explicando por la primera dimensión del ananlisis de correspondencia.

Desde el punto de vista para el negocio, podemos concluir que la correlacion entre el tipo de vivienda y zona puede ser de una variación del 0.0830% (no es buena idea). esto sucede porque cada zona abarca muchos apartamentos y casas , con distintas caracteristicas en precio, estrato, area ,etc.

tipo de vivienda, barrio

tabla <- table(data$barrio, data$tipo)

chisq.test(tabla)
## Warning in chisq.test(tabla): Chi-squared approximation may be incorrect
## 
##  Pearson's Chi-squared test
## 
## data:  tabla
## X-squared = 2431.8, df = 384, p-value < 2.2e-16

la evidencia en contra de la hipótesis nula es muy fuerte, lo que sugiere que hay una asociación significativa entre las variables en la tabla de contingencia.

resultados_ac <- CA(tabla)
valores_prop <-resultados_ac$eig
print(valores_prop)
##       eigenvalue percentage of variance cumulative percentage of variance
## dim 1  0.2923159                    100                               100

Se aprecia que el porcentaje de varianza esta explicado en 100% con una dimensió que tiene un valor propio de 0.2923.

Desde el punto de vista para el negocio, podemos concluir que la correlacion entre el tipo de vivienda y el barrio puede ser de una variación del 30%. no tan buena, pues los barrios pueden tener los dos tipos de vivienda existentes.

zona, barrio

tabla <- table(data$barrio, data$zona)

chisq.test(tabla)
## Warning in chisq.test(tabla): Chi-squared approximation may be incorrect
## 
##  Pearson's Chi-squared test
## 
## data:  tabla
## X-squared = 29260, df = 1536, p-value < 2.2e-16

la evidencia en contra de la hipótesis nula es muy fuerte, lo que sugiere que hay una asociación significativa entre las variables en la tabla de contingencia. La magnitud del estadístico chi-cuadrado y el gran número de grados de libertad indican que hay una diferencia sustancial entre las frecuencias observadas y esperadas.

par(mfrow = c(1, 1), mar = c(4, 4, 2, 1), oma = c(0, 0, 0, 0), mgp = c(2, 1, 0), las = 1)
resultados_ac <- CA(tabla, graph = FALSE)
plot(resultados_ac, cex = 0.5)

valores_prop <-resultados_ac$eig
print(valores_prop)
##       eigenvalue percentage of variance cumulative percentage of variance
## dim 1  0.9617200               27.34332                          27.34332
## dim 2  0.9288778               26.40956                          53.75288
## dim 3  0.8944248               25.43000                          79.18289
## dim 4  0.7321801               20.81711                         100.00000

Entre Zona Barrio se observa que existen mas dimenciones requeridas, que casdi porcentajes similares, donde se destacan las primeras dimensiones que explican el 27.3% y el 26.4% de la variación respectivamente. con un valores eigen de 0.96 y 0.92 respectivamente.

Para el negocio, es evidente que existe una clara relación entre zona y barrio, pues su relación es directa, y se divide en 4 dimensiones que se asemejan a las zonas que tenemos en la ciudad de Cali