El siguiente documento constituye un análisis de las viviendas proporcionadas en el archivo ‘vivienda_faltantes.csv’. El propósito principal es examinar el comportamiento de las ventas y desarrollar estrategias de marketing enfocadas en segmentos especializados, así como servicios diseñados para satisfacer las necesidades específicas de nuestros clientes.
Realizar un análisis descriptivo de los datos proporcionados con el propósito de identificar patrones y tendencias de importancia. Estos hallazgos actuarán como pilares orientativos esenciales para la elaboración de planes de marketing en el futuro.
Para llevar a cabo el análisis siguiente, se contó con las siguientes fases y sub procesos respectivos:
library(readr)
library(dplyr)
library(stringr)
library(mice)
library(knitr)
library(kableExtra)
library(ggplot2)
library(wordcloud)
library(tidyr)
library(psych)
library(gridExtra)
extractor_datos <- function(x) {
#Función empleada para hacer quitar descripción de texto innecesario en datos
#ejemplo: 'Mean:80.5' se desea quitar el 'Mean:'
#**NOTA**: No se trata de una función genérica, sino de una función personalizada
#construida específicamente para estos datos.
#
#@texto_con_indicador(string): texto a remover descripción del indicador
#@return(string): indicador sin la descripción del campo
trimws(str_split(x, ':', n = 2)[[1]][2])
}
ajustar_coordenadas <-function(x) {
#Función empleada para hacer procesamiento de las coordenadas longitud y latitud
#**NOTA**: No se trata de una función genérica, sino de una función personalizada
#construida específicamente para estos datos.
#
#@coordenada(float): coordeanda a procesar
#@return(float): coordenada previamente procesada
if (abs(x) >1000){
return (x /1000)
} else {
return (x)
}
}
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)
}
contiene_caracteres_especiales <- function(texto){
#Función empleada para verificar que no tenga mas caracteres especiales en mi etiqueta
#luego de haberlos tratado con la función 'reemplazar_caracteres'
#**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(bool): validación si contiene caracter especial
patron <- '[^[:alpha:]]'
if (gsub(' ','',texto) == gsub(patron,'',texto)){
return (FALSE)
} else {return(TRUE)}
}
data <- read_csv('vivienda_faltantes.csv')
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°1 Resumen de dataset antes de tratamientos**",
align = "c", escape = FALSE,
row.names = FALSE,
booktabs = TRUE)
| num_filas | num_col | vacios_totales | porc_vacios | filas_con_mas_un_vacio | filas_all_vacios | col_all_vacios | filas_duplicadas |
|---|---|---|---|---|---|---|---|
| 8330 | 13 | 4279 | 3.95% | 3518 | 2 | 0 | 9 |
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:
data <- data[rowSums(is.na(data)) < ncol(data), ]
data <- data[!duplicated(data), ]
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**",
align = "c", escape = FALSE,
row.names = FALSE,
booktabs = TRUE)
| num_filas | num_col | vacios_totales | porc_vacios | filas_con_mas_un_vacio | filas_all_vacios | col_all_vacios | filas_duplicadas |
|---|---|---|---|---|---|---|---|
| 8320 | 13 | 4249 | 3.93% | 3512 | 0 | 0 | 0 |
grafico_vacios <-md.pattern(data, rotate.names = TRUE)
data <- data[!is.na(data$id), ]
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°3 Resumen de dataset con tratamientos y registros mayormente completados**",
align = "c", escape = FALSE,
row.names = FALSE,
booktabs = TRUE)
| num_filas | num_col | vacios_totales | porc_vacios | filas_con_mas_un_vacio | filas_all_vacios | col_all_vacios | filas_duplicadas |
|---|---|---|---|---|---|---|---|
| 8319 | 13 | 4237 | 3.92% | 3511 | 0 | 0 | 0 |
Se obtiene la Tabla n°3 Resumen de dataset con tratamientos donde se aprecia el total de registros con el que se procederan a realizar las siguientes fases.
Se inicia con una clasificación de cada uno de los campos entre cuantitativos y cualitativos
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
Algunas variables directamente no se ven representantes para nuestro estudio, como es el caso de: ‘id’
col <- 'piso'
frecuencias <- table(data$piso)
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$piso)
## vars n mean sd median trimmed mad min max range skew kurtosis se
## X1 1 5684 3.77 2.61 3 3.37 1.48 1 12 11 1.28 1.05 0.03
El gráfico indica que los registros estan sesgados a la izquierda (el 80.77% de los datos estan entre el 1-5 piso), con una media del 3.77 y una desviación 2.61 nota este campo presenta una cantidad considerabe de registros vacios muy importante 2635 vacios, por lo que se ha decidido crear una nueva categoria y examinarlo como un caso especial.
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.
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 368.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.
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 indica 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.
col <- 'parquea'
frecuencias <- table(data$parquea)
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$parquea)
## vars n mean sd median trimmed mad min max range skew kurtosis se
## X1 1 6717 1.84 1.12 2 1.62 1.48 1 10 9 2.33 8.31 0.01
El gráfico revela que los registros muestran un sesgo hacia la izquierda, el 83.82% de los datos estan en el 1 y 2 piso.nota recordemos que esta variable representa una alta cantidad de valores perdido 1602, por lo que se manejara los faltantes como una nueva categoria y se revisara mas adelante como un caso especial.
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.
col <- 'habitac'
frecuencias <- table(data$habitac)
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$habitac)
## 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.
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 pone de manifiesto problemas en los registros, ya que las coordenadas de la ciudad no superan las tres cifras en su parte entera. Por lo tanto, es necesario llevar a cabo un proceso de tratamiento en este campo.
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)
}
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)
Se pueden identificar tipo de apartamentos con falta de estandarización en los datos. Algunos ejemplos de esto son el uso de mayúsculas y abreviaciones.
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.
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.
-Se estandariza el campo tipo de vivienda
data$tipo <- gsub("^Apartamento", "Apartamento", data$tipo, ignore.case = TRUE)
data$tipo <- gsub("^apto", "Apartamento", data$tipo, ignore.case = TRUE)
data$tipo <- gsub("^Apartamento", "Apartamento", data$tipo, ignore.case = TRUE)
data$tipo <- gsub("^casa", "Casa", data$tipo, ignore.case = TRUE)
data$tipo <- gsub("^casa", "casa", data$tipo, ignore.case = TRUE)
-Se estandariza el campo barrio
data$barrio <- sapply(data$barrio, reemplazar_caracteres)
data$longitud <- sapply(data$longitud, ajustar_coordenadas)
data$latitud <- sapply(data$latitud, ajustar_coordenadas)
Veamos como quedaron las variables que normalizamos:
col <- 'tipo'
frecuencia_palabras_temp = aggregate(data[[col]], list(data[[col]]), FUN=length)
colnames(frecuencia_palabras_temp)=c("Palabra","Frecuencia")
frecuencia_palabras_temp = as.data.frame(frecuencia_palabras_temp)
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)
Veamos que Aparatmentos tiene una alta participación en la base de datos, un dato muy interesante de revisar frente al precio y otras varaibles de interes
col <- 'barrio'
frecuencia_palabras_temp = aggregate(data[[col]], list(data[[col]]), FUN=length)
colnames(frecuencia_palabras_temp)=c("Palabra","Frecuencia")
frecuencia_palabras_temp = as.data.frame(frecuencia_palabras_temp)
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)
## Warning in wordcloud(words = frecuencia_palabras_temp$Palabra, freq =
## frecuencia_palabras_temp$Frecuencia, : VALLE DEL LILI could not be fit on page.
## It will not be plotted.
Veamos que el barrio mas representativo son: Ciudad Jardin, Pance , La flora y santa teresita. De hecho son barrios que representan a la Zona sur, el norte de Cali y el oeste de Cali corroborando el comportamiento que tiene las zonas.
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, las concentraciones visibles nos llevan a cuestionar si la transformación de los registros ocurrió de acuerdo a nuestras expectativas. Para abordar esto, procedimos a realizar una clasificación del mismo gráfico, dividiéndolo en zonas. Esto nos brindará una comprensión más precisa de los datos en juego
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 muy confiable
resumen_df <- as.data.frame.matrix(summary(data))
resumen_df <- t(resumen_df)
columns_with_text <- sapply(resumen_df, is.character)
for (col in colnames(resumen_df)) {
resumen_df[,col] <- sapply(resumen_df[,col], extractor_datos)
}
# Separamos el resumen en dos dataframes: uno para variables cuantitativas y otro para cualitativas
types <- sapply(data, class)
resumen_cuantitativa <- resumen_df[types %in% c("numeric", "integer"), ]
nuevos_nombres <- c("Min.","1st Qu.","Median","Mean","3rd Qu.","Max","NA's")
colnames(resumen_cuantitativa) <- nuevos_nombres
kable(as.data.frame(resumen_cuantitativa),
format = "markdown",
caption = "**Tabla n°4 Tabla de resumen estadistico - campos cuantitativos**",
align = "c", escape = FALSE,
row.names = TRUE,
booktabs = TRUE,
table.attr = 'style="border-collapse: collapse; border-spacing: 0 5px; border: 1px solid black;"')
| Min. | 1st Qu. | Median | Mean | 3rd Qu. | Max | NA’s | |
|---|---|---|---|---|---|---|---|
| id | 1 | 2080 | 4160 | 4160 | 6240 | 8319 | NA |
| piso | 1.000 | 2.000 | 3.000 | 3.771 | 5.000 | 12.000 | 2635 |
| estrato | 3.000 | 4.000 | 5.000 | 4.634 | 5.000 | 6.000 | NA |
| preciom | 58.0 | 220.0 | 330.0 | 433.9 | 540.0 | 1999.0 | NA |
| areaconst | 30.0 | 80.0 | 123.0 | 174.9 | 229.0 | 1745.0 | NA |
| parquea | 1.000 | 1.000 | 2.000 | 1.835 | 2.000 | 10.000 | 1602 |
| banios | 0.000 | 2.000 | 3.000 | 3.111 | 4.000 | 10.000 | NA |
| habitac | 0.000 | 3.000 | 3.000 | 3.605 | 4.000 | 10.000 | NA |
| longitud | -76.59 | -76.54 | -76.53 | -76.53 | -76.52 | -76.46 | NA |
| latitud | 3.333 | 3.381 | 3.416 | 3.418 | 3.452 | 3.498 | NA |
Un resumen que nos muestra los indicadores de tendencia central y de posición, pero que nos muestra o al menos para mi una tendencia generalizada en todos los campos de la tabla. por lo que el siguiente paso sera revisar los campos que nos llamo la atención:
En los campos piso y habitación tenemos campos vacios, los vamos a rellenar con un valor arbitrario para diferenciarlos y estudiarlos de una manera aislada posteriormente, valor arbitrario sera 0, para poder procesar modelos númericos y mas adelante, realiza los estudio con estos valores y sin ellos
data$parquea <- replace(data$parquea, is.na(data$parquea),0)
data$piso <- replace(data$piso, is.na(data$piso),0)
Partiendo del estudio de la varaible de precios,podemos partir de un valor atipico si supera ciertas caracteristicas, especialmente en la parte superiro del precio. para ello vamos a calcular el limite teorico del valor atipico y revisar el impacto con otras variables
#Calculamos la linea de valor atipico con base en los valores generales de la base de datos
Q3 = as.numeric(resumen_cuantitativa[which(rownames(resumen_cuantitativa) == " preciom"), "3rd Qu."])
Q1 = as.numeric(resumen_cuantitativa[which(rownames(resumen_cuantitativa) == " preciom"), "1st Qu."])
datos_atipicos_precio= Q3 + 1.5 * (Q3 - Q1)
valores_promedio <- aggregate(preciom ~ zona, data = data, mean)
ggplot(data=data, aes(x=zona, y=preciom))+
geom_boxplot(fill="#0576FF")+
geom_point(data = subset(data, preciom > datos_atipicos_precio), aes(color = "Atípico"), size = 3,color = "#FF0576") +
stat_summary(fun = "mean", geom = "point", shape = 18, size = 4, color = "green") +
geom_text(data = valores_promedio, aes(label = round(preciom, 2)), vjust = -0.5) +
labs(x="Zonas", y="precio venta($millones)")+
ggtitle("Precio de las viviendas en las diferentes zonas de Cali")+
theme_minimal() +
theme(plot.background = element_rect(fill = "white"),
panel.grid = element_blank(),
panel.border = element_blank(),
plot.title = element_text(size = 16, hjust = 0.5),
axis.line = element_line(size = 1, color = "#515354"))
Con el pasado grafico de clasificación de zonas, podemos destacar: + la zona Oesta, destaca por aportar muchos valores por encima de nuestro valor atipico teorico y una alta variación entre los precios con que maneja sus viviedas. no obstante su media de precios es superior con diferencia del resto de las zonas. + las zonas sur y norte aportan registros con precios que superan el valor atipico teorico, pero su boxplot son mas compactos, lo que nos dice que sus desviaciones no son tan altas con respecto a la zona Oeste + la zona oriente y centro tiene cajas de bigotes mas compactas, sus medias de precios son las mas bajas entre el resto de las zonas y cuentan con pocos valores atipicos De hecho, llama la atención que tengan registros que superan el valor atipico teorico
data$estrato_categoria <- factor(data$estrato, levels = sort(unique(data$estrato)))
valores_promedio <- aggregate(preciom ~ estrato_categoria, data = data, mean)
ggplot(data=data, aes(x=estrato_categoria, y=preciom))+
geom_boxplot(fill="#0576FF")+
geom_point(data = subset(data, preciom > datos_atipicos_precio), aes(color = "Atípico"), size = 3,color = "#FF0576") +
stat_summary(fun = "mean", geom = "point", shape = 18, size = 4, color = "green") +
geom_text(data = valores_promedio, aes(label = round(preciom, 2)), vjust = -0.5) +
labs(x="Estratos", y="precio venta($millones)")+
ggtitle("Precio de las viviendas en los diferentes estratos de la base de datos")+
theme_minimal() +
theme(plot.background = element_rect(fill = "white"),
panel.grid = element_blank(),
panel.border = element_blank(),
plot.title = element_text(size = 16, hjust = 0.5),
axis.line = element_line(size = 1, color = "#515354"))
Con el pasado grafico de clasificación de estratos, podemos destacar: + Se confirma la tendencia del engocio de que el estrato marca el precio de la vivienda, entre mas alto el estrato , mas alto es el estrato , mayor sera el precio almenos en una tendencia general + Todos los estratos tiene mucha variacion en los precios + Todos los estratos cuentan con registros que superan el precio atipico teorico calculado, lo que nos hace sospechar de que debe existir otro campo o campos igualmente influyentes en el precio fuera del estrato.
Tal como observamos en el punto anterior, el estrato revela ciertas tendencias en los precios. Paralelamente, notamos que las zonas también pueden ejercer una influencia significativa en los precios. Por esta razón, consideramos valioso visualizar la interacción de estos dos aspectos con el precio promedio mediante un diagrama de burbujas.
# Contar registros para cada combinación de "estrato" y "zona"
conteo <- table(data$estrato_categoria, data$zona)
conteo_df <- as.data.frame(as.table(conteo))
names(conteo_df) <- c("estrato", "zona", "conteo")
diagrama_conteo <- ggplot(conteo_df, aes(x = estrato, y = zona, size= conteo, color = conteo)) +
geom_point() +
scale_size_continuous(range = c(5, 20), guide = "none") +
scale_color_gradient(low = "blue", high = "darkblue") +
labs(title = "Diagrama de Burbuja",
x = "Estrato",
y = "Zona",
size = "Conteo")+
theme_minimal() +
theme(plot.background = element_rect(fill = "white"),
panel.grid = element_blank(),
panel.border = element_blank(),
plot.title = element_text(size = 16, hjust = 0.5),
axis.line = element_line(size = 1, color = "#515354"))
promedio_precio <- aggregate(preciom ~ estrato_categoria + zona, data = data, FUN = mean)
tabla_mediaPrecios <- xtabs(preciom ~ estrato_categoria + zona, data = promedio_precio)
mediaPrecios_df <- as.data.frame(as.table(tabla_mediaPrecios))
names(mediaPrecios_df) <- c("estrato", "zona", "MediaPrecios")
diagrama_precio <- ggplot(mediaPrecios_df, aes(x = estrato, y = zona, size= MediaPrecios, color = MediaPrecios)) +
geom_point() +
scale_size_continuous(range = c(5, 20), guide = "none") +
scale_color_gradient(low = "blue", high = "#FF0576") +
labs(title = "Diagrama de Burbuja de precios en Millones",
x = "Estrato",
y = "Zona",
size = "Precio medio ($milones)")+
theme_minimal() +
theme(plot.background = element_rect(fill = "white"),
panel.grid = element_blank(),
panel.border = element_blank(),
plot.title = element_text(size = 16, hjust = 0.5),
axis.line = element_line(size = 1, color = "#515354"))
grid.arrange(diagrama_conteo, diagrama_precio, ncol = 1,
heights = c(2, 2))
Observamos comportamientos bien interesantes: + El precio medio si es muy marcado alto en estratos altos, con una excepción estrato 5 - zona oriente, + dependiendo de las zonas, asi mismo se afecta la participación en el estrato, es decir: + la zona sur destaca en estratos 4 y 5 + la zona oriente en estrato 3 (Tiene sentido, es zona con pocos barrieos de estrato alto) + la zona norte esta muy disribuida entre 3,4, 5 con inclinación a este ultimo. + la zona oeste con una alta concentración el estrato 6 + Llama mucha la atención el precio medio de estrato 6 - zona oriente con un precio medio por encima del resto de las zonas y estratos.
Dejando la conclusión, deben existir otros vehiculos que acompañen a estas variables de estrato y zona que estan dando tendencia en nuestro analisis.
resumen_portipo = aggregate(preciom ~ tipo, data = data, FUN= function(x) c(Conteo = length(x), Total =sum(x)))
resultado <-as.data.frame(resumen_portipo$preciom)
resumen_portipo$Conteo <- resultado$Conteo
resumen_portipo$Total <- resultado$Total
resumen_portipo$Porc_Conteo <- prop.table(resumen_portipo$Conteo) * 100
resumen_portipo$Porc_Total <- prop.table(resumen_portipo$Total) * 100
resumen_portipo_long = gather(resumen_portipo[,c('tipo','Porc_Conteo','Porc_Total')], key="Tipo_Porc", value= "Porc",-tipo)
ggplot(resumen_portipo_long, aes(x = Tipo_Porc, y = Porc, fill = tipo)) +
geom_bar(stat = "identity", width = 0.5) +
geom_text(aes(label =scales::percent(Porc/100)) ,
position = position_stack(vjust = 0.5), size = 4) +
labs(title = "% de Apartamentos y Casas ( % en evento y $$$ ) n. 3",
x = "Tipo de Apartamento",
y = "") +
theme_minimal() +
theme(axis.text.y= element_blank(),
panel.background = element_rect(fill="white"),
panel.grid=element_blank(),
panel.border = element_blank(),
plot.title = element_text(size = 16, hjust = 0.5),)+
scale_fill_manual(values = c("#0576FF", "#50AC05", "red", "orange", "purple"))
Cuando vimos el analisis de la variable tipo de vivienda, recordemos que Apartamento se veia con tendencia frente a las casas. 61.3% del total de registros, lo llamativo es que a pesar de su aprticipación , en ventas totales fue casi igualadad por las casas bajo al 51.8% su participación global. dejando un campo muy interesante a revisar para nuestro estudio de determinar campos que son conductores del precio.
Con varios campos ya perfilados como interesantes para nuestro analisis, vamos a realizar una correlación entre los campos presenten en la base para determinar nuevos candidatos de vehiculos del precio de vivienda.
#vamos a buscar cuales caracteristicas tiene una correlación con preciom
campos_cuantitativos <- gsub(" ", "", rownames(resumen_cuantitativa))
data$zona_index = as.integer(factor(data$zona, levels = unique(data$zona)))
data$tipo_index = as.integer(factor(data$tipo, levels = unique(data$tipo)))
data$barrio_index = as.integer(factor(data$barrio, levels = unique(data$barrio)))
cor_data <- cor(select(data, -tipo, -barrio, -zona, -id,-estrato_categoria))
grafico_n4 <- corPlot(cor_data,
cex = 0.7,
xsrt = 75,
main = "Matriz de correlación") # Color de línea entre los números
Nota: Es importante recordar que en el caso de los campos “piso” y “parqueo” teníamos muchos registros faltantes, por lo que debemos tener en cuenta la relevancia de sus valores de correlación.
Hemos observado las siguientes relaciones interesantes:
Con base en las fase de resultados, se plantean revisar las siguientes hipotesis y sacar conclusiones
Para confirmar este comportamiento vamos que tener que revisar una variable que conectes el precio con el tipo dado su relación con ambas variables: area de contrucción
ggplot(data=data) +
geom_point(aes(x=areaconst,y=preciom, col=estrato_categoria),
#col='red',
size=3, alpha = 1/5 )+
geom_smooth(aes(x=areaconst,y=preciom, linetype=estrato_categoria),
method = 'lm', se=FALSE, col='#FF0576')+
facet_wrap(~ estrato_categoria, nrow =4)+
labs(title = "Gráfico de Relación entre Área y Precio por estratos")+
theme(panel.background = element_rect(fill = "white"))
## `geom_smooth()` using formula = 'y ~ x'
Observamos que el area de contrucción, clasificando por estrato tiene algo muy interesante, su pendiente aumenta a medida que subimos de estrato y subimos las areas de contrucción. bastante llamativo tambien es las dispersión que se muestra en pocas áreas de contrucción.
ggplot(data=data) +
geom_point(aes(x=areaconst,y=preciom, col=zona),
#col='red',
size=3, alpha = 1/5 )+
geom_smooth(aes(x=areaconst,y=preciom, linetype=zona),
method = 'lm', se=FALSE, col='#FF0576')+
facet_wrap(~ zona, nrow =4)+
labs(title = "Gráfico de Relación entre Área y Precio por zonas")+
theme(panel.background = element_rect(fill = "white"))
## `geom_smooth()` using formula = 'y ~ x'
En definitiva el área de contrucción es una variable que destaca tambien zonas, donde vemos que viendo esta relación que nos adelantaba las correlación. aunque, sigue llamando al dispersión del precio en áreas muy pequeñas
ggplot(data=data) +
geom_point(aes(x=areaconst,y=preciom, col=tipo),
#col='red',
size=3, alpha = 1/5 )+
geom_smooth(aes(x=areaconst,y=preciom, linetype=tipo),
method = 'lm', se=FALSE, col='#FF0576')+
facet_wrap(~ tipo, nrow =4)+
labs(title = "Gráfico de Relación entre Área y Precio por tipo de vivienda")+
theme(panel.background = element_rect(fill = "white"))
## `geom_smooth()` using formula = 'y ~ x'
Fantanstico! una pendiente muy distinta al separarlo con vivienda, y una dispersión menor al separar estas dos variables, lo que nos deja muy claro que el tipo de vivienda debe ser separada en nuestro estudio y construir dos tipos de modelos
cor_data <- cor(select(filter(data, tipo == 'Apartamento'), -tipo,-tipo_index, -barrio, -zona, -id,-estrato_categoria))
grafico_n4 <- corPlot(cor_data,
cex = 0.7,
xsrt = 75,
main = "Matriz de correlación") # Color de línea entre los números
Vemos que las relaciones se fortalecieron trayendo nuevaos campos: + Los baños destacan con una alta correlacion frente al modelo sin segregación del tipo de apartamento, pero recordemos que guarda una relación de logica con el área de contrucción + parqueo destaca como nuevo campo, pero tiene una restricción esta relaccion peude ser afectada por nuestra categoria sin valor, que esta en cero. + habitaciones aparece tener una relación con baños,y no tener relación con el precio, caso similar con el tipo de casa en el modelo inicial.
Veamos si dejando los valores de parqueo con valores diferentes a nulo, nos da una mirada diferente.
cor_data <- cor(select(filter(data, tipo == 'Apartamento', parquea > 0), -tipo,-tipo_index, -barrio, -zona, -id,-estrato_categoria))
grafico_n4 <- corPlot(cor_data,
cex = 0.7,
xsrt = 75,
main = "Matriz de correlación") # Color de línea entre los números
Vemos que la relación mejoro frente al precio (de 0.69 a 0.74 ) y el área de construcción (de 0.62 a 0.68). es decir que el cambio de llenar los datos faltante con valor cero, estaba perjudicando la relación de los datos.
data_temp <- filter(data, tipo == 'Apartamento', parquea > 0)
data_temp$parquea_categoria <- factor(data_temp$parquea, levels = sort(unique(data_temp$parquea)))
ggplot(data=data_temp) +
geom_point(aes(x=areaconst,y=preciom, col=parquea_categoria),
#col='red',
size=3, alpha = 1/5 )+
geom_smooth(aes(x=areaconst,y=preciom, linetype=parquea_categoria),
method = 'lm', se=FALSE, col='#FF0576')+
facet_wrap(~ parquea_categoria, nrow =4)+
labs(title = "Gráfico de Relación entre Área y Precio por número de paqueos")+
theme(panel.background = element_rect(fill = "white"))
## `geom_smooth()` using formula = 'y ~ x'
Se observa que a medida que vamos sumando el número de parqueo la
pendiente de precio por área de contrucción es mas inclinada,
confirmando la relacción con parqueo
ggplot(data=filter(data, tipo == 'Apartamento', parquea > 0)) +
geom_point(aes(x=areaconst,y=preciom, col=habitac),
#col='red',
size=3, alpha = 1/5 )+
# geom_smooth(aes(x=areaconst,y=preciom, linetype=habitac),
# method = 'lm', se=FALSE, col='#FF0576')+
facet_wrap(~ habitac, nrow =4)+
labs(title = "Gráfico de Relación entre Área y Precio por n° habitaciones")+
theme(panel.background = element_rect(fill = "white"))
Se podria decir que en la pendiente de los datos, independiente de la
habitación se mantiene igual. por lo que creemos q el número de
habitaciones podria ser un facot r no tan relevante para nuestro estudio
para el casod e los apartamentos
cor_data <- cor(select(filter(data, tipo == 'casa'), -tipo,-tipo_index, -barrio, -zona, -id,-estrato_categoria))
grafico_n4 <- corPlot(cor_data,
cex = 0.7,
xsrt = 75,
main = "Matriz de correlación") # Color de línea entre los números
Se destaca las siguientes correlaciones: * parqueo vuelve a jugar un papel imporante en las correlaciones * habitaciones aparece tener una relación con baños,y no tener relación con el precio, caso similar con el tipo de casa en el modelo inicial.
ggplot(data=filter(data, tipo == 'casa', parquea > 0)) +
geom_point(aes(x=areaconst,y=preciom, col=habitac),
#col='red',
size=3, alpha = 1/5 )+
#geom_smooth(aes(x=areaconst,y=preciom, linetype=habitac),
# method = 'lm', se=FALSE, col='#FF0576')+
facet_wrap(~ habitac, nrow =4)
Se podria decir que en la pendiente de los datos va bajando con el base
en el número de habitaciones. por lo que creemos q el número de
habitaciones podria ser un factor no tan relevante para nuestro estudio
para el caso de las casas
Unos de los graficos que determino tendencias muy marcadas por geolocalización fue un comparativo tipo diagrama de burbuja, que me gustaria revisarlo bajo la mirada de los modelos de apartamento y casa
# Contar registros para cada combinación de "estrato" y "zona"
data_temp <- filter(data, tipo == 'casa', parquea > 0)
conteo <- table(data_temp$estrato_categoria, data_temp$zona)
conteo_df <- as.data.frame(as.table(conteo))
names(conteo_df) <- c("estrato", "zona", "conteo")
diagrama_conteo <- ggplot(conteo_df, aes(x = estrato, y = zona, size= conteo, color = conteo)) +
geom_point() +
scale_size_continuous(range = c(5, 20), guide = "none") +
scale_color_gradient(low = "blue", high = "darkblue") +
labs(title = "Diagrama de Burbuja de número de observaciones - modelo casa",
x = "Estrato",
y = "Zona",
size = "Conteo")+
theme_minimal() +
theme(plot.background = element_rect(fill = "white"),
panel.grid = element_blank(),
panel.border = element_blank(),
plot.title = element_text(size = 16, hjust = 0.5),
axis.line = element_line(size = 1, color = "#515354"))
promedio_precio <- aggregate(preciom ~ estrato_categoria + zona, data = data_temp, FUN = mean)
tabla_mediaPrecios <- xtabs(preciom ~ estrato_categoria + zona, data = promedio_precio)
mediaPrecios_df <- as.data.frame(as.table(tabla_mediaPrecios))
names(mediaPrecios_df) <- c("estrato", "zona", "MediaPrecios")
diagrama_precio <- ggplot(mediaPrecios_df, aes(x = estrato, y = zona, size= MediaPrecios, color = MediaPrecios)) +
geom_point() +
scale_size_continuous(range = c(5, 20), guide = "none") +
scale_color_gradient(low = "blue", high = "#FF0576") +
labs(title = "Diagrama de Burbuja de precios en Millones - modelo casa",
x = "Estrato",
y = "Zona",
size = "Precio medio ($milones)")+
theme_minimal() +
theme(plot.background = element_rect(fill = "white"),
panel.grid = element_blank(),
panel.border = element_blank(),
plot.title = element_text(size = 16, hjust = 0.5),
axis.line = element_line(size = 1, color = "#515354"))
grid.arrange(diagrama_conteo, diagrama_precio, ncol = 1,
heights = c(2, 2))
Observamos comportamientos bien interesantes:
La zona sur destaca en el uso de casas, por lo que seria un buen sector nicho de clientes y tiene un promedio favorable de precios.especialmente estratos 4,5 y 6 (coincide con el crecimiente número de proyectos que tiene la ciudad en esta zona)
La zona Norte en estratos 4 y 5 seria interesante realizar inversiones en casas, aunque sus precios medios no destacan frente al resto de zonas
la zona oriente podria verse en estratos 3,unque el precio de venta no es para nada llamativo.
# Contar registros para cada combinación de "estrato" y "zona"
data_temp <- filter(data, tipo == 'Apartamento', parquea > 0)
conteo <- table(data_temp$estrato_categoria, data_temp$zona)
conteo_df <- as.data.frame(as.table(conteo))
names(conteo_df) <- c("estrato", "zona", "conteo")
diagrama_conteo <- ggplot(conteo_df, aes(x = estrato, y = zona, size= conteo, color = conteo)) +
geom_point() +
scale_size_continuous(range = c(5, 20), guide = "none") +
scale_color_gradient(low = "blue", high = "darkblue") +
labs(title = "Diagrama de Burbuja de número de observaciones - modelo apartamento",
x = "Estrato",
y = "Zona",
size = "Conteo")+
theme_minimal() +
theme(plot.background = element_rect(fill = "white"),
panel.grid = element_blank(),
panel.border = element_blank(),
plot.title = element_text(size = 16, hjust = 0.5),
axis.line = element_line(size = 1, color = "#515354"))
promedio_precio <- aggregate(preciom ~ estrato_categoria + zona, data = data_temp, FUN = mean)
tabla_mediaPrecios <- xtabs(preciom ~ estrato_categoria + zona, data = promedio_precio)
mediaPrecios_df <- as.data.frame(as.table(tabla_mediaPrecios))
names(mediaPrecios_df) <- c("estrato", "zona", "MediaPrecios")
diagrama_precio <- ggplot(mediaPrecios_df, aes(x = estrato, y = zona, size= MediaPrecios, color = MediaPrecios)) +
geom_point() +
scale_size_continuous(range = c(5, 20), guide = "none") +
scale_color_gradient(low = "blue", high = "#FF0576") +
labs(title = "Diagrama de Burbuja de precios en Millones - modelo apartamento",
x = "Estrato",
y = "Zona",
size = "Precio medio ($milones)")+
theme_minimal() +
theme(plot.background = element_rect(fill = "white"),
panel.grid = element_blank(),
panel.border = element_blank(),
plot.title = element_text(size = 16, hjust = 0.5),
axis.line = element_line(size = 1, color = "#515354"))
grid.arrange(diagrama_conteo, diagrama_precio, ncol = 1,
heights = c(2, 2))
Observamos comportamientos bien interesantes:
La zona sur destaca en estratos 4 y 5 , aunque sorprende sus precios, no son tan llamativos
la zona oeste es un sector interesante para apartamentos en estratos altos, con una media de precios bastantes
la zona norte en apartamentos esta en los estratos 5, con un precio normal medio frente a otras zonas.
Frente a estrategias de marketing que podemos recomendar a partir de este estudio:
El precio de las viviendas debe variar según el tipo de propiedad, ya sea apartamento o casa, de manera independiente. [5.1.2]
Para ambos modelos, el área de construcción desempeña un papel fundamental en la determinación del precio, y esta área siempre guarda una relación directa con el número de baños. [4.3, 5.2 y 5.3]
Para el modelo de apartamento, es evidente que a medida que aumentamos el número de plazas de estacionamiento, la pendiente del precio en función del área de construcción se vuelve más pronunciada, lo que confirma la relación con el estacionamiento. [5.2.3]
Para el modelo de casas, aparte del área de construcción, no se identifica ninguna otra variable destacada que podamos ofrecer a nuestros clientes. [5.3.1]
En relación a los planes de expansión del negocio inmobiliario, no se sugiere invertir en la zona céntrica, tanto para apartamentos como para casas. [5.4.1 y 5.4.2]
En cuanto a la zona Oriente, se proponen planes para casas de estrato 3, con precios asequibles que varían según los metros cuadrados. [5.4.1 y 5.4.2]
En la zona oeste se resaltan los apartamentos de estratos altos, ofreciendo precios promedios atractivos en comparación con otras zonas. [5.4.1 y 5.4.2]
Las zonas sur y norte, que están en auge en la ciudad de Cali, son adecuadas para proyectos de viviendas, tanto en forma de casas como apartamentos. Sin embargo, en términos generales, sus precios promedios no son tan llamativos (por encima) frente a otras zonas. [5.4.1 y 5.4.2]