str(vivienda)
## spc_tbl_ [8,322 × 13] (S3: spec_tbl_df/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 ...
## - attr(*, "spec")=
## .. cols(
## .. id = col_double(),
## .. zona = col_character(),
## .. piso = col_character(),
## .. estrato = col_double(),
## .. preciom = col_double(),
## .. areaconst = col_double(),
## .. parqueaderos = col_double(),
## .. banios = col_double(),
## .. habitaciones = col_double(),
## .. tipo = col_character(),
## .. barrio = col_character(),
## .. longitud = col_double(),
## .. latitud = col_double()
## .. )
## - attr(*, "problems")=<externalptr>
El piso deberia ser una variable numerica por lo que se realiza la conversion
db = vivienda
db$piso = as.numeric(db$piso)
head(db)
## # A tibble: 6 × 13
## id zona piso estrato preciom areaconst parqueaderos banios habitaciones
## <dbl> <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 1147 Zona O… NA 3 250 70 1 3 6
## 2 1169 Zona O… NA 3 320 120 1 2 3
## 3 1350 Zona O… NA 3 350 220 2 2 4
## 4 5992 Zona S… 2 4 400 280 3 5 3
## 5 1212 Zona N… 1 5 260 90 1 2 3
## 6 1724 Zona N… 1 5 240 87 1 3 3
## # ℹ 4 more variables: tipo <chr>, barrio <chr>, longitud <dbl>, latitud <dbl>
Se consultan valores unicos de variables categoricas para identificar correcciones necesarias
columnas_categoricas = db %>% select_if(is.character)
valores_unicos = sapply(columnas_categoricas, unique)
print(valores_unicos)
## $zona
## [1] "Zona Oriente" "Zona Sur" "Zona Norte" "Zona Oeste" "Zona Centro"
## [6] NA
##
## $tipo
## [1] "Casa" "Apartamento" NA
##
## $barrio
## [1] "20 de julio" "3 de julio"
## [3] "acopi" "agua blanca"
## [5] "aguablanca" "aguacatal"
## [7] "alameda" "alameda del río"
## [9] "alameda del rio" "alamos"
## [11] "alborada" "alcazares"
## [13] "alférez real" "alferez real"
## [15] "alfonso lópez" "alfonso lópez i"
## [17] "alfonso lopez" "alto jordán"
## [19] "altos de guadalupe" "altos de menga"
## [21] "altos de santa" "antonio nariño"
## [23] "aranjuez" "arboleda"
## [25] "arboleda campestre candelaria" "arboledas"
## [27] "atanasio girardot" "autopista sur"
## [29] "bajo aguacatal" "barranquilla"
## [31] "barrio 7de agosto" "barrio el recuerdo"
## [33] "barrio eucarístico" "barrio obrero"
## [35] "barrio tranquilo y" "base aérea"
## [37] "belalcazar" "Belalcazar"
## [39] "belisario caicedo" "bella suiza"
## [41] "bella suiza alta" "bellavista"
## [43] "benjamín herrera" "berlin"
## [45] "bloques del limonar" "bochalema"
## [47] "bolivariano" "bosques de alboleda"
## [49] "bosques del limonar" "boyacá"
## [51] "bretaña" "brisas de guadalupe"
## [53] "brisas de los" "Brisas De Los"
## [55] "brisas del guabito" "brisas del limonar"
## [57] "Bueno Madrid" "buenos aires"
## [59] "cañasgordas" "cañaveralejo"
## [61] "cañaverales" "cañaverales los samanes"
## [63] "caldas" "Cali"
## [65] "cali bella" "cali canto"
## [67] "calibella" "calicanto"
## [69] "calicanto viii" "calima"
## [71] "calimio norte" "calipso"
## [73] "cambulos" "camino real"
## [75] "Camino Real" "campestre"
## [77] "caney" "caney especial"
## [79] "capri" "cascajal"
## [81] "cataya real" "ceibas"
## [83] "centelsa" "centenario"
## [85] "Centenario" "centro"
## [87] "cerro cristales" "cerros de guadalupe"
## [89] "champagnat" "chapinero"
## [91] "chiminangos" "Chiminangos"
## [93] "chiminangos 1 etapa" "chiminangos 2 etapa"
## [95] "chipichape" "ciudad 2000"
## [97] "Ciudad 2000" "ciudad antejardin"
## [99] "ciudad bochalema" "ciudad córdoba"
## [101] "ciudad córdoba reservado" "ciudad capri"
## [103] "ciudad cordoba" "ciudad country"
## [105] "ciudad del campo" "ciudad jardín"
## [107] "Ciudad Jardín" "ciudad jardin"
## [109] "ciudad jardin pance" "ciudad los álamos"
## [111] "ciudad los alamos" "ciudad meléndez"
## [113] "ciudad melendez" "ciudad modelo"
## [115] "ciudad pacifica" "Ciudad Pacifica"
## [117] "ciudad real" "ciudad talanga"
## [119] "ciudad universitaria" "ciudadela comfandi"
## [121] "ciudadela del río" "ciudadela melendez"
## [123] "ciudadela paso ancho" "ciudadela pasoancho"
## [125] "colinas de menga" "colinas del bosque"
## [127] "colinas del sur" "colon"
## [129] "colseguros" "colseguros andes"
## [131] "Colseguros Andes" "comfenalco"
## [133] "compartir" "conjunto gibraltar"
## [135] "cristóbal colón" "cristales"
## [137] "cristobal colón" "cuarto de legua"
## [139] "departamental" "ed benjamin herrera"
## [141] "el bosque" "El Bosque"
## [143] "el caney" "El Caney"
## [145] "el castillo" "el cedro"
## [147] "el diamante" "el dorado"
## [149] "el gran limonar" "el guabal"
## [151] "el guabito" "el ingenio"
## [153] "El Ingenio" "el ingenio 3"
## [155] "el ingenio i" "el ingenio ii"
## [157] "el ingenio iii" "el jardín"
## [159] "el jordán" "el lido"
## [161] "el limonar" "el nacional"
## [163] "el paraíso" "el peñon"
## [165] "el prado" "el refugio"
## [167] "el rodeo" "el sena"
## [169] "el trébol" "el troncal"
## [171] "el vallado" "eucarístico"
## [173] "evaristo garcía" "farrallones de pance"
## [175] "fenalco kennedy" "fepicol"
## [177] "flora" "flora industrial"
## [179] "floralia" "fonaviemcali"
## [181] "francisco eladio ramirez" "fuentes de la"
## [183] "gaitan" "gran limonar"
## [185] "granada" "guadalupe"
## [187] "guadalupe alto" "guaduales"
## [189] "guayaquil" "hacienda alferez real"
## [191] "ingenio" "ingenio i"
## [193] "ingenio ii" "jamundi"
## [195] "jamundi alfaguara" "jorge eliecer gaitán"
## [197] "jorge isaacs" "jose manuel marroquín"
## [199] "juanamb√∫" "juanambu"
## [201] "junín" "junin"
## [203] "la alborada" "la alianza"
## [205] "la arboleda" "la base"
## [207] "la buitrera" "la campiña"
## [209] "la cascada" "la ceibas"
## [211] "la esmeralda" "la flora"
## [213] "La Flora" "la floresta"
## [215] "la fortaleza" "la gran colombia"
## [217] "la hacienda" "La Hacienda"
## [219] "la independencia" "la libertad"
## [221] "la luisa" "la merced"
## [223] "la morada" "la nueva base"
## [225] "la playa" "la portada al"
## [227] "la primavera" "la reforma"
## [229] "la rivera" "la rivera i"
## [231] "la rivera ii" "la riverita"
## [233] "la riviera" "la selva"
## [235] "la villa del" "laflora"
## [237] "lares de comfenalco" "las acacias"
## [239] "las américas" "las camelias"
## [241] "las ceibas" "las delicias"
## [243] "las granjas" "las quintas de"
## [245] "las vegas" "las vegas de"
## [247] "libertadores" "los alamos"
## [249] "los alcázares" "los alcazares"
## [251] "los andes" "los cámbulos"
## [253] "los cambulos" "los cristales"
## [255] "los cristales club" "los farallones"
## [257] "los guaduales" "Los Guaduales"
## [259] "los guayacanes" "los jockeys"
## [261] "los libertadores" "los parques barranquilla"
## [263] "los robles" "lourdes"
## [265] "mamellan" "manzanares"
## [267] "mariano ramos" "marroquín iii"
## [269] "mayapan las vegas" "meléndez"
## [271] "melendez" "menga"
## [273] "metropolitano del norte" "miradol del aguacatal"
## [275] "miraflores" "Miraflores"
## [277] "morichal de comfandi" "multicentro"
## [279] "municipal" "nápoles"
## [281] "napoles" "normandía"
## [283] "normandía west point" "normandia"
## [285] "norte" "norte la flora"
## [287] "nueva base" "nueva floresta"
## [289] "nueva tequendama" "oasis de comfandi"
## [291] "oasis de pasoancho" "occidente"
## [293] "pacará" "pacara"
## [295] "palmas del ingenio" "pampa linda"
## [297] "pampalinda" "panamericano"
## [299] "pance" "Pance"
## [301] "parcelaciones pance" "parque residencial el"
## [303] "paseo de los" "paso del comercio"
## [305] "pasoancho" "poblado campestre"
## [307] "ponce" "popular"
## [309] "portada de comfandi" "portales de comfandi"
## [311] "porvenir" "prados de oriente"
## [313] "prados del limonar" "Prados Del Limonar"
## [315] "prados del norte" "Prados Del Norte"
## [317] "prados del sur" "primavera"
## [319] "primero de mayo" "primitivo crespo"
## [321] "puente del comercio" "puente palma"
## [323] "quintas de don" "Quintas De Don"
## [325] "quintas de salomia" "rafael uribe uribe"
## [327] "refugio" "rep√∫blica de israel"
## [329] "rincón de salomia" "rincon de la"
## [331] "riveras del valle" "rozo la torre"
## [333] "saavedra galindo" "salomia"
## [335] "samanes" "samanes de guadalupe"
## [337] "sameco" "san antonio"
## [339] "san bosco" "san carlos"
## [341] "san cayetano" "san fernando"
## [343] "San Fernando" "san fernando nuevo"
## [345] "san fernando viejo" "san joaquín"
## [347] "san joaquin" "san juan bosco"
## [349] "san judas" "san judas tadeo"
## [351] "san luís" "san luis"
## [353] "san nicolás" "san nicolas"
## [355] "san pedro" "san vicente"
## [357] "santa" "santa anita"
## [359] "Santa Anita" "santa anita sur"
## [361] "santa bárbara" "santa elena"
## [363] "santa fe" "santa helena de"
## [365] "santa isabel" "Santa Isabel"
## [367] "santa mónica" "santa mónica alta"
## [369] "santa mónica popular" "santa mónica residencial"
## [371] "santa monica" "Santa Monica"
## [373] "santa monica norte" "santa monica popular"
## [375] "santa monica residencial" "santa rita"
## [377] "santa rosa" "santa teresita"
## [379] "Santa Teresita" "Santafe"
## [381] "santander" "santo domingo"
## [383] "Santo Domingo" "sector aguacatal"
## [385] "sector cañaveralejo guadalupe" "seminario"
## [387] "sierras de normandía" "siete de agosto"
## [389] "simón bolivar" "tejares cristales"
## [391] "tejares de san" "templete"
## [393] "tequendama" "tequendema"
## [395] "terrón colorado" "torres de comfandi"
## [397] "unión de vivienda" "unicentro cali"
## [399] "urbanización barranquilla" "urbanización boyacá"
## [401] "urbanización colseguros" "urbanización la flora"
## [403] "urbanización la merced" "urbanización la nueva"
## [405] "urbanización las cascadas" "urbanización nueva granada"
## [407] "urbanización pacara" "urbanización río lili"
## [409] "urbanización san joaquin" "urbanización tequendama"
## [411] "urbanizacion el saman" "urbanizacion gratamira"
## [413] "urbanizacion lili" "valle de lili"
## [415] "valle del lili" "Valle Del Lili"
## [417] "valle grande" "versalles"
## [419] "villa colombia" "villa de veracruz"
## [421] "villa del lago" "villa del parque"
## [423] "villa del prado" "Villa Del Prado"
## [425] "villa del sol" "villa del sur"
## [427] "Villas De Veracruz" "villas de veracruz"
## [429] "vipasa" "zona centro"
## [431] "zona norte" "zona norte los"
## [433] "zona oeste" "zona oriente"
## [435] "zona residencial" "zona sur"
## [437] NA
Se puede observar que no hay valores que necesiten ajusten en los atributos zona o tipo, se observan algunos campos en donde es necesario realizar correcciones en el atributo barrio (“alf√©rez real” vs “alferez real”, “mel√©ndez” vs “melendez” o “miraflores” vs “Miraflores”), por lo que se reemplazaran los caracteres no legibles y se convertira todo a minusculas
# cantidad de valores unicos para el barrio
cat("Valores unicos iniciales para barrio:", length(unique(db$barrio)))
## Valores unicos iniciales para barrio: 437
db = db[order(db$id),] # Ordenar por columna id
df = subset(db, select = -c(longitud, latitud)) #elimina columnas innecesarias
df = mutate_if(df, is.character, tolower) #convierte a minusculas
df[df == " "] = "" #elimina espacios dobles del dataframe
df$barrio = gsub("é", "e", df$barrio) #reemplazar errores por acentos
df$barrio = gsub("√∫", "u", df$barrio) #reemplazar errores por acentos
df$barrio = stri_trans_general(str = df$barrio, id = "Latin-ASCII") #elimina acentos
# cantidad de valores unicos para el barrio
cat("Valores unicos finales para barrio:", length(unique(df$barrio)))
## Valores unicos finales para barrio: 386
tbl_precio_tipo = table(df$tipo, df$preciom)
boxplot(df$preciom ~ df$tipo, data =df, ylab = "tipo", xlab = "preciom", horizontal = T, main ="Precio Vs tipo")
boxplot(df$areaconst ~ df$tipo, data =df, ylab = "tipo", xlab = "area", horizontal = T, main = "Area vs Tipo")
plot(df$preciom ~ df$areaconst, data=df, ylab = "preciom", xlab = "area",main = "Area vs Precio")
skim(df)
| Name | df |
| Number of rows | 8322 |
| Number of columns | 11 |
| _______________________ | |
| Column type frequency: | |
| character | 3 |
| numeric | 8 |
| ________________________ | |
| Group variables | None |
Variable type: character
| skim_variable | n_missing | complete_rate | min | max | empty | n_unique | whitespace |
|---|---|---|---|---|---|---|---|
| zona | 3 | 1 | 8 | 12 | 0 | 5 | 0 |
| tipo | 3 | 1 | 4 | 11 | 0 | 2 | 0 |
| barrio | 3 | 1 | 4 | 29 | 0 | 385 | 0 |
Variable type: numeric
| skim_variable | n_missing | complete_rate | mean | sd | p0 | p25 | p50 | p75 | p100 | hist |
|---|---|---|---|---|---|---|---|---|---|---|
| id | 3 | 1.00 | 4160.00 | 2401.63 | 1 | 2080.5 | 4160 | 6239.5 | 8319 | ▇▇▇▇▇ |
| piso | 2638 | 0.68 | 3.77 | 2.61 | 1 | 2.0 | 3 | 5.0 | 12 | ▇▃▁▁▁ |
| estrato | 3 | 1.00 | 4.63 | 1.03 | 3 | 4.0 | 5 | 5.0 | 6 | ▅▆▁▇▆ |
| preciom | 2 | 1.00 | 433.89 | 328.65 | 58 | 220.0 | 330 | 540.0 | 1999 | ▇▂▁▁▁ |
| areaconst | 3 | 1.00 | 174.93 | 142.96 | 30 | 80.0 | 123 | 229.0 | 1745 | ▇▁▁▁▁ |
| parqueaderos | 1605 | 0.81 | 1.84 | 1.12 | 1 | 1.0 | 2 | 2.0 | 10 | ▇▁▁▁▁ |
| banios | 3 | 1.00 | 3.11 | 1.43 | 0 | 2.0 | 3 | 4.0 | 10 | ▇▇▃▁▁ |
| habitaciones | 3 | 1.00 | 3.61 | 1.46 | 0 | 3.0 | 3 | 4.0 | 10 | ▂▇▂▁▁ |
missmap(df,main = "Datos Faltantes",y.labels = ' ', y.at = 1 )
## Warning: Unknown or uninitialised column: `arguments`.
## Unknown or uninitialised column: `arguments`.
#Preprocesamiento de datos
Para el caso particular de la columna piso y parqueadero, se podria interpretar la ausencia como valores que no aplican, por ejemplo, NA para el piso indicaria que es una vivienda de un solo piso, o NA para parqueadero puede indicar que no cuenta con parqueadero, por lo que seria un 0, pero teniendo en cuenta que es un numero considerable de registros, se proceden a eliminar para evitar introducir sesgos a los datos.
df = na.omit(df)
skim(df)
| Name | df |
| Number of rows | 4808 |
| Number of columns | 11 |
| _______________________ | |
| Column type frequency: | |
| character | 3 |
| numeric | 8 |
| ________________________ | |
| Group variables | None |
Variable type: character
| skim_variable | n_missing | complete_rate | min | max | empty | n_unique | whitespace |
|---|---|---|---|---|---|---|---|
| zona | 0 | 1 | 8 | 12 | 0 | 5 | 0 |
| tipo | 0 | 1 | 4 | 11 | 0 | 2 | 0 |
| barrio | 0 | 1 | 4 | 29 | 0 | 244 | 0 |
Variable type: numeric
| skim_variable | n_missing | complete_rate | mean | sd | p0 | p25 | p50 | p75 | p100 | hist |
|---|---|---|---|---|---|---|---|---|---|---|
| id | 0 | 1 | 4426.92 | 2306.71 | 1 | 2478.75 | 4473.5 | 6413.25 | 8316 | ▅▇▇▇▇ |
| piso | 0 | 1 | 3.89 | 2.67 | 1 | 2.00 | 3.0 | 5.00 | 12 | ▇▃▁▁▁ |
| estrato | 0 | 1 | 4.84 | 0.93 | 3 | 4.00 | 5.0 | 6.00 | 6 | ▂▅▁▇▆ |
| preciom | 0 | 1 | 457.19 | 325.62 | 58 | 244.50 | 350.0 | 560.00 | 1999 | ▇▃▁▁▁ |
| areaconst | 0 | 1 | 174.76 | 138.29 | 40 | 85.00 | 123.0 | 225.00 | 1500 | ▇▁▁▁▁ |
| parqueaderos | 0 | 1 | 1.82 | 1.10 | 1 | 1.00 | 2.0 | 2.00 | 10 | ▇▁▁▁▁ |
| banios | 0 | 1 | 3.22 | 1.35 | 0 | 2.00 | 3.0 | 4.00 | 10 | ▇▇▃▁▁ |
| habitaciones | 0 | 1 | 3.56 | 1.33 | 0 | 3.00 | 3.0 | 4.00 | 10 | ▁▇▁▁▁ |
Como se puede observar, ya no quedan n_missing
Se realizara la codificacion de variables categoricas para poder realizar analisis de correlacion e implmentacion de modelos de ser requeridas
#Convertir variables categoricas a factores
df$zona_num <- as.numeric(as.factor(df$zona))
df$tipo_num <- as.numeric(as.factor(df$tipo))
df$barrio_num <- as.numeric(as.factor(df$barrio))
skim(df)
| Name | df |
| Number of rows | 4808 |
| Number of columns | 14 |
| _______________________ | |
| Column type frequency: | |
| character | 3 |
| numeric | 11 |
| ________________________ | |
| Group variables | None |
Variable type: character
| skim_variable | n_missing | complete_rate | min | max | empty | n_unique | whitespace |
|---|---|---|---|---|---|---|---|
| zona | 0 | 1 | 8 | 12 | 0 | 5 | 0 |
| tipo | 0 | 1 | 4 | 11 | 0 | 2 | 0 |
| barrio | 0 | 1 | 4 | 29 | 0 | 244 | 0 |
Variable type: numeric
| skim_variable | n_missing | complete_rate | mean | sd | p0 | p25 | p50 | p75 | p100 | hist |
|---|---|---|---|---|---|---|---|---|---|---|
| id | 0 | 1 | 4426.92 | 2306.71 | 1 | 2478.75 | 4473.5 | 6413.25 | 8316 | ▅▇▇▇▇ |
| piso | 0 | 1 | 3.89 | 2.67 | 1 | 2.00 | 3.0 | 5.00 | 12 | ▇▃▁▁▁ |
| estrato | 0 | 1 | 4.84 | 0.93 | 3 | 4.00 | 5.0 | 6.00 | 6 | ▂▅▁▇▆ |
| preciom | 0 | 1 | 457.19 | 325.62 | 58 | 244.50 | 350.0 | 560.00 | 1999 | ▇▃▁▁▁ |
| areaconst | 0 | 1 | 174.76 | 138.29 | 40 | 85.00 | 123.0 | 225.00 | 1500 | ▇▁▁▁▁ |
| parqueaderos | 0 | 1 | 1.82 | 1.10 | 1 | 1.00 | 2.0 | 2.00 | 10 | ▇▁▁▁▁ |
| banios | 0 | 1 | 3.22 | 1.35 | 0 | 2.00 | 3.0 | 4.00 | 10 | ▇▇▃▁▁ |
| habitaciones | 0 | 1 | 3.56 | 1.33 | 0 | 3.00 | 3.0 | 4.00 | 10 | ▁▇▁▁▁ |
| zona_num | 0 | 1 | 4.08 | 1.26 | 1 | 3.00 | 5.0 | 5.00 | 5 | ▁▂▂▁▇ |
| tipo_num | 0 | 1 | 1.34 | 0.47 | 1 | 1.00 | 1.0 | 2.00 | 2 | ▇▁▁▁▅ |
| barrio_num | 0 | 1 | 136.04 | 69.49 | 1 | 73.00 | 139.0 | 205.00 | 244 | ▂▇▅▆▇ |
Teniendo en cuenta que tenemos variables categoricas (aunque ya las hayamos codificado a factores numericos) y variables numericas, usaremos una correlacion de Spearman
cormat = round(cor(df %>% select(-zona, -tipo, -barrio, -id), method = "spearman"),2)
melted_cormat = melt(cormat)
# Get upper triangle of the correlation matrix
get_upper_tri <- function(cormat){
cormat[lower.tri(cormat)]<- NA
return(cormat)
}
reorder_cormat <- function(cormat){
# Use correlation between variables as distance
dd <- as.dist((1-cormat)/2)
hc <- hclust(dd)
cormat <-cormat[hc$order, hc$order]
}
# Reorder the correlation matrix
cormat <- reorder_cormat(cormat)
upper_tri <- get_upper_tri(cormat)
# Melt the correlation matrix
melted_cormat <- melt(upper_tri, na.rm = TRUE)
# Create a ggheatmap
ggheatmap <- ggplot(melted_cormat, aes(Var2, Var1, fill = value))+
geom_tile(color = "white")+
scale_fill_gradient2(low = "blue", high = "red", mid = "white",
midpoint = 0, limit = c(-1,1), space = "Lab",
name="Spearman\nCorrelation") +
theme_minimal()+ # minimal theme
theme(axis.text.x = element_text(angle = 45, vjust = 1,
size = 12, hjust = 1))+
coord_fixed()
ggheatmap +
geom_text(aes(Var2, Var1, label = value), color = "black", size = 3) +
theme(
axis.title.x = element_blank(),
axis.title.y = element_blank(),
panel.grid.major = element_blank(),
panel.border = element_blank(),
panel.background = element_blank(),
axis.ticks = element_blank(),
legend.justification = c(1, 0),
legend.position = c(0.6, 0.7),
legend.direction = "horizontal")+
guides(fill = guide_colorbar(barwidth = 7, barheight = 1,
title.position = "top", title.hjust = 0.5))
Se puede observar una fuerte correlacion entre el area construida y varias variables como habitaciones (66%), tipo (63%), banios (79%) y preciom (83%) lo que nos dice que el area construida puede ser un indicador del precio y otras caracteristicas de la vivienda.
Para poder realizar Principal Component Analysis (PCA), hay unas consideraciones que se deben tener en cuenta antes de iniciar.
En el paso de limpieza del df eliminamos columnas no relevantes para el dataset, pero para PCA en particular, debemos eliminar algunas columnas extra. Esto se hace basados en el hecho de que PCA se realiza con variables cuantitativas, a pesar de que convertimos nuestras variables categoricas a numericas a traves de la funcion factor, es necesario tener en cuenta la naturaleza ordinal del label encoding, por lo que se remueven las variables categoricas.
Adicionalmente se remueve la variable piso debido a la poca correlacion que muestra en el heatmap
df_pca = df %>% select(-barrio_num, -zona_num, -tipo_num, -barrio, -zona, -tipo, -piso, -id)
str(df_pca)
## tibble [4,808 × 6] (S3: tbl_df/tbl/data.frame)
## $ estrato : num [1:4808] 6 4 6 6 6 5 4 6 5 5 ...
## $ preciom : num [1:4808] 880 1200 1300 513 870 310 240 690 220 200 ...
## $ areaconst : num [1:4808] 237 800 600 160 490 82.5 80 150 92 71 ...
## $ parqueaderos: num [1:4808] 2 3 4 2 3 1 1 2 2 1 ...
## $ banios : num [1:4808] 5 6 7 4 6 2 2 5 3 2 ...
## $ habitaciones: num [1:4808] 4 7 5 4 5 3 3 4 3 3 ...
## - attr(*, "na.action")= 'omit' Named int [1:3514] 3 4 12 13 15 16 17 21 22 29 ...
## ..- attr(*, "names")= chr [1:3514] "3" "4" "12" "13" ...
md.pattern(df_pca, rotate.names = TRUE)
str(df_pca)
## tibble [4,808 × 6] (S3: tbl_df/tbl/data.frame)
## $ estrato : num [1:4808] 6 4 6 6 6 5 4 6 5 5 ...
## $ preciom : num [1:4808] 880 1200 1300 513 870 310 240 690 220 200 ...
## $ areaconst : num [1:4808] 237 800 600 160 490 82.5 80 150 92 71 ...
## $ parqueaderos: num [1:4808] 2 3 4 2 3 1 1 2 2 1 ...
## $ banios : num [1:4808] 5 6 7 4 6 2 2 5 3 2 ...
## $ habitaciones: num [1:4808] 4 7 5 4 5 3 3 4 3 3 ...
## - attr(*, "na.action")= 'omit' Named int [1:3514] 3 4 12 13 15 16 17 21 22 29 ...
## ..- attr(*, "names")= chr [1:3514] "3" "4" "12" "13" ...
df_scaled = scale(df_pca)
head(round(df_scaled,2))
## estrato preciom areaconst parqueaderos banios habitaciones
## [1,] 1.25 1.30 0.45 0.17 1.32 0.33
## [2,] -0.90 2.28 4.52 1.08 2.06 2.58
## [3,] 1.25 2.59 3.07 1.99 2.80 1.08
## [4,] 1.25 0.17 -0.11 0.17 0.58 0.33
## [5,] 1.25 1.27 2.28 1.08 2.06 1.08
## [6,] 0.17 -0.45 -0.67 -0.74 -0.90 -0.42
res_pca = prcomp(df_pca, scale. = TRUE)
summary(res_pca)
## Importance of components:
## PC1 PC2 PC3 PC4 PC5 PC6
## Standard deviation 1.8866 1.0884 0.69530 0.58528 0.49647 0.42886
## Proportion of Variance 0.5932 0.1974 0.08057 0.05709 0.04108 0.03065
## Cumulative Proportion 0.5932 0.7906 0.87117 0.92827 0.96935 1.00000
fviz_eig(res_pca, addlabels = TRUE)
cumulative_variance <- (cumsum(res_pca$sdev^2 / sum(res_pca$sdev^2))*100)
# Plot cumulative variance
plot(cumulative_variance, type = "b", xlab = "Componentes Principales", ylab = "Variabilidad acumulada (%)", ylim = c(0, 100), main = "Variabilidad acumulada")
text(seq_along(cumulative_variance), cumulative_variance, labels = sprintf("%.2f%%", cumulative_variance), pos = 3, cex = 0.7)
En este caso, el primer compenente principal explica el 59.32% de la variabilidad contenida en la base de datos, observando la variabilidad explicada por los componentes, nos damos cuenta que para poder explicar al menos un 80% de la variabilidad, necesitamos conservar 2 componentes principales, que equivale a un 79%
res_pca$rotation[, 1:2]
## PC1 PC2
## estrato -0.2946513 -0.6488133
## preciom -0.4676270 -0.2332802
## areaconst -0.4495212 0.2109552
## parqueaderos -0.4287458 -0.1463697
## banios -0.4639679 0.1419484
## habitaciones -0.3055275 0.6622281
fviz_pca_var(res_pca,
col.var = "contrib", # Color by contributions to the PC
gradient.cols = c("red","blue"),
repel = TRUE, # Avoid text overlapping
axes = c(1,2)
)
El componente 1, esta asociado principalment con las variables area construida y baños.
El componente 2 se asocia con el numero de habitaciones.
Al igual que con el PCA, necesitamos validar ciertos requerimientos antes de realizar el analisis de conglomerados.
Para poder utilizar distancias euclidianas, y clustering jerarquico, necesitamos garantizar que los valores a usar sean de caracter numerico, por lo que eliminaremos las variables categoricas, adicionalmente conservaremos solo las variables relevantes para el analisis
df_clus = df %>% select(-barrio_num, -zona_num, -tipo_num, -barrio, -zona, -tipo, -piso, -id)
str(df_clus)
## tibble [4,808 × 6] (S3: tbl_df/tbl/data.frame)
## $ estrato : num [1:4808] 6 4 6 6 6 5 4 6 5 5 ...
## $ preciom : num [1:4808] 880 1200 1300 513 870 310 240 690 220 200 ...
## $ areaconst : num [1:4808] 237 800 600 160 490 82.5 80 150 92 71 ...
## $ parqueaderos: num [1:4808] 2 3 4 2 3 1 1 2 2 1 ...
## $ banios : num [1:4808] 5 6 7 4 6 2 2 5 3 2 ...
## $ habitaciones: num [1:4808] 4 7 5 4 5 3 3 4 3 3 ...
## - attr(*, "na.action")= 'omit' Named int [1:3514] 3 4 12 13 15 16 17 21 22 29 ...
## ..- attr(*, "names")= chr [1:3514] "3" "4" "12" "13" ...
md.pattern(df_clus, rotate.names = TRUE)
str(df_clus)
## tibble [4,808 × 6] (S3: tbl_df/tbl/data.frame)
## $ estrato : num [1:4808] 6 4 6 6 6 5 4 6 5 5 ...
## $ preciom : num [1:4808] 880 1200 1300 513 870 310 240 690 220 200 ...
## $ areaconst : num [1:4808] 237 800 600 160 490 82.5 80 150 92 71 ...
## $ parqueaderos: num [1:4808] 2 3 4 2 3 1 1 2 2 1 ...
## $ banios : num [1:4808] 5 6 7 4 6 2 2 5 3 2 ...
## $ habitaciones: num [1:4808] 4 7 5 4 5 3 3 4 3 3 ...
## - attr(*, "na.action")= 'omit' Named int [1:3514] 3 4 12 13 15 16 17 21 22 29 ...
## ..- attr(*, "names")= chr [1:3514] "3" "4" "12" "13" ...
head(df_clus)
## # A tibble: 6 × 6
## estrato preciom areaconst parqueaderos banios habitaciones
## <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 6 880 237 2 5 4
## 2 4 1200 800 3 6 7
## 3 6 1300 600 4 7 5
## 4 6 513 160 2 4 4
## 5 6 870 490 3 6 5
## 6 5 310 82.5 1 2 3
df_clus_og = df_clus # Copia del dataset para graficacion en la etapa de analisis
df_clus = scale(df_clus)
df_clus = as.data.frame(df_clus)
head(df_clus)
## estrato preciom areaconst parqueaderos banios habitaciones
## 1 1.2544421 1.2984933 0.4500474 0.1682046 1.3178809 0.3272519
## 2 -0.9046263 2.2812429 4.5210796 1.0779092 2.0579382 2.5814453
## 3 1.2544421 2.5883521 3.0748870 1.9876139 2.7979956 1.0786497
## 4 1.2544421 0.1714025 -0.1067367 0.1682046 0.5778235 0.3272519
## 5 1.2544421 1.2677824 2.2794811 1.0779092 2.0579382 1.0786497
## 6 0.1749079 -0.4520293 -0.6671364 -0.7415001 -0.9022913 -0.4241459
Inicialmente se calculan las distancias para los valores estandarizados. Debido a la cantidad de registros, un dendograma no resulta muy util, puesto que seria imposible de leer, por esto, se utilizara el metodo del codo y el indice de Silhouette promedio para determinar el numero optimo de clusters
dis_df_clus = dist(df_clus, method = "euclidean")
# Metodo del codo
fviz_nbclust(df_clus, # data
hcut, # clustering algorithm
iter.max = 200, # the maximum number of iterations allowed.
method = "wss") # elbow method
El metodo del codo solo muestra una caida abrupta del primer a segundo cluster, pero de ahi en adelante es relativamente continue, por lo que queda como un ejercicio de interpretacion abierta si se deberia incluir el tercer cluster, para tratar de ganar mas contexto, se utiliza el coeficiente de Silhouette promedio para identificar el numero optimo de clusters
# Indice de Silhouette promedio
fviz_nbclust(df_clus, # data
hcut, # clustering algorithm
method = "silhouette") # silhouette
El resultado es ahora mucho mas evidente, el numero optimo de clusters para el dataset es 2.
# Cluster jerarquico con el método complete
hc_df <- hclust(dis_df_clus, method = 'complete')
# Determinamos a dónde pertenece cada observación
cluster_assigments <- cutree(hc_df, k = 2)
# asignamos los clusters
assigned_cluster <- df_clus %>% mutate(cluster = as.factor(cluster_assigments))
head(assigned_cluster)
## estrato preciom areaconst parqueaderos banios habitaciones cluster
## 1 1.2544421 1.2984933 0.4500474 0.1682046 1.3178809 0.3272519 1
## 2 -0.9046263 2.2812429 4.5210796 1.0779092 2.0579382 2.5814453 1
## 3 1.2544421 2.5883521 3.0748870 1.9876139 2.7979956 1.0786497 1
## 4 1.2544421 0.1714025 -0.1067367 0.1682046 0.5778235 0.3272519 1
## 5 1.2544421 1.2677824 2.2794811 1.0779092 2.0579382 1.0786497 1
## 6 0.1749079 -0.4520293 -0.6671364 -0.7415001 -0.9022913 -0.4241459 1
cluster_results = assigned_cluster %>% group_by(cluster) #Agrupacion por clusters
#Creacion de tabla de medias
resultados <- cluster_results %>%
summarize(
Media_estrato = mean(estrato),
Media_preciom = mean(preciom),
Media_areaconst = mean(areaconst),
Media_parqueaderos = mean(parqueaderos),
Media_banios = mean(banios),
Media_habitaciones = mean(habitaciones))
# Muestra los resultados
print(resultados)
## # A tibble: 2 × 7
## cluster Media_estrato Media_preciom Media_areaconst Media_parqueaderos
## <fct> <dbl> <dbl> <dbl> <dbl>
## 1 1 -0.00656 -0.0199 -0.0414 -0.0217
## 2 2 0.846 2.57 5.34 2.80
## # ℹ 2 more variables: Media_banios <dbl>, Media_habitaciones <dbl>
# gráfico de puntos
ggplot(assigned_cluster, aes(x = preciom, y = areaconst, color = cluster)) +
geom_point(size = 4) +
geom_text(aes(label = cluster), vjust = -.8) + # Agregar etiquetas del clúster
theme_classic()
Como podemos ver se crean dos clusters que pueden describir dos tipos de viviendas, Viviendas Tipo 1 caracterizadas por ser de menor area y mayoritariamente tener un menor precio, y Viviendas Tipo 2, viviendas con mayor area y precios premium, pero es dificil atribuir numeros a este resultado, puesto que los valores estan escalados. Por esto, aprovechando que el orden de los registros no se altera, vamos a traer el cluster asignado a nuestro dataset original para poder repetir la grafica con valores en las unidades originales
#Se agrega el cluster al dataset original respetando el orden de los registros
df_clus_og_ = cbind(df_clus_og, Cluster = assigned_cluster$cluster)
head(df_clus_og_)
## estrato preciom areaconst parqueaderos banios habitaciones Cluster
## 1 6 880 237.0 2 5 4 1
## 2 4 1200 800.0 3 6 7 1
## 3 6 1300 600.0 4 7 5 1
## 4 6 513 160.0 2 4 4 1
## 5 6 870 490.0 3 6 5 1
## 6 5 310 82.5 1 2 3 1
clus_og = df_clus_og_ %>% group_by(Cluster)
resultados_og <- clus_og %>%
summarize(
Media_estrato = mean(estrato),
Media_preciom = mean(preciom),
Media_areaconst = mean(areaconst),
Media_parqueaderos = mean(parqueaderos),
Media_banios = mean(banios),
Media_habitaciones = mean(habitaciones))
# Muestra los resultados
print(resultados_og)
## # A tibble: 2 × 7
## Cluster Media_estrato Media_preciom Media_areaconst Media_parqueaderos
## <fct> <dbl> <dbl> <dbl> <dbl>
## 1 1 4.83 451. 169. 1.79
## 2 2 5.62 1292. 914. 4.89
## # ℹ 2 more variables: Media_banios <dbl>, Media_habitaciones <dbl>
# gráfico de puntos
ggplot(df_clus_og_, aes(x = preciom, y = areaconst, color = Cluster)) +
geom_point(size = 4) +
geom_text(aes(label = Cluster), vjust = -.8) + # Agregar etiquetas del clúster
theme_classic()
Viendo los resultados con las unidades originales, podemos ver que el cluster 2, lo que denominamos Viviendas Tipo 2, esta compuesto por viviendas con un area construida promedio de 900m, vs los 170m de las Viviendas Tipo 1 y un precio casi triplicado, lo que podria identificarlas como casas de campo, haciendas, o salones de eventos.
Para el analisis de correspondencia se utilizan las variables categoricas del dataset, pero dentro las categoricas, la variable barrio tiene demasiados resultados (386), por lo que vamos a omitirla y a usar la variable estrato como variable categorica ordinal
corresp = df %>% select(zona, tipo, estrato)
head(corresp)
## # A tibble: 6 × 3
## zona tipo estrato
## <chr> <chr> <dbl>
## 1 zona sur casa 6
## 2 zona oeste casa 4
## 3 zona sur casa 6
## 4 zona sur casa 6
## 5 zona sur casa 6
## 6 zona sur apartamento 5
Para poder realizar analisis de correspondencia necesitamos garantizar que las variables sean categoricas, por lo que cambiaremos el tipo de la variable estrato a factor
corresp$estrato = as.factor(corresp$estrato)
str(corresp)
## tibble [4,808 × 3] (S3: tbl_df/tbl/data.frame)
## $ zona : chr [1:4808] "zona sur" "zona oeste" "zona sur" "zona sur" ...
## $ tipo : chr [1:4808] "casa" "casa" "casa" "casa" ...
## $ estrato: Factor w/ 4 levels "3","4","5","6": 4 2 4 4 4 3 2 4 3 3 ...
## - attr(*, "na.action")= 'omit' Named int [1:3514] 3 4 12 13 15 16 17 21 22 29 ...
## ..- attr(*, "names")= chr [1:3514] "3" "4" "12" "13" ...
Adicional a esto verificaremos que no hay valores faltantes
md.pattern(corresp, rotate.names = TRUE)
Realizamos una tabla cruzada con los valores de estrato y zona
tabla <- table(corresp$zona, corresp$estrato)
tabla
##
## 3 4 5 6
## zona centro 33 3 0 0
## zona norte 141 184 482 79
## zona oeste 19 51 181 502
## zona oriente 94 2 1 0
## zona sur 147 973 1195 721
chisq.test(tabla)
## Warning in chisq.test(tabla): Chi-squared approximation may be incorrect
##
## Pearson's Chi-squared test
##
## data: tabla
## X-squared = 2172.8, df = 12, p-value < 2.2e-16
El resultado indica que se rechaza la hipótesis de independencia de las variables (p-value: 0.0000), indicando grado tipo de relación entre ellas.
Vamos a realizar el analisis de correspondencia que consiste en estimar las coordenadas para cada uno de los niveles de ambas variables
res.ca <- CA(tabla)
El grafico nos muestra las siguientes relaciones:
Ahora vamos a proceder a verificar las dimensiones y su porcentaje de varianza explicado
valores_prop <- res.ca$eig
valores_prop
## eigenvalue percentage of variance cumulative percentage of variance
## dim 1 0.29526876 65.338252 65.33825
## dim 2 0.13919088 30.800716 96.13897
## dim 3 0.01744831 3.861032 100.00000
fviz_screeplot(res.ca, addlabels = TRUE, ylim = c(0, 80))+ggtitle("")+
ylab("Porcentaje de varianza explicado") + xlab("Ejes")
Podemos observar que solo con dos dimensiones explicamos el 97.6% de la variabilidad acumulada