#Introduccion
Para el trabajo de Open Data he elegido estos datos de Kaggle sobre el precio de la vivienda. Dejo el link abajo al código original.
He elegido estos datos por una situación familiar en la que estamos buscando comprarnos una casa. Puesto que es prioridad para mi este tema en estos momentos me ha parecido curioso.
Contestar a las preguntas de tipo: ¿Cuáles son las variables que se tienen en cuenta para el precio? ¿Cuáles son más importantes o tienen más peso?
##Cargar las librerias necesarias en R
library(knitr)
library(ggplot2)
library(plyr)
library(dplyr)
library(corrplot)
library(caret)
library(gridExtra)
library(scales)
library(Rmisc)
library(ggrepel)
library(randomForest)
library(psych)
library(xgboost)
Leemos los datos tanto de test como de entrenamiento.
train <- read.csv("train.csv", stringsAsFactors = F)
test <- read.csv("test.csv", stringsAsFactors = F)
##Tamaño de los datos y estructura.
El conjunto de datos del tren consiste en variables de caracteres y enteras. La mayoría de las variables de caracteres son en realidad factores (ordinales), pero he optado por leerlas en R como cadenas de caracteres, ya que la mayoría de ellas requieren una limpieza y/o una ingeniería de características primero. En total, hay 81 columnas/variables, de las cuales la última es la variable de respuesta (SalePrice). A continuación, sólo muestro una parte de las variables. Todas ellas se comentan con más detalle a lo largo del documento.
dim(train)
## [1] 1460 81
str(train[,c(1:10, 81)]) #mostrar las 10 primeras variables y la variable de respuesta
## 'data.frame': 1460 obs. of 11 variables:
## $ Id : int 1 2 3 4 5 6 7 8 9 10 ...
## $ MSSubClass : int 60 20 60 70 60 50 20 60 50 190 ...
## $ MSZoning : chr "RL" "RL" "RL" "RL" ...
## $ LotFrontage: int 65 80 68 60 84 85 75 NA 51 50 ...
## $ LotArea : int 8450 9600 11250 9550 14260 14115 10084 10382 6120 7420 ...
## $ Street : chr "Pave" "Pave" "Pave" "Pave" ...
## $ Alley : chr NA NA NA NA ...
## $ LotShape : chr "Reg" "Reg" "IR1" "IR1" ...
## $ LandContour: chr "Lvl" "Lvl" "Lvl" "Lvl" ...
## $ Utilities : chr "AllPub" "AllPub" "AllPub" "AllPub" ...
## $ SalePrice : int 208500 181500 223500 140000 250000 143000 307000 200000 129900 118000 ...
#Deshacerse de los IDs pero mantener los IDs de las pruebas en un vector. Estos son necesarios para componer el archivo de presentación
test_labels <- test$Id
test$Id <- NULL
train$Id <- NULL
test$SalePrice <- NA
all <- rbind(train, test)
dim(all)
## [1] 2919 80
Sin los IDs el dataset tiene 79 predictores y una sola variable de salida, el precio de la vivienda.
#Explorando las más importante
##La variable respuesta; SalePrice
Como se puede ver, los precios de venta están muy sesgados. Esto era de esperar, ya que pocas personas pueden permitirse casas muy caras. Esto, el autor, lo toma en cuenta a la hora de modelar.
ggplot(data=all[!is.na(all$SalePrice),], aes(x=SalePrice)) +
geom_histogram(fill="blue", binwidth = 10000) +
scale_x_continuous(breaks= seq(0, 800000, by=100000), labels = comma)
summary(all$SalePrice)
## Min. 1st Qu. Median Mean 3rd Qu. Max. NA's
## 34900 129975 163000 180921 214000 755000 1459
##Los predictores numéricos más importantes
Las variables de carácter necesitan algo de trabajo antes de que pueda utilizarlas. Para hacerme una idea del conjunto de datos, decidí ver primero qué variables numéricas tienen una alta correlación con el Precio de venta.
###Correlación con SalePrice
En total, hay 10 variables numéricas con una correlación de al menos 0,5 con SalePrice. Todas esas correlaciones son positivas.
numericVars <- which(sapply(all, is.numeric)) #index vector numeric variables
numericVarNames <- names(numericVars) #saving names vector for use later on
cat('There are', length(numericVars), 'numeric variables')
## There are 37 numeric variables
all_numVar <- all[, numericVars]
cor_numVar <- cor(all_numVar, use="pairwise.complete.obs") #correlations of all numeric variables
#sort on decreasing correlations with SalePrice
cor_sorted <- as.matrix(sort(cor_numVar[,'SalePrice'], decreasing = TRUE))
#select only high corelations
CorHigh <- names(which(apply(cor_sorted, 1, function(x) abs(x)>0.5)))
cor_numVar <- cor_numVar[CorHigh, CorHigh]
corrplot.mixed(cor_numVar, tl.col="black", tl.pos = "lt")
En el resto de esta sección, visualizaré la relación entre el Precio de Venta y los dos predictores con la mayor correlación con el Precio de Venta; la Calidad General y la Superficie Habitable “Por Encima del Grado” (esta es la proporción de la casa que no está en un sótano; enlace).
También queda claro que la multicolinealidad es un problema. Por ejemplo: la correlación entre GarageCars y GarageArea es muy alta (0,89), y ambas tienen correlaciones similares (altas) con SalePrice. Las otras seis variables con una correlación superior a 0,5 con Precio de venta son: -TotalBsmtSF: Total de pies cuadrados de superficie de sótano -1stFlrSF: Pies cuadrados de la primera planta -FullBath: Baños completos por encima del grado -TotRmsAbvGrd: Total de habitaciones sobre el grado (no incluye los baños) -YearBuilt: Fecha de construcción original -YearRemodAdd: Fecha de remodelación (la misma que la fecha de construcción si no hay remodelaciones o adiciones)
###Calidad general
La calidad general tiene la mayor correlación con el precio de venta entre las variables numéricas (0,79). Califica el material y el acabado general de la casa en una escala de 1 (muy pobre) a 10 (muy excelente).
ggplot(data=all[!is.na(all$SalePrice),], aes(x=factor(OverallQual), y=SalePrice))+
geom_boxplot(col='blue') + labs(x='Overall Quality') +
scale_y_continuous(breaks= seq(0, 800000, by=100000), labels = comma)
La correlación positiva está ciertamente ahí, y parece ser una curva ligeramente ascendente. En cuanto a los valores atípicos, no veo ningún valor extremo. Si hay un candidato para sacar como valor atípico más adelante, parece ser la casa cara con grado 4.
###Above Grade (Ground) Living Area (square feet)
La variable numérica con la segunda mayor correlación con el precio de venta es Above Grade Living Area. Esto tiene mucho sentido; las casas grandes suelen ser más caras.
ggplot(data=all[!is.na(all$SalePrice),], aes(x=GrLivArea, y=SalePrice))+
geom_point(col='blue') + geom_smooth(method = "lm", se=FALSE, color="black", aes(group=1)) +
scale_y_continuous(breaks= seq(0, 800000, by=100000), labels = comma) +
geom_text_repel(aes(label = ifelse(all$GrLivArea[!is.na(all$SalePrice)]>4500, rownames(all), '')))
## `geom_smooth()` using formula 'y ~ x'
Especialmente las dos casas con grandes superficies y bajos precios de venta parecen valores atípicos (casas 524 y 1299, ver etiquetas en el gráfico). No los eliminaré todavía, ya que tomar valores atípicos puede ser peligroso. Por ejemplo, una puntuación baja en la calidad general podría explicar un precio bajo. Sin embargo, como se puede ver a continuación, estas dos casas también obtienen la máxima puntuación en la calidad general. Por lo tanto, tendré en cuenta las casas 1299 y 524 como principales candidatas a ser eliminadas como valores atípicos.
all[c(524, 1299), c('SalePrice', 'GrLivArea', 'OverallQual')]
## SalePrice GrLivArea OverallQual
## 524 184750 4676 10
## 1299 160000 5642 10
#Datos perdidos, codificación de etiquetas y factorización de variables
##Integridad de los datos
En primer lugar, me gustaría ver qué variables contienen valores perdidos.
NAcol <- which(colSums(is.na(all)) > 0)
sort(colSums(sapply(all[NAcol], is.na)), decreasing = TRUE)
## PoolQC MiscFeature Alley Fence SalePrice FireplaceQu
## 2909 2814 2721 2348 1459 1420
## LotFrontage GarageYrBlt GarageFinish GarageQual GarageCond GarageType
## 486 159 159 159 159 157
## BsmtCond BsmtExposure BsmtQual BsmtFinType2 BsmtFinType1 MasVnrType
## 82 82 81 80 79 24
## MasVnrArea MSZoning Utilities BsmtFullBath BsmtHalfBath Functional
## 23 4 2 2 2 2
## Exterior1st Exterior2nd BsmtFinSF1 BsmtFinSF2 BsmtUnfSF TotalBsmtSF
## 1 1 1 1 1 1
## Electrical KitchenQual GarageCars GarageArea SaleType
## 1 1 1 1 1
cat('There are', length(NAcol), 'columns with missing values')
## There are 35 columns with missing values
Por supuesto, los 1459 NAs en SalePrice coinciden perfectamente con el tamaño del conjunto de pruebas. Esto significa que tengo que arreglar los NAs en 34 variables predictoras.
##Imputar los datos que faltan {.tabset}
En esta sección, voy a corregir los 34 predictores que contienen valores perdidos. Iré a través de ellos trabajando hacia abajo desde la mayoría de los NA hasta que los haya arreglado todos. Si me encuentro con una variable que realmente forma un grupo con otras variables, también las trataré como un grupo. Por ejemplo, hay varias variables relacionadas con Piscina, Garaje y Sótano.
Como quiero mantener el documento lo más legible posible, he decidido utilizar la opción de “Tabs” que proporciona knitr. Puedes encontrar un breve análisis para cada (grupo de) variables bajo cada pestaña. No es necesario recorrer todas las secciones, y también se puede echar un vistazo a unas pocas pestañas. Si lo haces, creo que especialmente las secciones de Garaje y Sótano merecen la pena, ya que he sido cuidadoso a la hora de determinar qué casas realmente no tienen sótano o garaje.
Además de asegurarme de que se eliminan las NA, también he convertido las variables de carácter en enteros ordinales si hay una clara ordinalidad, o en factores si los niveles son categorías sin ordinalidad. Convertiré estos factores en numéricos más adelante utilizando one-hot encoding (usando la función model.matrix).
###Variables de la piscina
Calidad de la piscina y la variable PoolArea
El PoolQC es la variable con más NAs. La descripción es la siguiente:
PoolQC: Pool quality
Ex Excellent
Gd Good
TA Average/Typical
Fa Fair
NA No Pool
Por lo tanto, es obvio que tengo que asignar simplemente “No pool” a las NA. Además, el elevado número de NA tiene sentido, ya que normalmente sólo una pequeña proporción de casas tiene piscina.
all$PoolQC[is.na(all$PoolQC)] <- 'None'
También está claro que puedo etiquetar esta variable ya que los valores son ordinales. Como hay múltiples variables que utilizan los mismos niveles de calidad, voy a crear un vector que puedo reutilizar más adelante.
Qualities <- c('None' = 0, 'Po' = 1, 'Fa' = 2, 'TA' = 3, 'Gd' = 4, 'Ex' = 5)
all$PoolQC<-as.integer(revalue(all$PoolQC, Qualities))
table(all$PoolQC)
##
## 0 2 4 5
## 2909 2 4 4
Sin embargo, hay una segunda variable que se relaciona con las Piscinas. Se trata de la variable PoolArea (en pies cuadrados). Como puede ver a continuación, hay 3 casas sin PoolQC. En primer lugar, comprobé si había una relación clara entre PoolArea y PoolQC. Como no vi una relación clara (piscinas más grandes o más pequeñas con mejor PoolQC), voy a imputar los valores de PoolQC basándome en la Calidad General de las casas (que no es muy alta para esas 3 casas).
all[all$PoolArea>0 & all$PoolQC==0, c('PoolArea', 'PoolQC', 'OverallQual')]
## PoolArea PoolQC OverallQual
## 2421 368 0 4
## 2504 444 0 6
## 2600 561 0 3
all$PoolQC[2421] <- 2
all$PoolQC[2504] <- 3
all$PoolQC[2600] <- 2
###Miscellaneous Feature
Miscellaneous feature no incluídos en otras categorías
Con Miscellaneous Feature, hay 2814 NAs. Como los valores no son ordinales, convertiré MiscFeature en un factor. Valores:
Elev Elevator
Gar2 2nd Garage (if not described in garage section)
Othr Other
Shed Shed (over 100 SF)
TenC Tennis Court
NA None
all$MiscFeature[is.na(all$MiscFeature)] <- 'None'
all$MiscFeature <- as.factor(all$MiscFeature)
ggplot(all[!is.na(all$SalePrice),], aes(x=MiscFeature, y=SalePrice)) +
geom_bar(stat='summary', fun.y = "median", fill='blue') +
scale_y_continuous(breaks= seq(0, 800000, by=100000), labels = comma) +
geom_label(stat = "count", aes(label = ..count.., y = ..count..))
## Warning: Ignoring unknown parameters: fun.y
## No summary function supplied, defaulting to `mean_se()`
table(all$MiscFeature)
##
## Gar2 None Othr Shed TenC
## 5 2814 4 95 1
Al observar las frecuencias, la variable me parece irrelevante. Tener un cobertizo probablemente signifique “no tener garaje”, lo que explicaría el menor precio de venta del cobertizo. Además, aunque tiene mucho sentido que una casa con pista de tenis sea cara, sólo hay una casa con pista de tenis en el conjunto de entrenamiento.
###Callejón
Tipo de callejón de acceso a la propiedad
Con Alley, hay 2721 NAs. Como los valores no son ordinales, convertiré a Alley en un factor. Valores:
Grvl Gravel
Pave Paved
NA No alley access
all$Alley[is.na(all$Alley)] <- 'None'
all$Alley <- as.factor(all$Alley)
ggplot(all[!is.na(all$SalePrice),], aes(x=Alley, y=SalePrice)) +
geom_bar(stat='summary', fun.y = "median", fill='blue')+
scale_y_continuous(breaks= seq(0, 200000, by=50000), labels = comma)
## Warning: Ignoring unknown parameters: fun.y
## No summary function supplied, defaulting to `mean_se()`
table(all$Alley)
##
## Grvl None Pave
## 120 2721 78
###Valla
Calidad de la valla
Con Fence, hay 2348 NAs. Los valores parecen ser ordinales. Valores:
GdPrv Good Privacy
MnPrv Minimum Privacy
GdWo Good Wood
MnWw Minimum Wood/Wire
NA No Fence
all$Fence[is.na(all$Fence)] <- 'None'
table(all$Fence)
##
## GdPrv GdWo MnPrv MnWw None
## 118 112 329 12 2348
all[!is.na(all$SalePrice),] %>% group_by(Fence) %>% summarise(median = median(SalePrice), counts=n())
## # A tibble: 5 x 3
## Fence median counts
## <chr> <dbl> <int>
## 1 GdPrv 167500 59
## 2 GdWo 138750 54
## 3 MnPrv 137450 157
## 4 MnWw 130000 11
## 5 None 173000 1179
Mi conclusión es que los valores no parecen ordinales (ninguna valla es mejor). Por lo tanto, voy a convertir Fence en un factor.
all$Fence <- as.factor(all$Fence)
###Variables de la chimenea
Calidad de la chimenea, y Número de chimeneas
Dentro de la calidad de las chimeneas, hay 1420 NAs. El número de chimeneas está completo.
Fireplace quality
El número de NAs en FireplaceQu coincide con el número de casas con 0 chimeneas. Esto significa que puedo sustituir con seguridad los NAs en FireplaceQu por “sin chimenea”. Los valores son ordinales, y puedo utilizar el vector de Cualidades que ya he creado para la Cualidad Piscina. Valores:
Ex Excellent - Exceptional Masonry Fireplace
Gd Good - Masonry Fireplace in main level
TA Average - Prefabricated Fireplace in main living area or Masonry Fireplace in basement
Fa Fair - Prefabricated Fireplace in basement
Po Poor - Ben Franklin Stove
NA No Fireplace
all$FireplaceQu[is.na(all$FireplaceQu)] <- 'None'
all$FireplaceQu<-as.integer(revalue(all$FireplaceQu, Qualities))
table(all$FireplaceQu)
##
## 0 1 2 3 4 5
## 1420 46 74 592 744 43
Número de chimeneas
Chimeneas es una variable entera, y no hay valores perdidos.
table(all$Fireplaces)
##
## 0 1 2 3 4
## 1420 1268 219 11 1
sum(table(all$Fireplaces))
## [1] 2919
###Variables de la parcela
3 variables. Una con 1 NA, y 2 variables completas.
LotFrontage: Pies lineales de calle conectados a la propiedad
486 NAs. La imputación más razonable parece tomar la mediana por barrio.
ggplot(all[!is.na(all$LotFrontage),], aes(x=as.factor(Neighborhood), y=LotFrontage)) +
geom_bar(stat='summary', fun.y = "median", fill='blue') +
theme(axis.text.x = element_text(angle = 45, hjust = 1))
## Warning: Ignoring unknown parameters: fun.y
## No summary function supplied, defaulting to `mean_se()`
for (i in 1:nrow(all)){
if(is.na(all$LotFrontage[i])){
all$LotFrontage[i] <- as.integer(median(all$LotFrontage[all$Neighborhood==all$Neighborhood[i]], na.rm=TRUE))
}
}
LotShape: Forma general de la propiedad
No NAs. Los valores parecen ordinales (Regular=best)
Reg Regular
IR1 Slightly irregular
IR2 Moderately Irregular
IR3 Irregular
all$LotShape<-as.integer(revalue(all$LotShape, c('IR3'=0, 'IR2'=1, 'IR1'=2, 'Reg'=3)))
table(all$LotShape)
##
## 0 1 2 3
## 16 76 968 1859
sum(table(all$LotShape))
## [1] 2919
LotConfig: Configuración de la parcela
No NAs. The values seemed possibly ordinal to me, but the visualization does not show this. Therefore, I will convert the variable into a factor.
Inside Inside lot
Corner Corner lot
CulDSac Cul-de-sac
FR2 Frontage on 2 sides of property
FR3 Frontage on 3 sides of property
ggplot(all[!is.na(all$SalePrice),], aes(x=as.factor(LotConfig), y=SalePrice)) +
geom_bar(stat='summary', fun.y = "median", fill='blue')+
scale_y_continuous(breaks= seq(0, 800000, by=100000), labels = comma) +
geom_label(stat = "count", aes(label = ..count.., y = ..count..))
## Warning: Ignoring unknown parameters: fun.y
## No summary function supplied, defaulting to `mean_se()`
all$LotConfig <- as.factor(all$LotConfig)
table(all$LotConfig)
##
## Corner CulDSac FR2 FR3 Inside
## 511 176 85 14 2133
sum(table(all$LotConfig))
## [1] 2919
###Garage variables
En total, hay 7 variables relacionadas con los garajes
Dos de ellas tienen un NA (GarageCars y GarageArea), una tiene 157 NAs (GarageType), 4 variables tienen 159 NAs.
En primer lugar, voy a sustituir los 159 valores que faltan de GarageYrBlt: Año en que se construyó el garaje con los valores de YearBuilt (esto es similar a YearRemodAdd, que también tiene como valor por defecto YearBuilt si no hay remodelaciones o adiciones).
all$GarageYrBlt[is.na(all$GarageYrBlt)] <- all$YearBuilt[is.na(all$GarageYrBlt)]
Como las NAs significan ‘No Garage’ para las variables de carácter, ahora quiero averiguar de dónde provienen las diferencias entre la 157 NA GarageType y las otras 3 variables de carácter con 159 NAs.
#comprobar si las 157 NAs son las mismas observaciones entre las variables con 157/159 NAs
length(which(is.na(all$GarageType) & is.na(all$GarageFinish) & is.na(all$GarageCond) & is.na(all$GarageQual)))
## [1] 157
#Encontrar 2 NAs adicionales
kable(all[!is.na(all$GarageType) & is.na(all$GarageFinish), c('GarageCars', 'GarageArea', 'GarageType', 'GarageCond', 'GarageQual', 'GarageFinish')])
| GarageCars | GarageArea | GarageType | GarageCond | GarageQual | GarageFinish | |
|---|---|---|---|---|---|---|
| 2127 | 1 | 360 | Detchd | NA | NA | NA |
| 2577 | NA | NA | Detchd | NA | NA | NA |
Los 157 NAs dentro de GarageType resultan ser todos NA en GarageCondition, GarageQuality y GarageFinish también. Las diferencias se encuentran en las casas 2127 y 2577. Como se puede ver, la casa 2127 parece tener un garaje y la casa 2577 no. Por lo tanto, debería haber 158 casas sin Garaje. Para arreglar la casa 2127, imputaré los valores más comunes (modos) para GarageCond, GarageQual y GarageFinish.
#Imputing modes.
all$GarageCond[2127] <- names(sort(-table(all$GarageCond)))[1]
all$GarageQual[2127] <- names(sort(-table(all$GarageQual)))[1]
all$GarageFinish[2127] <- names(sort(-table(all$GarageFinish)))[1]
#display "fixed" house
kable(all[2127, c('GarageYrBlt', 'GarageCars', 'GarageArea', 'GarageType', 'GarageCond', 'GarageQual', 'GarageFinish')])
| GarageYrBlt | GarageCars | GarageArea | GarageType | GarageCond | GarageQual | GarageFinish | |
|---|---|---|---|---|---|---|---|
| 2127 | 1910 | 1 | 360 | Detchd | TA | TA | Unf |
GarageCars and GarageArea: Tamaño del garaje en capacidad de coches y Tamaño del garaje en cuadrados
Ambos tienen 1 NA. Como puede ver arriba, se trata de la casa 2577 para ambas variables. El problema probablemente se produjo porque el tipo de garaje de esta casa es “independiente”, mientras que todas las demás variables de garaje parecen indicar que esta casa no tiene garaje.
#fixing 3 values for house 2577
all$GarageCars[2577] <- 0
all$GarageArea[2577] <- 0
all$GarageType[2577] <- NA
#comprobar si los NAs de las variables de carácter son ahora todos 158
length(which(is.na(all$GarageType) & is.na(all$GarageFinish) & is.na(all$GarageCond) & is.na(all$GarageQual)))
## [1] 158
Ahora, las 4 variables de carácter relacionadas con el garaje tienen todas el mismo conjunto de 158 NAs, que corresponden a “Sin garaje”. Los arreglaré todos en el resto de esta sección
GarageType: Localización del garaje
Los valores no parecen ordinales, así que los convertiré en un factor.
2Types More than one type of garage
Attchd Attached to home
Basment Basement Garage
BuiltIn Built-In (Garage part of house - typically has room above garage)
CarPort Car Port
Detchd Detached from home
NA No Garage
all$GarageType[is.na(all$GarageType)] <- 'No Garage'
all$GarageType <- as.factor(all$GarageType)
table(all$GarageType)
##
## 2Types Attchd Basment BuiltIn CarPort Detchd No Garage
## 23 1723 36 186 15 778 158
GarageFinish: Acabado interior del garaje
Los valores son ordinales.
Fin Finished
RFn Rough Finished
Unf Unfinished
NA No Garage
all$GarageFinish[is.na(all$GarageFinish)] <- 'None'
Finish <- c('None'=0, 'Unf'=1, 'RFn'=2, 'Fin'=3)
all$GarageFinish<-as.integer(revalue(all$GarageFinish, Finish))
table(all$GarageFinish)
##
## 0 1 2 3
## 158 1231 811 719
GarageQual: Calidad del garaje
Otra variable que puede hacerse ordinal con el vector Cualidades.
Ex Excellent
Gd Good
TA Typical/Average
Fa Fair
Po Poor
NA No Garage
all$GarageQual[is.na(all$GarageQual)] <- 'None'
all$GarageQual<-as.integer(revalue(all$GarageQual, Qualities))
table(all$GarageQual)
##
## 0 1 2 3 4 5
## 158 5 124 2605 24 3
GarageCond: Condición del garaje
Otra variable que puede hacerse ordinal con el vector Cualidades.
Ex Excellent
Gd Good
TA Typical/Average
Fa Fair
Po Poor
NA No Garage
all$GarageCond[is.na(all$GarageCond)] <- 'None'
all$GarageCond<-as.integer(revalue(all$GarageCond, Qualities))
table(all$GarageCond)
##
## 0 1 2 3 4 5
## 158 14 74 2655 15 3
###Variables del sotano
En total, hay 11 variables relacionadas con los sótanos de una casa
Cinco de ellos tienen 79-82 NAs, seis tienen uno o dos NAs.
#comprobar si las 79 NAs son las mismas observaciones entre las variables con 80+ NAs
length(which(is.na(all$BsmtQual) & is.na(all$BsmtCond) & is.na(all$BsmtExposure) & is.na(all$BsmtFinType1) & is.na(all$BsmtFinType2)))
## [1] 79
#Find the additional NAs; BsmtFinType1 is the one with 79 NAs
all[!is.na(all$BsmtFinType1) & (is.na(all$BsmtCond)|is.na(all$BsmtQual)|is.na(all$BsmtExposure)|is.na(all$BsmtFinType2)), c('BsmtQual', 'BsmtCond', 'BsmtExposure', 'BsmtFinType1', 'BsmtFinType2')]
## BsmtQual BsmtCond BsmtExposure BsmtFinType1 BsmtFinType2
## 333 Gd TA No GLQ <NA>
## 949 Gd TA <NA> Unf Unf
## 1488 Gd TA <NA> Unf Unf
## 2041 Gd <NA> Mn GLQ Rec
## 2186 TA <NA> No BLQ Unf
## 2218 <NA> Fa No Unf Unf
## 2219 <NA> TA No Unf Unf
## 2349 Gd TA <NA> Unf Unf
## 2525 TA <NA> Av ALQ Unf
Así que, en total, parece que hay 79 casas sin sótano, porque las variables de sótano de las otras casas con valores perdidos están todas completas al 80% (falta 1 de cada 5 valores). Voy a imputar los modos para arreglar esas 9 casas.
#Imputing modes.
all$BsmtFinType2[333] <- names(sort(-table(all$BsmtFinType2)))[1]
all$BsmtExposure[c(949, 1488, 2349)] <- names(sort(-table(all$BsmtExposure)))[1]
all$BsmtCond[c(2041, 2186, 2525)] <- names(sort(-table(all$BsmtCond)))[1]
all$BsmtQual[c(2218, 2219)] <- names(sort(-table(all$BsmtQual)))[1]
Ahora que las 5 variables consideradas coinciden en 79 casas con “sin sótano”, voy a factorizarlas/codificarlas en caliente a continuación.
BsmtQual: Evalúa la altura del sótano
Una variable que puede convertirse en ordinal con el vector Cualidades.
Ex Excellent (100+ inches)
Gd Good (90-99 inches)
TA Typical (80-89 inches)
Fa Fair (70-79 inches)
Po Poor (<70 inches
NA No Basement
all$BsmtQual[is.na(all$BsmtQual)] <- 'None'
all$BsmtQual<-as.integer(revalue(all$BsmtQual, Qualities))
table(all$BsmtQual)
##
## 0 2 3 4 5
## 79 88 1285 1209 258
BsmtCond: Evalúa el estado general del sótano
Una variable que puede convertirse en ordinal con el vector Cualidades.
Ex Excellent
Gd Good
TA Typical - slight dampness allowed
Fa Fair - dampness or some cracking or settling
Po Poor - Severe cracking, settling, or wetness
NA No Basement
all$BsmtCond[is.na(all$BsmtCond)] <- 'None'
all$BsmtCond<-as.integer(revalue(all$BsmtCond, Qualities))
table(all$BsmtCond)
##
## 0 1 2 3 4
## 79 5 104 2609 122
BsmtExposure: Se refiere a los muros a nivel del jardín o de la calle
Una variable que puede convertirse en ordinal.
Gd Good Exposure
Av Average Exposure (split levels or foyers typically score average or above)
Mn Mimimum Exposure
No No Exposure
NA No Basement
all$BsmtExposure[is.na(all$BsmtExposure)] <- 'None'
Exposure <- c('None'=0, 'No'=1, 'Mn'=2, 'Av'=3, 'Gd'=4)
all$BsmtExposure<-as.integer(revalue(all$BsmtExposure, Exposure))
table(all$BsmtExposure)
##
## 0 1 2 3 4
## 79 1907 239 418 276
BsmtFinType1: Calificación de la superficie del sótano terminado
Una variable que puede convertirse en ordinal.
GLQ Good Living Quarters
ALQ Average Living Quarters
BLQ Below Average Living Quarters
Rec Average Rec Room
LwQ Low Quality
Unf Unfinshed
NA No Basement
all$BsmtFinType1[is.na(all$BsmtFinType1)] <- 'None'
FinType <- c('None'=0, 'Unf'=1, 'LwQ'=2, 'Rec'=3, 'BLQ'=4, 'ALQ'=5, 'GLQ'=6)
all$BsmtFinType1<-as.integer(revalue(all$BsmtFinType1, FinType))
table(all$BsmtFinType1)
##
## 0 1 2 3 4 5 6
## 79 851 154 288 269 429 849
BsmtFinType2: Calificación de la superficie acabada del sótano (si hay varios tipos)
Una variable que puede convertirse en ordinal con el vector FinType.
GLQ Good Living Quarters
ALQ Average Living Quarters
BLQ Below Average Living Quarters
Rec Average Rec Room
LwQ Low Quality
Unf Unfinshed
NA No Basement
all$BsmtFinType2[is.na(all$BsmtFinType2)] <- 'None'
FinType <- c('None'=0, 'Unf'=1, 'LwQ'=2, 'Rec'=3, 'BLQ'=4, 'ALQ'=5, 'GLQ'=6)
all$BsmtFinType2<-as.integer(revalue(all$BsmtFinType2, FinType))
table(all$BsmtFinType2)
##
## 0 1 2 3 4 5 6
## 79 2494 87 105 68 52 34
El resto de variabes del sótano con sólo unos pocos NAs
Ahora todavía hay que lidiar con esas 6 variables que tienen 1 o 2 NAs.
#mostrar los restantes NA. Utilizando BsmtQual como referencia para las 79 viviendas sin sótano acordadas anteriormente
all[(is.na(all$BsmtFullBath)|is.na(all$BsmtHalfBath)|is.na(all$BsmtFinSF1)|is.na(all$BsmtFinSF2)|is.na(all$BsmtUnfSF)|is.na(all$TotalBsmtSF)), c('BsmtQual', 'BsmtFullBath', 'BsmtHalfBath', 'BsmtFinSF1', 'BsmtFinSF2', 'BsmtUnfSF', 'TotalBsmtSF')]
## BsmtQual BsmtFullBath BsmtHalfBath BsmtFinSF1 BsmtFinSF2 BsmtUnfSF
## 2121 0 NA NA NA NA NA
## 2189 0 NA NA 0 0 0
## TotalBsmtSF
## 2121 NA
## 2189 0
Debería ser obvio que los restantes NAs se refieren todos a “no presente”. A continuación, estoy arreglando esas variables restantes.
BsmtFullBath: Baños completos en el sótano
Una variable entera.
all$BsmtFullBath[is.na(all$BsmtFullBath)] <-0
table(all$BsmtFullBath)
##
## 0 1 2 3
## 1707 1172 38 2
BsmtHalfBath: Medios baños en el sótano
Una variable entera.
all$BsmtHalfBath[is.na(all$BsmtHalfBath)] <-0
table(all$BsmtHalfBath)
##
## 0 1 2
## 2744 171 4
BsmtFinSF1: Tipo 1 pies cuadrados terminados
Una variable entera.
all$BsmtFinSF1[is.na(all$BsmtFinSF1)] <-0
BsmtFinSF2: Tipo 2 pies cuadrados terminados
Una variable entera.
all$BsmtFinSF2[is.na(all$BsmtFinSF2)] <-0
BsmtUnfSF: Pies cuadrados de superficie de sótano sin terminar
Una variable entera.
all$BsmtUnfSF[is.na(all$BsmtUnfSF)] <-0
TotalBsmtSF: Total de pies cuadrados de superficie de sótano
Una variable entera.
all$TotalBsmtSF[is.na(all$TotalBsmtSF)] <-0
###Variables de mampostería
Tipo de revestimiento de mampostería y área de revestimiento de mampostería
El tipo de revestimiento de mampostería tiene 24 NAs. El área de revestimiento de mampostería tiene 23 NAs. Si una casa tiene un área de revestimiento, también debería tener un tipo de revestimiento de mampostería. Arreglemos esto primero.
#compruebe si las 23 casas con superficie de chapa de madera NA son también NA en el tipo de chapa de madera
length(which(is.na(all$MasVnrType) & is.na(all$MasVnrArea)))
## [1] 23
#encontrar el que debe tener un MasVnrType
all[is.na(all$MasVnrType) & !is.na(all$MasVnrArea), c('MasVnrType', 'MasVnrArea')]
## MasVnrType MasVnrArea
## 2611 <NA> 198
#fijar este tipo de chapa imputando el modo
all$MasVnrType[2611] <- names(sort(-table(all$MasVnrType)))[2] #taking the 2nd value as the 1st is 'none'
all[2611, c('MasVnrType', 'MasVnrArea')]
## MasVnrType MasVnrArea
## 2611 BrkFace 198
Esto me deja con 23 casas que realmente no tienen mampostería.
Tipo de revestimiento de mampostería
Comprobará la ordinalidad a continuación.
BrkCmn Brick Common
BrkFace Brick Face
CBlock Cinder Block
None None
Stone Stone
all$MasVnrType[is.na(all$MasVnrType)] <- 'None'
all[!is.na(all$SalePrice),] %>% group_by(MasVnrType) %>% summarise(median = median(SalePrice), counts=n()) %>% arrange(median)
## # A tibble: 4 x 3
## MasVnrType median counts
## <chr> <dbl> <int>
## 1 BrkCmn 139000 15
## 2 None 143125 872
## 3 BrkFace 181000 445
## 4 Stone 246839 128
Parece que hay una diferencia significativa entre el “ladrillo común/ninguno” y los otros tipos. Supongo que las piedras simples y, por ejemplo, las casas de madera son simplemente más baratas. Haré la ordenación en consecuencia.
Masonry <- c('None'=0, 'BrkCmn'=0, 'BrkFace'=1, 'Stone'=2)
all$MasVnrType<-as.integer(revalue(all$MasVnrType, Masonry))
table(all$MasVnrType)
##
## 0 1 2
## 1790 880 249
MasVnrArea: Área de revestimiento de mampostería en pies cuadrados
An integer variable.
all$MasVnrArea[is.na(all$MasVnrArea)] <-0
###MS Zoning
MSZoning: Identifica la clasificación zonal general de la venta
4 NAs. Los valores son categóricos.
A Agriculture
C Commercial
FV Floating Village Residential
I Industrial
RH Residential High Density
RL Residential Low Density
RP Residential Low Density Park
RM Residential Medium Density
#imputing the mode
all$MSZoning[is.na(all$MSZoning)] <- names(sort(-table(all$MSZoning)))[1]
all$MSZoning <- as.factor(all$MSZoning)
table(all$MSZoning)
##
## C (all) FV RH RL RM
## 25 139 26 2269 460
sum(table(all$MSZoning))
## [1] 2919
###Kitchen variables
Kitchen quality and numer of Kitchens above grade
Kitchen quality has 1 NA. Number of Kitchens is complete.
Kitchen quality
1NA. Can be made ordinal with the qualities vector.
Ex Excellent
Gd Good
TA Typical/Average
Fa Fair
Po Poor
all$KitchenQual[is.na(all$KitchenQual)] <- 'TA' #replace with most common value
all$KitchenQual<-as.integer(revalue(all$KitchenQual, Qualities))
table(all$KitchenQual)
##
## 2 3 4 5
## 70 1493 1151 205
sum(table(all$KitchenQual))
## [1] 2919
Number of Kitchens above grade
An integer variable with no NAs.
table(all$KitchenAbvGr)
##
## 0 1 2 3
## 3 2785 129 2
sum(table(all$KitchenAbvGr))
## [1] 2919
Please return to the 5.2 Tabs menu to select other (groups of) variables
###Utilities
Utilities: Type of utilities available
2 NAs. Ordinal as additional utilities is better.
AllPub All public Utilities (E,G,W,& S)
NoSewr Electricity, Gas, and Water (Septic Tank)
NoSeWa Electricity and Gas Only
ELO Electricity only
However, the table below shows that only one house does not have all public utilities. This house is in the train set. Therefore, imputing ‘AllPub’ for the NAs means that all houses in the test set will have ‘AllPub’. This makes the variable useless for prediction. Consequently, I will get rid of it.
table(all$Utilities)
##
## AllPub NoSeWa
## 2916 1
kable(all[is.na(all$Utilities) | all$Utilities=='NoSeWa', 1:9])
| MSSubClass | MSZoning | LotFrontage | LotArea | Street | Alley | LotShape | LandContour | Utilities | |
|---|---|---|---|---|---|---|---|---|---|
| 945 | 20 | RL | 82 | 14375 | Pave | None | 2 | Lvl | NoSeWa |
| 1916 | 30 | RL | 109 | 21780 | Grvl | None | 3 | Lvl | NA |
| 1946 | 20 | RL | 64 | 31220 | Pave | None | 2 | Bnk | NA |
all$Utilities <- NULL
Please return to the 5.2 Tabs menu to select other (groups of) variables
###Home functionality
Functional: Home functionality
1NA. Can be made ordinal (salvage only is worst, typical is best).
Typ Typical Functionality
Min1 Minor Deductions 1
Min2 Minor Deductions 2
Mod Moderate Deductions
Maj1 Major Deductions 1
Maj2 Major Deductions 2
Sev Severely Damaged
Sal Salvage only
#impute mode for the 1 NA
all$Functional[is.na(all$Functional)] <- names(sort(-table(all$Functional)))[1]
all$Functional <- as.integer(revalue(all$Functional, c('Sal'=0, 'Sev'=1, 'Maj2'=2, 'Maj1'=3, 'Mod'=4, 'Min2'=5, 'Min1'=6, 'Typ'=7)))
table(all$Functional)
##
## 1 2 3 4 5 6 7
## 2 9 19 35 70 65 2719
sum(table(all$Functional))
## [1] 2919
###Exterior variables
Hay 4 variables exteriores
2 variables tienen 1 NA, 2 variables no tienen NAs.
Exterior1st: Revestimiento exterior de la casa
1 NA. Los valores son categóricos.
AsbShng Asbestos Shingles
AsphShn Asphalt Shingles
BrkComm Brick Common
BrkFace Brick Face
CBlock Cinder Block
CemntBd Cement Board
HdBoard Hard Board
ImStucc Imitation Stucco
MetalSd Metal Siding
Other Other
Plywood Plywood
PreCast PreCast
Stone Stone
Stucco Stucco
VinylSd Vinyl Siding
Wd Sdng Wood Siding
WdShing Wood Shingles
#imputing mode
all$Exterior1st[is.na(all$Exterior1st)] <- names(sort(-table(all$Exterior1st)))[1]
all$Exterior1st <- as.factor(all$Exterior1st)
table(all$Exterior1st)
##
## AsbShng AsphShn BrkComm BrkFace CBlock CemntBd HdBoard ImStucc MetalSd Plywood
## 44 2 6 87 2 126 442 1 450 221
## Stone Stucco VinylSd Wd Sdng WdShing
## 2 43 1026 411 56
sum(table(all$Exterior1st))
## [1] 2919
Exterior2nd: Revestimiento exterior de la casa (si hay más de un material)
1 NA. Los valores son categóricos.
AsbShng Asbestos Shingles
AsphShn Asphalt Shingles
BrkComm Brick Common
BrkFace Brick Face
CBlock Cinder Block
CemntBd Cement Board
HdBoard Hard Board
ImStucc Imitation Stucco
MetalSd Metal Siding
Other Other
Plywood Plywood
PreCast PreCast
Stone Stone
Stucco Stucco
VinylSd Vinyl Siding
Wd Sdng Wood Siding
WdShing Wood Shingles
#imputing mode
all$Exterior2nd[is.na(all$Exterior2nd)] <- names(sort(-table(all$Exterior2nd)))[1]
all$Exterior2nd <- as.factor(all$Exterior2nd)
table(all$Exterior2nd)
##
## AsbShng AsphShn Brk Cmn BrkFace CBlock CmentBd HdBoard ImStucc MetalSd Other
## 38 4 22 47 3 126 406 15 447 1
## Plywood Stone Stucco VinylSd Wd Sdng Wd Shng
## 270 6 47 1015 391 81
sum(table(all$Exterior2nd))
## [1] 2919
ExterQual: Evalúa la calidad del material en el exterior
No hay NAs. Se puede hacer ordinal utilizando el vector Cualidades.
Ex Excellent
Gd Good
TA Average/Typical
Fa Fair
Po Poor
all$ExterQual<-as.integer(revalue(all$ExterQual, Qualities))
## The following `from` values were not present in `x`: None, Po
table(all$ExterQual)
##
## 2 3 4 5
## 35 1798 979 107
sum(table(all$ExterQual))
## [1] 2919
ExterCond: Evalúa el estado actual del material en el exterior
No hay NAs. Se puede hacer ordinal utilizando el vector Cualidades.
Ex Excellent
Gd Good
TA Average/Typical
Fa Fair
Po Poor
all$ExterCond<-as.integer(revalue(all$ExterCond, Qualities))
## The following `from` values were not present in `x`: None
table(all$ExterCond)
##
## 1 2 3 4 5
## 3 67 2538 299 12
sum(table(all$ExterCond))
## [1] 2919
###Sistema eléctrico
Electrical: Sistema eléctrico
1 NA. Los valores son categóricos.
SBrkr Standard Circuit Breakers & Romex
FuseA Fuse Box over 60 AMP and all Romex wiring (Average)
FuseF 60 AMP Fuse Box and mostly Romex wiring (Fair)
FuseP 60 AMP Fuse Box and mostly knob & tube wiring (poor)
Mix Mixed
#imputing mode
all$Electrical[is.na(all$Electrical)] <- names(sort(-table(all$Electrical)))[1]
all$Electrical <- as.factor(all$Electrical)
table(all$Electrical)
##
## FuseA FuseF FuseP Mix SBrkr
## 188 50 8 1 2672
sum(table(all$Electrical))
## [1] 2919
###Tipo y condición de venta
SaleType: Tipo de venta
1 NA. Los valores son categóricos..
WD Warranty Deed - Conventional
CWD Warranty Deed - Cash
VWD Warranty Deed - VA Loan
New Home just constructed and sold
COD Court Officer Deed/Estate
Con Contract 15% Down payment regular terms
ConLw Contract Low Down payment and low interest
ConLI Contract Low Interest
ConLD Contract Low Down
Oth Other
#imputing mode
all$SaleType[is.na(all$SaleType)] <- names(sort(-table(all$SaleType)))[1]
all$SaleType <- as.factor(all$SaleType)
table(all$SaleType)
##
## COD Con ConLD ConLI ConLw CWD New Oth WD
## 87 5 26 9 8 12 239 7 2526
sum(table(all$SaleType))
## [1] 2919
SaleCondition: Condición de venta
No hay NAs. Los valores son categóricos.
Normal Normal Sale
Abnorml Abnormal Sale - trade, foreclosure, short sale
AdjLand Adjoining Land Purchase
Alloca Allocation - two linked properties with separate deeds, typically condo with a garage unit
Family Sale between family members
Partial Home was not completed when last assessed (associated with New Homes)
all$SaleCondition <- as.factor(all$SaleCondition)
table(all$SaleCondition)
##
## Abnorml AdjLand Alloca Family Normal Partial
## 190 12 24 46 2402 245
sum(table(all$SaleCondition))
## [1] 2919
##Codificación de etiquetas/factorización de las restantes variables de caracteres {.tabset}
En este punto, me he asegurado de que todas las variables con NAs son atendidas. Sin embargo, todavía tengo que ocuparme de las restantes variables de caracteres que no tienen valores perdidos. Al igual que en la sección anterior, he creado pestañas para grupos de variables.
Charcol <- names(all[,sapply(all, is.character)])
Charcol
## [1] "Street" "LandContour" "LandSlope" "Neighborhood" "Condition1"
## [6] "Condition2" "BldgType" "HouseStyle" "RoofStyle" "RoofMatl"
## [11] "Foundation" "Heating" "HeatingQC" "CentralAir" "PavedDrive"
cat('There are', length(Charcol), 'remaining columns with character values')
## There are 15 remaining columns with character values
###Cimiento
Foundation: Tipo de cimiento
BrkTil Brick & Tile
CBlock Cinder Block
PConc Poured Contrete
Slab Slab
Stone Stone
Wood Wood
#No ordinality, so converting into factors
all$Foundation <- as.factor(all$Foundation)
table(all$Foundation)
##
## BrkTil CBlock PConc Slab Stone Wood
## 311 1235 1308 49 11 5
sum(table(all$Foundation))
## [1] 2919
###Calefacción y aire acondicionado
Hay 2 variables de calefacción, y una que indica Airco Sí/No.
Heating: Tipo de calefacción
Floor Floor Furnace
GasA Gas forced warm air furnace
GasW Gas hot water or steam heat
Grav Gravity furnace
OthW Hot water or steam heat other than gas
Wall Wall furnace
#No ordinality, so converting into factors
all$Heating <- as.factor(all$Heating)
table(all$Heating)
##
## Floor GasA GasW Grav OthW Wall
## 1 2874 27 9 2 6
sum(table(all$Heating))
## [1] 2919
HeatingQC: Calidad y estado de la calefacción
Ex Excellent
Gd Good
TA Average/Typical
Fa Fair
Po Poor
#making the variable ordinal using the Qualities vector
all$HeatingQC<-as.integer(revalue(all$HeatingQC, Qualities))
## The following `from` values were not present in `x`: None
table(all$HeatingQC)
##
## 1 2 3 4 5
## 3 92 857 474 1493
sum(table(all$HeatingQC))
## [1] 2919
CentralAir: Aire acondicionado central
N No
Y Yes
all$CentralAir<-as.integer(revalue(all$CentralAir, c('N'=0, 'Y'=1)))
table(all$CentralAir)
##
## 0 1
## 196 2723
sum(table(all$CentralAir))
## [1] 2919
###Tejado
Hay dos variables que tienen que ver con el techo de las casas.
RoofStyle: Tipos de tejado
Flat Flat
Gable Gable
Gambrel Gabrel (Barn)
Hip Hip
Mansard Mansard
Shed Shed
#No ordinality, so converting into factors
all$RoofStyle <- as.factor(all$RoofStyle)
table(all$RoofStyle)
##
## Flat Gable Gambrel Hip Mansard Shed
## 20 2310 22 551 11 5
sum(table(all$RoofStyle))
## [1] 2919
RoofMatl: Material del techo
ClyTile Clay or Tile
CompShg Standard (Composite) Shingle
Membran Membrane
Metal Metal
Roll Roll
Tar&Grv Gravel & Tar
WdShake Wood Shakes
WdShngl Wood Shingles
#No ordinality, so converting into factors
all$RoofMatl <- as.factor(all$RoofMatl)
table(all$RoofMatl)
##
## ClyTile CompShg Membran Metal Roll Tar&Grv WdShake WdShngl
## 1 2876 1 1 1 23 9 7
sum(table(all$RoofMatl))
## [1] 2919
###Terreno
2 variables que especifican la planicidad y la inclinación de la propiedad.
LandContour: La planicie de la propiedad
Lvl Near Flat/Level
Bnk Banked - Quick and significant rise from street grade to building
HLS Hillside - Significant slope from side to side
Low Depression
#No ordinality, so converting into factors
all$LandContour <- as.factor(all$LandContour)
table(all$LandContour)
##
## Bnk HLS Low Lvl
## 117 120 60 2622
sum(table(all$LandContour))
## [1] 2919
LandSlope: Pendiente de la propiedad
Gtl Gentle slope
Mod Moderate Slope
Sev Severe Slope
#Ordinal, so label encoding
all$LandSlope<-as.integer(revalue(all$LandSlope, c('Sev'=0, 'Mod'=1, 'Gtl'=2)))
table(all$LandSlope)
##
## 0 1 2
## 16 125 2778
sum(table(all$LandSlope))
## [1] 2919
###Vivienda
2 variables que especifican el tipo y el estilo de la vivienda.
BldgType: Tipo de vivienda
1Fam Single-family Detached
2FmCon Two-family Conversion; originally built as one-family dwelling
Duplx Duplex
TwnhsE Townhouse End Unit
TwnhsI Townhouse Inside Unit
Esto me parece ordinario (unifamiliar = mejor). Vamos a comprobarlo con la visualización.
ggplot(all[!is.na(all$SalePrice),], aes(x=as.factor(BldgType), y=SalePrice)) +
geom_bar(stat='summary', fun.y = "median", fill='blue')+
scale_y_continuous(breaks= seq(0, 800000, by=100000), labels = comma) +
geom_label(stat = "count", aes(label = ..count.., y = ..count..))
## Warning: Ignoring unknown parameters: fun.y
## No summary function supplied, defaulting to `mean_se()`
Sin embargo, la visualización no muestra la ordinalidad.
#No ordinality, so converting into factors
all$BldgType <- as.factor(all$BldgType)
table(all$BldgType)
##
## 1Fam 2fmCon Duplex Twnhs TwnhsE
## 2425 62 109 96 227
sum(table(all$BldgType))
## [1] 2919
HouseStyle: Estilo de la vivienda
1Story One story
1.5Fin One and one-half story: 2nd level finished
1.5Unf One and one-half story: 2nd level unfinished
2Story Two story
2.5Fin Two and one-half story: 2nd level finished
2.5Unf Two and one-half story: 2nd level unfinished
SFoyer Split Foyer
SLvl Split Level
#No ordinality, so converting into factors
all$HouseStyle <- as.factor(all$HouseStyle)
table(all$HouseStyle)
##
## 1.5Fin 1.5Unf 1Story 2.5Fin 2.5Unf 2Story SFoyer SLvl
## 314 19 1471 8 24 872 83 128
sum(table(all$HouseStyle))
## [1] 2919
###Vecindario y condiciones
3 variables que especifican la ubicación física y la proximidad de las “condiciones”.
Neighborhood: Ubicaciones físicas dentro de los límites de la ciudad de Ames
Blmngtn Bloomington Heights
Blueste Bluestem
BrDale Briardale
BrkSide Brookside
ClearCr Clear Creek
CollgCr College Creek
Crawfor Crawford
Edwards Edwards
Gilbert Gilbert
IDOTRR Iowa DOT and Rail Road
MeadowV Meadow Village
Mitchel Mitchell
Names North Ames
NoRidge Northridge
NPkVill Northpark Villa
NridgHt Northridge Heights
NWAmes Northwest Ames
OldTown Old Town
SWISU South & West of Iowa State University
Sawyer Sawyer
SawyerW Sawyer West
Somerst Somerset
StoneBr Stone Brook
Timber Timberland
Veenker Veenker
#No ordinality, so converting into factors
all$Neighborhood <- as.factor(all$Neighborhood)
table(all$Neighborhood)
##
## Blmngtn Blueste BrDale BrkSide ClearCr CollgCr Crawfor Edwards Gilbert IDOTRR
## 28 10 30 108 44 267 103 194 165 93
## MeadowV Mitchel NAmes NoRidge NPkVill NridgHt NWAmes OldTown Sawyer SawyerW
## 37 114 443 71 23 166 131 239 151 125
## Somerst StoneBr SWISU Timber Veenker
## 182 51 48 72 24
sum(table(all$Neighborhood))
## [1] 2919
Condition1: Proximidad a diversas condiciones
Artery Adjacent to arterial street
Feedr Adjacent to feeder street
Norm Normal
RRNn Within 200' of North-South Railroad
RRAn Adjacent to North-South Railroad
PosN Near positive off-site feature--park, greenbelt, etc.
PosA Adjacent to postive off-site feature
RRNe Within 200' of East-West Railroad
RRAe Adjacent to East-West Railroad
#No ordinality, so converting into factors
all$Condition1 <- as.factor(all$Condition1)
table(all$Condition1)
##
## Artery Feedr Norm PosA PosN RRAe RRAn RRNe RRNn
## 92 164 2511 20 39 28 50 6 9
sum(table(all$Condition1))
## [1] 2919
Condition2: Proximidad de varias condiciones (si hay más de una)
Artery Adjacent to arterial street
Feedr Adjacent to feeder street
Norm Normal
RRNn Within 200' of North-South Railroad
RRAn Adjacent to North-South Railroad
PosN Near positive off-site feature--park, greenbelt, etc.
PosA Adjacent to postive off-site feature
RRNe Within 200' of East-West Railroad
RRAe Adjacent to East-West Railroad
#No ordinality, so converting into factors
all$Condition2 <- as.factor(all$Condition2)
table(all$Condition2)
##
## Artery Feedr Norm PosA PosN RRAe RRAn RRNn
## 5 13 2889 4 4 1 1 2
sum(table(all$Condition2))
## [1] 2919
###Pavimento de la calle y de la calzada
2 variables
Street: Tipo de acceso a la propiedad
Grvl Gravel
Pave Paved
#Ordinal, so label encoding
all$Street<-as.integer(revalue(all$Street, c('Grvl'=0, 'Pave'=1)))
table(all$Street)
##
## 0 1
## 12 2907
sum(table(all$Street))
## [1] 2919
PavedDrive: Calzada pavimentada
Y Paved
P Partial Pavement
N Dirt/Gravel
#Ordinal, so label encoding
all$PavedDrive<-as.integer(revalue(all$PavedDrive, c('N'=0, 'P'=1, 'Y'=2)))
table(all$PavedDrive)
##
## 0 1 2
## 216 62 2641
sum(table(all$PavedDrive))
## [1] 2919
##Cambio de algunas variables numéricas en factores
En este punto, todas las variables están completas (sin NAs), y todas las variables de carácter se convierten en etiquetas numéricas o en factores. Sin embargo, hay 3 variables que se registran como numéricas pero que en realidad deberían ser categóricas.
###Año y mes de venta
Aunque la oridinalidad dentro del YearBuilt (o remodelado) tiene sentido (las casas antiguas valen menos), estamos hablando de sólo 5 años de ventas. Estos años también incluyen una crisis económica. Por ejemplo: Los precios de venta en 2009 (después del colapso) es muy probable que sean mucho más bajos que en 2007. Convertiré YrSold en un factor antes de modelar, pero como necesito la versión numérica de YrSold para crear una variable Age, no lo estoy haciendo todavía.
El mes de venta también es una variable entera. Sin embargo, diciembre no es “mejor” que enero. Por lo tanto, volveré a convertir los valores de MoSold en factores.
str(all$YrSold)
## int [1:2919] 2008 2007 2008 2006 2008 2009 2007 2009 2008 2008 ...
str(all$MoSold)
## int [1:2919] 2 5 9 2 12 10 8 11 4 1 ...
all$MoSold <- as.factor(all$MoSold)
Aunque posiblemente sea un poco menos pronunciado de lo que se esperaba, los efectos de las crisis bancarias que tuvieron lugar a finales de 2007 pueden apreciarse de hecho. Después de los precios medios más altos de 2007, los precios disminuyeron gradualmente. Sin embargo, la estacionalidad parece desempeñar un papel más importante, como puede verse a continuación.
ys <- ggplot(all[!is.na(all$SalePrice),], aes(x=as.factor(YrSold), y=SalePrice)) +
geom_bar(stat='summary', fun.y = "median", fill='blue')+
scale_y_continuous(breaks= seq(0, 800000, by=25000), labels = comma) +
geom_label(stat = "count", aes(label = ..count.., y = ..count..)) +
coord_cartesian(ylim = c(0, 200000)) +
geom_hline(yintercept=163000, linetype="dashed", color = "red") #dashed line is median SalePrice
## Warning: Ignoring unknown parameters: fun.y
ms <- ggplot(all[!is.na(all$SalePrice),], aes(x=MoSold, y=SalePrice)) +
geom_bar(stat='summary', fun.y = "median", fill='blue')+
scale_y_continuous(breaks= seq(0, 800000, by=25000), labels = comma) +
geom_label(stat = "count", aes(label = ..count.., y = ..count..)) +
coord_cartesian(ylim = c(0, 200000)) +
geom_hline(yintercept=163000, linetype="dashed", color = "red") #dashed line is median SalePrice
## Warning: Ignoring unknown parameters: fun.y
grid.arrange(ys, ms, widths=c(1,2))
## No summary function supplied, defaulting to `mean_se()`
## No summary function supplied, defaulting to `mean_se()`
###MSSubClass
MSSubClass: Identifica el tipo de vivienda implicada en la venta.
20 1-STORY 1946 & NEWER ALL STYLES
30 1-STORY 1945 & OLDER
40 1-STORY W/FINISHED ATTIC ALL AGES
45 1-1/2 STORY - UNFINISHED ALL AGES
50 1-1/2 STORY FINISHED ALL AGES
60 2-STORY 1946 & NEWER
70 2-STORY 1945 & OLDER
75 2-1/2 STORY ALL AGES
80 SPLIT OR MULTI-LEVEL
85 SPLIT FOYER
90 DUPLEX - ALL STYLES AND AGES
120 1-STORY PUD (Planned Unit Development) - 1946 & NEWER
150 1-1/2 STORY PUD - ALL AGES
160 2-STORY PUD - 1946 & NEWER
180 PUD - MULTILEVEL - INCL SPLIT LEV/FOYER
190 2 FAMILY CONVERSION - ALL STYLES AND AGES
Estas clases están codificadas como números, pero realmente son categorías.
str(all$MSSubClass)
## int [1:2919] 60 20 60 70 60 50 20 60 50 190 ...
all$MSSubClass <- as.factor(all$MSSubClass)
#revalue for better readability
all$MSSubClass<-revalue(all$MSSubClass, c('20'='1 story 1946+', '30'='1 story 1945-', '40'='1 story unf attic', '45'='1,5 story unf', '50'='1,5 story fin', '60'='2 story 1946+', '70'='2 story 1945-', '75'='2,5 story all ages', '80'='split/multi level', '85'='split foyer', '90'='duplex all style/age', '120'='1 story PUD 1946+', '150'='1,5 story PUD all', '160'='2 story PUD 1946+', '180'='PUD multilevel', '190'='2 family conversion'))
str(all$MSSubClass)
## Factor w/ 16 levels "1 story 1946+",..: 6 1 6 7 6 5 1 6 5 16 ...
#Visualización de variables importantes
Por fin he llegado al punto en que todas las variables de carácter se han convertido en factores categóricos o se han codificado en números. Además, 3 variables numéricas se han convertido en factores, y he eliminado una variable (Utilities). Como puede ver a continuación, el número de variables numéricas es ahora de 56 (incluida la variable de respuesta), y las 23 variables restantes son categóricas.
numericVars <- which(sapply(all, is.numeric)) #index vector numeric variables
factorVars <- which(sapply(all, is.factor)) #index vector factor variables
cat('There are', length(numericVars), 'numeric variables, and', length(factorVars), 'categoric variables')
## There are 56 numeric variables, and 23 categoric variables
##Correlaciones de nuevo
all_numVar <- all[, numericVars]
cor_numVar <- cor(all_numVar, use="pairwise.complete.obs") #correlations of all numeric variables
#sort on decreasing correlations with SalePrice
cor_sorted <- as.matrix(sort(cor_numVar[,'SalePrice'], decreasing = TRUE))
#select only high corelations
CorHigh <- names(which(apply(cor_sorted, 1, function(x) abs(x)>0.5)))
cor_numVar <- cor_numVar[CorHigh, CorHigh]
corrplot.mixed(cor_numVar, tl.col="black", tl.pos = "lt", tl.cex = 0.7,cl.cex = .7, number.cex=.7)
##Encontrar la importancia de las variables con un Random Forest rápido
Aunque las correlaciones están dando una buena visión de las variables numéricas más importantes y la multicolinealidad entre esas variables, quería obtener una visión de las variables más importantes incluyendo las variables categóricas antes de pasar a la visualización.
Intenté obtener la importancia relativa de las variables con un modelo de regresión lineal rápido con la función calc.relimp del paquete , y también probé la función boruta del paquete boruta que separa las variables en grupos que son importantes o no. Sin embargo, estos métodos han llevado mucho tiempo. Como sólo quiero obtener una indicación de la importancia de las variables, finalmente decidí mantenerlo simple y utilizar un modelo rápido y sucio de Bosque Aleatorio con sólo 100 árboles. Esto también me sirve y no me lleva mucho tiempo, ya que puedo especificar un número (relativamente) pequeño de árboles.
set.seed(2018)
quick_RF <- randomForest(x=all[1:1460,-79], y=all$SalePrice[1:1460], ntree=100,importance=TRUE)
imp_RF <- importance(quick_RF)
imp_DF <- data.frame(Variables = row.names(imp_RF), MSE = imp_RF[,1])
imp_DF <- imp_DF[order(imp_DF$MSE, decreasing = TRUE),]
ggplot(imp_DF[1:20,], aes(x=reorder(Variables, MSE), y=MSE, fill=MSE)) + geom_bar(stat = 'identity') + labs(x = 'Variables', y= '% increase MSE if variable is randomly permuted') + coord_flip() + theme(legend.position="none")
Sólo 3 de esas variables más importantes son categóricas según la RF; Neighborhood, MSSubClass y GarageType.
As I have already visualized the relation between the Above Ground Living Area and SalePrice in my initial explorations, I will now just display the distribution itself. As there are more ‘square feet’ surface measurements in the Top 20, I am taking the opportunity to bundle them in this section. Note: GarageArea is taken care of in the Garage variables section.
I am also adding ‘Total Rooms Above Ground’ (TotRmsAbvGrd) as this variable is highly correlated with the Above Ground Living Area(0.81).
s1 <- ggplot(data= all, aes(x=GrLivArea)) +
geom_density() + labs(x='Square feet living area')
s2 <- ggplot(data=all, aes(x=as.factor(TotRmsAbvGrd))) +
geom_histogram(stat='count') + labs(x='Rooms above Ground')
s3 <- ggplot(data= all, aes(x=X1stFlrSF)) +
geom_density() + labs(x='Square feet first floor')
s4 <- ggplot(data= all, aes(x=X2ndFlrSF)) +
geom_density() + labs(x='Square feet second floor')
s5 <- ggplot(data= all, aes(x=TotalBsmtSF)) +
geom_density() + labs(x='Square feet basement')
s6 <- ggplot(data= all[all$LotArea<100000,], aes(x=LotArea)) +
geom_density() + labs(x='Square feet lot')
s7 <- ggplot(data= all, aes(x=LotFrontage)) +
geom_density() + labs(x='Linear feet lot frontage')
s8 <- ggplot(data= all, aes(x=LowQualFinSF)) +
geom_histogram() + labs(x='Low quality square feet 1st & 2nd')
layout <- matrix(c(1,2,5,3,4,8,6,7),4,2,byrow=TRUE)
multiplot(s1, s2, s3, s4, s5, s6, s7, s8, layout=layout)
Más adelante investigaré varias de estas variables en busca de valores atípicos. Para la visualización del lote, ya he sacado los lotes de más de 100.000 pies cuadrados (4 casas).
GrLivArea parecía ser sólo el total de pies cuadrados del primer y segundo piso. Sin embargo, en una versión posterior, descubrí que también hay una variable llamada LowQualFinSF: Pies cuadrados terminados de baja calidad (todos los pisos). Como se puede ver arriba (Low quality square feet 1st and 2nd) casi todas las casas no tienen nada de esto (sólo 40 casas tienen algo). Resulta que estos pies cuadrados sí están incluidos en el GrLivArea. La correlación entre esas 3 variables y GrLivArea es exactamente 1.
cor(all$GrLivArea, (all$X1stFlrSF + all$X2ndFlrSF + all$LowQualFinSF))
## [1] 1
head(all[all$LowQualFinSF>0, c('GrLivArea', 'X1stFlrSF', 'X2ndFlrSF', 'LowQualFinSF')])
## GrLivArea X1stFlrSF X2ndFlrSF LowQualFinSF
## 52 1176 816 0 360
## 89 1526 1013 0 513
## 126 754 520 0 234
## 171 1382 854 0 528
## 186 3608 1518 1518 572
## 188 1656 808 704 144
###La variable categórica más importante;Neighborhood
El primer gráfico muestra la mediana del precio de venta por barrio. La frecuencia (número de casas) de cada barrio en el conjunto de entrenamiento se muestra en las etiquetas.
El segundo gráfico muestra las frecuencias de todos los datos.
n1 <- ggplot(all[!is.na(all$SalePrice),], aes(x=Neighborhood, y=SalePrice)) +
geom_bar(stat='summary', fun.y = "median", fill='blue') +
theme(axis.text.x = element_text(angle = 45, hjust = 1)) +
scale_y_continuous(breaks= seq(0, 800000, by=50000), labels = comma) +
geom_label(stat = "count", aes(label = ..count.., y = ..count..), size=3) +
geom_hline(yintercept=163000, linetype="dashed", color = "red") #dashed line is median SalePrice
n2 <- ggplot(data=all, aes(x=Neighborhood)) +
geom_histogram(stat='count')+
geom_label(stat = "count", aes(label = ..count.., y = ..count..), size=3)+
theme(axis.text.x = element_text(angle = 45, hjust = 1))
grid.arrange(n1, n2)
## No summary function supplied, defaulting to `mean_se()`
###Calidad global y otras variables de calidad
Ya he visualizado la relación entre la calidad general y el precio de venta en mis exploraciones iniciales, pero quiero visualizar también la distribución de frecuencias. Como hay más medidas de calidad, aprovecho para agruparlas en esta sección.
q1 <- ggplot(data=all, aes(x=as.factor(OverallQual))) +
geom_histogram(stat='count')
q2 <- ggplot(data=all, aes(x=as.factor(ExterQual))) +
geom_histogram(stat='count')
q3 <- ggplot(data=all, aes(x=as.factor(BsmtQual))) +
geom_histogram(stat='count')
q4 <- ggplot(data=all, aes(x=as.factor(KitchenQual))) +
geom_histogram(stat='count')
q5 <- ggplot(data=all, aes(x=as.factor(GarageQual))) +
geom_histogram(stat='count')
q6 <- ggplot(data=all, aes(x=as.factor(FireplaceQu))) +
geom_histogram(stat='count')
q7 <- ggplot(data=all, aes(x=as.factor(PoolQC))) +
geom_histogram(stat='count')
layout <- matrix(c(1,2,8,3,4,8,5,6,7),3,3,byrow=TRUE)
multiplot(q1, q2, q3, q4, q5, q6, q7, layout=layout)
La Calidad Global es muy importante, y también más granular que las otras variables. La Calidad externa también es improtante, pero tiene una alta correlación con la Calidad general (0,73). La Calidad de la Cocina también parece una de las que hay que mantener, ya que todas las casas tienen cocina y hay una varianza con cierta sustancia. La Calidad del Garaje no parece distinguirse mucho, ya que la mayoría de los garajes tienen Q3. La Calidad de la Chimenea está en la lista de correlaciones altas, y en la lista de variables importantes. El PoolQC es sólo muy escaso (las 13 piscinas ni siquiera se puede ver en esta escala). Miraré de crear una variable “tiene piscina” más adelante.
###La segunda variable categórica más importante; MSSubClass
La primera visualización muestra la mediana del precio de venta por MSSubClass. La frecuencia (número de casas) de cada MSSubClass en el conjunto de entrenamiento se muestra en las etiquetas.
El histrograma muestra las frecuencias de todos los datos. La mayoría de las casas son relativamente nuevas y tienen una o dos plantas.
ms1 <- ggplot(all[!is.na(all$SalePrice),], aes(x=MSSubClass, y=SalePrice)) +
geom_bar(stat='summary', fun.y = "median", fill='blue') +
theme(axis.text.x = element_text(angle = 45, hjust = 1)) +
scale_y_continuous(breaks= seq(0, 800000, by=50000), labels = comma) +
geom_label(stat = "count", aes(label = ..count.., y = ..count..), size=3) +
geom_hline(yintercept=163000, linetype="dashed", color = "red") #dashed line is median SalePrice
ms2 <- ggplot(data=all, aes(x=MSSubClass)) +
geom_histogram(stat='count')+
geom_label(stat = "count", aes(label = ..count.., y = ..count..), size=3) +
theme(axis.text.x = element_text(angle = 45, hjust = 1))
grid.arrange(ms1, ms2)
## No summary function supplied, defaulting to `mean_se()`
###Garaje variables
Varias variables de Garaje tienen una alta correlación con Precio de Venta, y también están en la lista de las 20 primeras del bosque aleatorio rápido. Sin embargo, hay multicolinealidad entre ellas y creo que 7 variables de garaje son demasiadas de todos modos. Creo que algo así como 3 variables deberían ser suficientes (posiblemente GarageCars, GarageType, y una medida de Calidad), pero antes de hacer cualquier selección estoy visualizando todas ellas en esta sección.
#correct error
all$GarageYrBlt[2593] <- 2007 #this must have been a typo. GarageYrBlt=2207, YearBuilt=2006, YearRemodAdd=2007.
g1 <- ggplot(data=all[all$GarageCars !=0,], aes(x=GarageYrBlt)) +
geom_histogram()
g2 <- ggplot(data=all, aes(x=as.factor(GarageCars))) +
geom_histogram(stat='count')
g3 <- ggplot(data= all, aes(x=GarageArea)) +
geom_density()
g4 <- ggplot(data=all, aes(x=as.factor(GarageCond))) +
geom_histogram(stat='count')
g5 <- ggplot(data=all, aes(x=GarageType)) +
geom_histogram(stat='count')
g6 <- ggplot(data=all, aes(x=as.factor(GarageQual))) +
geom_histogram(stat='count')
g7 <- ggplot(data=all, aes(x=as.factor(GarageFinish))) +
geom_histogram(stat='count')
layout <- matrix(c(1,5,5,2,3,8,6,4,7),3,3,byrow=TRUE)
multiplot(g1, g2, g3, g4, g5, g6, g7, layout=layout)
Como ya se mencionó en la sección 4.2, GarageCars y GarageArea están altamente correlacionados. Aquí, GarageQual y GarageCond también parecen altamente correlacionados, y ambos están dominados por el nivel =3.
###Variables del sótano
Al igual que las variables del garaje, las múltiples variables del sótano son importantes en la matriz de correlaciones y en la lista de los 20 mejores predictores de la RF. Sin embargo, 11 variables de sótano parecen excesivas. Antes de decidir qué voy a hacer con ellas, a continuación visualizo 8 de ellas. Las 2 variables “Cuarto de baño” se tratan en la ingeniería de características (sección 7.1), y los “Pies cuadrados del sótano” ya se tratan en la sección 6.2.1.
b1 <- ggplot(data=all, aes(x=BsmtFinSF1)) +
geom_histogram() + labs(x='Type 1 finished square feet')
b2 <- ggplot(data=all, aes(x=BsmtFinSF2)) +
geom_histogram()+ labs(x='Type 2 finished square feet')
b3 <- ggplot(data=all, aes(x=BsmtUnfSF)) +
geom_histogram()+ labs(x='Unfinished square feet')
b4 <- ggplot(data=all, aes(x=as.factor(BsmtFinType1))) +
geom_histogram(stat='count')+ labs(x='Rating of Type 1 finished area')
b5 <- ggplot(data=all, aes(x=as.factor(BsmtFinType2))) +
geom_histogram(stat='count')+ labs(x='Rating of Type 2 finished area')
b6 <- ggplot(data=all, aes(x=as.factor(BsmtQual))) +
geom_histogram(stat='count')+ labs(x='Height of the basement')
b7 <- ggplot(data=all, aes(x=as.factor(BsmtCond))) +
geom_histogram(stat='count')+ labs(x='Rating of general condition')
b8 <- ggplot(data=all, aes(x=as.factor(BsmtExposure))) +
geom_histogram(stat='count')+ labs(x='Walkout or garden level walls')
layout <- matrix(c(1,2,3,4,5,9,6,7,8),3,3,byrow=TRUE)
multiplot(b1, b2, b3, b4, b5, b6, b7, b8, layout=layout)
Así que parecía como si la superficie total del sótano en pies cuadrados (TotalBsmtSF) se desglosa en áreas terminadas (2 si más de un tipo de acabado), y el área sin terminar. Hice una comprobación entre la correlación del total de esas 3 variables, y TotalBsmtSF. La correlación es exactamente 1, así que eso es algo bueno (sin errores o pequeñas discrepancias).
La calidad del sótano es un nombre de variable confuso, ya que resulta que califica específicamente la altura del sótano.
#Ingeniería de funciones
##Número total de baños
Hay 4 variables de baño. Por separado, estas variables no son muy importantes. Sin embargo, supongo que si las sumo en un solo predictor, es probable que éste se convierta en un predictor fuerte.
“Un medio baño, también conocido como tocador o baño de invitados, sólo tiene dos de los cuatro componentes principales del cuarto de baño, normalmente un inodoro y un lavabo”. En consecuencia, también contaré los medios baños como medios.
all$TotBathrooms <- all$FullBath + (all$HalfBath*0.5) + all$BsmtFullBath + (all$BsmtHalfBath*0.5)
Como se puede ver en el primer gráfico, ahora parece haber una clara correlación (es de 0,63). La distribución de frecuencias de los Baños en todos los datos se muestra en el segundo gráfico.
tb1 <- ggplot(data=all[!is.na(all$SalePrice),], aes(x=as.factor(TotBathrooms), y=SalePrice))+
geom_point(col='blue') + geom_smooth(method = "lm", se=FALSE, color="black", aes(group=1)) +
scale_y_continuous(breaks= seq(0, 800000, by=100000), labels = comma)
tb2 <- ggplot(data=all, aes(x=as.factor(TotBathrooms))) +
geom_histogram(stat='count')
grid.arrange(tb1, tb2)
## `geom_smooth()` using formula 'y ~ x'
##Añadir las variables “Edad de la casa”, “Remodelado (Sí/No)” y “Es nuevo”.
En total, hay 3 variables que son relevantes con respecto a la Edad de una casa; YearBlt, YearRemodAdd, y YearSold. YearRemodAdd es por defecto YearBuilt si no ha habido ninguna remodelación/adición. Utilizaré YearRemodeled y YearSold para determinar la edad. Sin embargo, como siempre quedarán partes de construcciones antiguas y puede que sólo se hayan renovado partes de la casa, también introduciré una variable Remodelado Sí/No. Esto debería verse como una especie de parámetro de penalización que indica que si la Edad se basa en una fecha de remodelación, probablemente valga menos que las casas que se construyeron desde cero en ese mismo año.
all$Remod <- ifelse(all$YearBuilt==all$YearRemodAdd, 0, 1) #0=No Remodeling, 1=Remodeling
all$Age <- as.numeric(all$YrSold)-all$YearRemodAdd
ggplot(data=all[!is.na(all$SalePrice),], aes(x=Age, y=SalePrice))+
geom_point(col='blue') + geom_smooth(method = "lm", se=FALSE, color="black", aes(group=1)) +
scale_y_continuous(breaks= seq(0, 800000, by=100000), labels = comma)
## `geom_smooth()` using formula 'y ~ x'
As expected, the graph shows a negative correlation with Age (old house are worth less).
cor(all$SalePrice[!is.na(all$SalePrice)], all$Age[!is.na(all$SalePrice)])
## [1] -0.5090787
ggplot(all[!is.na(all$SalePrice),], aes(x=as.factor(Remod), y=SalePrice)) +
geom_bar(stat='summary', fun.y = "median", fill='blue') +
geom_label(stat = "count", aes(label = ..count.., y = ..count..), size=6) +
scale_y_continuous(breaks= seq(0, 800000, by=50000), labels = comma) +
theme_grey(base_size = 18) +
geom_hline(yintercept=163000, linetype="dashed") #dashed line is median SalePrice
## Warning: Ignoring unknown parameters: fun.y
## No summary function supplied, defaulting to `mean_se()`
Por último, voy a crear la variable IsNew a continuación. En total, hay
116 casas nuevas en el conjunto de datos.
all$IsNew <- ifelse(all$YrSold==all$YearBuilt, 1, 0)
table(all$IsNew)
##
## 0 1
## 2803 116
Estas 116 casas nuevas se distribuyen de forma bastante equitativa entre el conjunto de entrenamiento y el de prueba, y como se puede ver las casas nuevas valen considerablemente más en promedio.
ggplot(all[!is.na(all$SalePrice),], aes(x=as.factor(IsNew), y=SalePrice)) +
geom_bar(stat='summary', fun.y = "median", fill='blue') +
geom_label(stat = "count", aes(label = ..count.., y = ..count..), size=6) +
scale_y_continuous(breaks= seq(0, 800000, by=50000), labels = comma) +
theme_grey(base_size = 18) +
geom_hline(yintercept=163000, linetype="dashed") #dashed line is median SalePrice
## Warning: Ignoring unknown parameters: fun.y
## No summary function supplied, defaulting to `mean_se()`
all$YrSold <- as.factor(all$YrSold) #the numeric version is now not needed anymore
##Binning Neighborhood
nb1 <- ggplot(all[!is.na(all$SalePrice),], aes(x=reorder(Neighborhood, SalePrice, FUN=median), y=SalePrice)) +
geom_bar(stat='summary', fun.y = "median", fill='blue') + labs(x='Neighborhood', y='Median SalePrice') +
theme(axis.text.x = element_text(angle = 45, hjust = 1)) +
scale_y_continuous(breaks= seq(0, 800000, by=50000), labels = comma) +
geom_label(stat = "count", aes(label = ..count.., y = ..count..), size=3) +
geom_hline(yintercept=163000, linetype="dashed", color = "red") #dashed line is median SalePrice
## Warning: Ignoring unknown parameters: fun.y
nb2 <- ggplot(all[!is.na(all$SalePrice),], aes(x=reorder(Neighborhood, SalePrice, FUN=mean), y=SalePrice)) +
geom_bar(stat='summary', fun.y = "mean", fill='blue') + labs(x='Neighborhood', y="Mean SalePrice") +
theme(axis.text.x = element_text(angle = 45, hjust = 1)) +
scale_y_continuous(breaks= seq(0, 800000, by=50000), labels = comma) +
geom_label(stat = "count", aes(label = ..count.., y = ..count..), size=3) +
geom_hline(yintercept=163000, linetype="dashed", color = "red") #dashed line is median SalePrice
## Warning: Ignoring unknown parameters: fun.y
grid.arrange(nb1, nb2)
## No summary function supplied, defaulting to `mean_se()`
## No summary function supplied, defaulting to `mean_se()`
Tanto la mediana como la media de los precios de venta coinciden en 3 barrios con precios de venta sustancialmente más altos. La separación de los 3 barrios relativamente pobres es menos clara, pero al menos ambos gráficos coinciden en los mismos 3 barrios pobres. Como no quiero “sobreponderar”, sólo estoy creando categorías para esos “extremos”.
all$NeighRich[all$Neighborhood %in% c('StoneBr', 'NridgHt', 'NoRidge')] <- 2
all$NeighRich[!all$Neighborhood %in% c('MeadowV', 'IDOTRR', 'BrDale', 'StoneBr', 'NridgHt', 'NoRidge')] <- 1
all$NeighRich[all$Neighborhood %in% c('MeadowV', 'IDOTRR', 'BrDale')] <- 0
table(all$NeighRich)
##
## 0 1 2
## 160 2471 288
##Pies cuadrados totales
Como el espacio habitable total suele ser muy importante cuando la gente compra casas, estoy añadiendo un predictor que suma el espacio habitable por encima y por debajo del suelo.
all$TotalSqFeet <- all$GrLivArea + all$TotalBsmtSF
ggplot(data=all[!is.na(all$SalePrice),], aes(x=TotalSqFeet, y=SalePrice))+
geom_point(col='blue') + geom_smooth(method = "lm", se=FALSE, color="black", aes(group=1)) +
scale_y_continuous(breaks= seq(0, 800000, by=100000), labels = comma) +
geom_text_repel(aes(label = ifelse(all$GrLivArea[!is.na(all$SalePrice)]>4500, rownames(all), '')))
## `geom_smooth()` using formula 'y ~ x'
Como era de esperar, la correlación con SalePrice es muy fuerte (0,78).
cor(all$SalePrice, all$TotalSqFeet, use= "pairwise.complete.obs")
## [1] 0.7789588
Los dos posibles valores atípicos parecen “salir” aún más que antes. Al eliminar estos dos valores atípicos, la correlación aumenta un 5%.
cor(all$SalePrice[-c(524, 1299)], all$TotalSqFeet[-c(524, 1299)], use= "pairwise.complete.obs")
## [1] 0.829042
##Consolidación de las variables del porche
A continuación, he enumerado las variables que parecen estar relacionadas con los porches.
WoodDeckSF: Área de la cubierta de madera en pies cuadrados
OpenPorchSF: Área de porche abierto en pies cuadrados
EnclosedPorch: Área de porche cerrado en pies cuadrados
3SsnPorch: Área de porche de tres estaciones en pies cuadrados
ScreenPorch: Superficie del porche en pies cuadrados
Por lo que sé, los porches son áreas protegidas fuera de la casa, y una cubierta de madera no está protegida. Por lo tanto, estoy dejando WoodDeckSF solo, y sólo se consolidan las 4 variables de porche.
all$TotalPorchSF <- all$OpenPorchSF + all$EnclosedPorch + all$X3SsnPorch + all$ScreenPorch
Although adding up these Porch areas makes sense (there should not be any overlap between areas), the correlation with SalePrice is not very strong.
cor(all$SalePrice, all$TotalPorchSF, use= "pairwise.complete.obs")
## [1] 0.1957389
ggplot(data=all[!is.na(all$SalePrice),], aes(x=TotalPorchSF, y=SalePrice))+
geom_point(col='blue') + geom_smooth(method = "lm", se=FALSE, color="black", aes(group=1)) +
scale_y_continuous(breaks= seq(0, 800000, by=100000), labels = comma)
## `geom_smooth()` using formula 'y ~ x'
#Preparación de los datos para la modelización
##Eliminación de variables altamente correlacionadas
En primer lugar, descarto una variable si dos variables están muy correlacionadas. Para encontrar estos pares correlacionados, he vuelto a utilizar la matriz de correlaciones (véase la sección 6.1). Por ejemplo: GarajeCoches y GarajeÁrea tienen una correlación de 0,89. De estas dos, estoy descartando la variable con la menor correlación con Precio de venta (que es Área de garaje con una correlación Precio de venta de 0,62. GarageCars tiene una correlación con SalePrice de 0,64).
dropVars <- c('YearRemodAdd', 'GarageYrBlt', 'GarageArea', 'GarageCond', 'TotalBsmtSF', 'TotalRmsAbvGrd', 'BsmtFinSF1')
all <- all[,!(names(all) %in% dropVars)]
##Eliminación de valores atípicos
Por el momento, me limito a eliminar manualmente las dos casas realmente grandes con un precio de venta bajo. Sin embargo, tengo la intención de investigar esto más a fondo en una etapa posterior (posiblemente utilizando el paquete “outliers”).
all <- all[-c(524, 1299),]
##Preprocesamiento de las variables predictoras
Antes de modelar necesito centrar y escalar los predictores “numéricos verdaderos” (es decir, las variables que han sido codificadas con etiquetas), y crear variables ficticias para los predictores categóricos. A continuación, divido el marco de datos en uno con todas las variables numéricas (verdaderas) y otro marco de datos con los factores (ordinales).
numericVarNames <- numericVarNames[!(numericVarNames %in% c('MSSubClass', 'MoSold', 'YrSold', 'SalePrice', 'OverallQual', 'OverallCond'))] #numericVarNames was created before having done anything
numericVarNames <- append(numericVarNames, c('Age', 'TotalPorchSF', 'TotBathrooms', 'TotalSqFeet'))
DFnumeric <- all[, names(all) %in% numericVarNames]
DFfactors <- all[, !(names(all) %in% numericVarNames)]
DFfactors <- DFfactors[, names(DFfactors) != 'SalePrice']
cat('There are', length(DFnumeric), 'numeric variables, and', length(DFfactors), 'factor variables')
## There are 30 numeric variables, and 49 factor variables
###Asimetría y normalización de los predictores numéricos
Asimetría La asimetría es una medida de la simetría de una distribución. Un conjunto de datos simétricos tendrá una asimetría igual a 0. Así, una distribución normal tendrá una asimetría de 0. La asimetría mide esencialmente el tamaño relativo de las dos colas. Como regla general, la asimetría debe estar entre -1 y 1. En este rango, los datos se consideran bastante simétricos. Para corregir la asimetría, estoy tomando el logaritmo para todos los predictores numéricos con una asimetría absoluta superior a 0,8 (en realidad: log+1, para evitar problemas de división por cero).
for(i in 1:ncol(DFnumeric)){
if (abs(skew(DFnumeric[,i]))>0.8){
DFnumeric[,i] <- log(DFnumeric[,i] +1)
}
}
Normalizing the data
PreNum <- preProcess(DFnumeric, method=c("center", "scale"))
print(PreNum)
## Created from 2917 samples and 30 variables
##
## Pre-processing:
## - centered (30)
## - ignored (0)
## - scaled (30)
DFnorm <- predict(PreNum, DFnumeric)
dim(DFnorm)
## [1] 2917 30
###One hot encoding las variables categóricas
El último paso necesario para garantizar que todos los predictores se
conviertan en columnas numéricas (lo cual es necesario para la mayoría
de los algoritmos de aprendizaje automático) es “one-hot encode” las
variables categóricas. Esto significa, básicamente, que todos los
valores de los factores (no ordinales) reciben columnas separadas con 1
y 0 (1 significa básicamente Sí/Presente). Para hacer esta one-hot
encoding, estoy utilizando la función model.matrix().
DFdummies <- as.data.frame(model.matrix(~.-1, DFfactors))
dim(DFdummies)
## [1] 2917 201
###Eliminación de niveles con pocas o ninguna observación en el tren o la prueba
En versiones anteriores, trabajé con la función
Varianza cercana a cero de Caret. Aunque esto funciona,
también es una solución rápida y se pierde demasiada información. Por
ejemplo, al utilizar los valores predeterminados, todos los barrios con
menos de 146 casas se omiten como variables (codificadas con un punto)
(relación de frecuencia superior a 95/5). Por lo tanto, he tomado un
enfoque manual más cuidadoso en esta versión.
#check if some values are absent in the test set
ZerocolTest <- which(colSums(DFdummies[(nrow(all[!is.na(all$SalePrice),])+1):nrow(all),])==0)
colnames(DFdummies[ZerocolTest])
## [1] "Condition2RRAe" "Condition2RRAn" "Condition2RRNn"
## [4] "HouseStyle2.5Fin" "RoofMatlMembran" "RoofMatlMetal"
## [7] "RoofMatlRoll" "Exterior1stImStucc" "Exterior1stStone"
## [10] "Exterior2ndOther" "HeatingOthW" "ElectricalMix"
## [13] "MiscFeatureTenC"
DFdummies <- DFdummies[,-ZerocolTest] #removing predictors
#check if some values are absent in the train set
ZerocolTrain <- which(colSums(DFdummies[1:nrow(all[!is.na(all$SalePrice),]),])==0)
colnames(DFdummies[ZerocolTrain])
## [1] "MSSubClass1,5 story PUD all"
DFdummies <- DFdummies[,-ZerocolTrain] #removing predictor
También se eliminan las variables con menos de 10 “unos” en el conjunto de trenes.
fewOnes <- which(colSums(DFdummies[1:nrow(all[!is.na(all$SalePrice),]),])<10)
colnames(DFdummies[fewOnes])
## [1] "MSSubClass1 story unf attic" "LotConfigFR3"
## [3] "NeighborhoodBlueste" "NeighborhoodNPkVill"
## [5] "Condition1PosA" "Condition1RRNe"
## [7] "Condition1RRNn" "Condition2Feedr"
## [9] "Condition2PosA" "Condition2PosN"
## [11] "RoofStyleMansard" "RoofStyleShed"
## [13] "RoofMatlWdShake" "RoofMatlWdShngl"
## [15] "Exterior1stAsphShn" "Exterior1stBrkComm"
## [17] "Exterior1stCBlock" "Exterior2ndAsphShn"
## [19] "Exterior2ndBrk Cmn" "Exterior2ndCBlock"
## [21] "Exterior2ndStone" "FoundationStone"
## [23] "FoundationWood" "HeatingGrav"
## [25] "HeatingWall" "ElectricalFuseP"
## [27] "GarageTypeCarPort" "MiscFeatureOthr"
## [29] "SaleTypeCon" "SaleTypeConLD"
## [31] "SaleTypeConLI" "SaleTypeConLw"
## [33] "SaleTypeCWD" "SaleTypeOth"
## [35] "SaleConditionAdjLand"
DFdummies <- DFdummies[,-fewOnes] #removing predictors
dim(DFdummies)
## [1] 2917 152
En total, he eliminado 49 predictores codificados de un solo golpe con poca o ninguna varianza. Aunque pueda parecer un número significativo, en realidad es mucho menor que el número de predictores que se eliminaron utilizando la función de varianza cercana a cero de caret (utilizando sus umbrales por defecto).
combined <- cbind(DFnorm, DFdummies) #combining all (now numeric) predictors into one dataframe
##Tratamiento de la asimetría de la variable de respuesta
skew(all$SalePrice)
## [1] 1.877427
qqnorm(all$SalePrice)
qqline(all$SalePrice)
El sesgo de 1,87 indica un sesgo a la derecha demasiado alto, y el gráfico Q-Q muestra que los precios de venta tampoco se distribuyen normalmente. Para solucionar esto, estoy tomando el logaritmo de SalePrice.
all$SalePrice <- log(all$SalePrice) #default is the natural logarithm, "+1" is not necessary as there are no 0's
skew(all$SalePrice)
## [1] 0.1213182
Como puedes ver, el sesgo es ahora bastante bajo y el gráfico Q-Q también se ve mucho mejor.
qqnorm(all$SalePrice)
qqline(all$SalePrice)
##Composición de los conjuntos de entrenamiento y prueba
train1 <- combined[!is.na(all$SalePrice),]
test1 <- combined[is.na(all$SalePrice),]
#Modelado
##Modelo de regresión Lasso
También he probado los modelos Ridge y Elastic Net, pero como el lasso da los mejores resultados de esos 3 modelos, sólo mantengo el modelo lasso en el documento.
La penalización de la red elástica está controlada por alfa, y cubre la brecha entre el lazo (alfa=1) y la cresta (alfa=0). El parámetro de ajuste lambda controla la fuerza general de la penalización. Se sabe que la penalización de cresta encoge los coeficientes de los predictores correlacionados entre sí, mientras que el lazo tiende a elegir uno de ellos y descartar los demás.
A continuación, utilizo la validación cruzada de caret para encontrar el mejor valor de lambda, que es el único hiperparámetro que debe ajustarse para el modelo lasso.
set.seed(27042018)
my_control <-trainControl(method="cv", number=5)
lassoGrid <- expand.grid(alpha = 1, lambda = seq(0.001,0.1,by = 0.0005))
lasso_mod <- train(x=train1, y=all$SalePrice[!is.na(all$SalePrice)], method='glmnet', trControl= my_control, tuneGrid=lassoGrid)
lasso_mod$bestTune
## alpha lambda
## 1 1 0.001
min(lasso_mod$results$RMSE)
## [1] 0.1127325
La documentación de la función caret `varImp’ dice: para glmboost y glmnet se utiliza el valor absoluto de los coeficientes correspondientes al modelo ajustado.
Aunque esto significa que no se almacena una clasificación real de las variables más importantes, me da la oportunidad de averiguar cuántas de las variables no se utilizan en el modelo (y por tanto tienen coeficiente 0).
lassoVarImp <- varImp(lasso_mod,scale=F)
lassoImportance <- lassoVarImp$importance
varsSelected <- length(which(lassoImportance$Overall!=0))
varsNotSelected <- length(which(lassoImportance$Overall==0))
cat('Lasso uses', varsSelected, 'variables in its model, and did not select', varsNotSelected, 'variables.')
## Lasso uses 132 variables in its model, and did not select 50 variables.
Así que el lazo hizo lo que se supone que tiene que hacer: parece haber tratado bien la multicolinealidad al no utilizar cerca del 45% de las variables disponibles en el modelo.
LassoPred <- predict(lasso_mod, test1)
predictions_lasso <- exp(LassoPred) #need to reverse the log to the real values
head(predictions_lasso)
## 1461 1462 1463 1464 1465 1466
## 115562.1 162498.8 179170.1 197566.9 205357.5 168499.1
##Modelo XGBoost
Inicialmente, sólo trabajé con el paquete XGBoost directamente. La razón principal para ello era que el paquete utiliza su propia estructura de datos eficiente (xgb.DMatrix). El paquete también proporciona una función de validación cruzada. Sin embargo, esta función de CV sólo determina el número óptimo de rondas, y no admite una búsqueda completa de hiperparámetros.
Aunque caret no parece utilizar la estructura de datos (rápida) del paquete xgb, finalmente decidí hacer el ajuste de hiperparámetros con él de todos modos, ya que al menos soporta una búsqueda de cuadrícula completa. Según tengo entendido, los principales parámetros que hay que ajustar para evitar el sobreajuste son max_depth, y min_child_weight (ver documentación de XGBoost). A continuación estoy configurando una rejilla que ajusta ambos parámetros, y también la eta (tasa de aprendizaje).
xgb_grid = expand.grid(
nrounds = 1000,
eta = c(0.1, 0.05, 0.01),
max_depth = c(2, 3, 4, 5, 6),
gamma = 0,
colsample_bytree=1,
min_child_weight=c(1, 2, 3, 4 ,5),
subsample=1
)
El siguiente paso es dejar que caret encuentre los mejores valores de los hiperparámetros (utilizando una validación cruzada de 5 veces).
#xgb_caret <- train(x=train1, y=all$SalePrice[!is.na(all$SalePrice)], method='xgbTree', trControl= my_control, tuneGrid=xgb_grid)
#xgb_caret$bestTune
Como era de esperar, esto tomó bastante tiempo (localmente). Como quiero limitar el tiempo de ejecución en Kaggle, deshabilité el código, y sólo continúo con los resultados. Según caret, los parámetros de ‘bestTune’ son:
En el resto de esta sección, seguiré trabajando con el paquete xgboost directamente. A continuación, empiezo con la preparación de los datos en el formato recomendado.
label_train <- all$SalePrice[!is.na(all$SalePrice)]
# put our testing & training data into two seperates Dmatrixs objects
dtrain <- xgb.DMatrix(data = as.matrix(train1), label= label_train)
dtest <- xgb.DMatrix(data = as.matrix(test1))
Además, tomo los valores mejor sintonizados de la validación cruzada del caret.
default_param<-list(
objective = "reg:linear",
booster = "gbtree",
eta=0.05, #default = 0.3
gamma=0,
max_depth=3, #default=6
min_child_weight=4, #default=1
subsample=1,
colsample_bytree=1
)
El siguiente paso es hacer una validación cruzada para determinar el mejor número de rondas (para el conjunto de parámetros dado).
xgbcv <- xgb.cv( params = default_param, data = dtrain, nrounds = 500, nfold = 5, showsd = T, stratified = T, print_every_n = 40, early_stopping_rounds = 10, maximize = F)
## [19:12:55] WARNING: amalgamation/../src/objective/regression_obj.cu:203: reg:linear is now deprecated in favor of reg:squarederror.
## [19:12:55] WARNING: amalgamation/../src/objective/regression_obj.cu:203: reg:linear is now deprecated in favor of reg:squarederror.
## [19:12:55] WARNING: amalgamation/../src/objective/regression_obj.cu:203: reg:linear is now deprecated in favor of reg:squarederror.
## [19:12:55] WARNING: amalgamation/../src/objective/regression_obj.cu:203: reg:linear is now deprecated in favor of reg:squarederror.
## [19:12:55] WARNING: amalgamation/../src/objective/regression_obj.cu:203: reg:linear is now deprecated in favor of reg:squarederror.
## [1] train-rmse:10.955589+0.004521 test-rmse:10.955560+0.019198
## Multiple eval metrics are present. Will use test_rmse for early stopping.
## Will train until test_rmse hasn't improved in 10 rounds.
##
## [41] train-rmse:1.428255+0.000799 test-rmse:1.429178+0.017698
## [81] train-rmse:0.219896+0.000924 test-rmse:0.232053+0.013013
## [121] train-rmse:0.102608+0.002015 test-rmse:0.130365+0.011149
## [161] train-rmse:0.090555+0.002014 test-rmse:0.123322+0.011224
## [201] train-rmse:0.084558+0.002009 test-rmse:0.120920+0.011448
## [241] train-rmse:0.079900+0.001708 test-rmse:0.119551+0.011576
## [281] train-rmse:0.076070+0.001421 test-rmse:0.118870+0.011577
## [321] train-rmse:0.073007+0.001213 test-rmse:0.118383+0.011633
## [361] train-rmse:0.070253+0.001015 test-rmse:0.117983+0.011803
## Stopping. Best iteration:
## [358] train-rmse:0.070455+0.001041 test-rmse:0.117967+0.011768
Aunque ha costado un poco de trabajo, el ajuste de los hiperparámetros ha merecido la pena, ya que el RMSE validado de forma cruzada ha mejorado considerablemente (de 0,1225 sin el ajuste de caret, a 0,1162 en esta versión).
#train the model using the best iteration found by cross validation
xgb_mod <- xgb.train(data = dtrain, params=default_param, nrounds = 454)
## [19:13:01] WARNING: amalgamation/../src/objective/regression_obj.cu:203: reg:linear is now deprecated in favor of reg:squarederror.
XGBpred <- predict(xgb_mod, dtest)
predictions_XGB <- exp(XGBpred) #need to reverse the log to the real values
head(predictions_XGB)
## [1] 116387.0 162307.1 186493.8 187440.4 187258.2 166241.1
#view variable importance plot
library(Ckmeans.1d.dp) #required for ggplot clustering
## Warning: package 'Ckmeans.1d.dp' was built under R version 4.1.3
mat <- xgb.importance (feature_names = colnames(train1),model = xgb_mod)
xgb.ggplot.importance(importance_matrix = mat[1:20], rel_to_first = TRUE)
##Promedio de predicciones
Dado que los algoritmos de lazo y XGBoost son muy diferentes, promediar las predicciones probablemente mejore las puntuaciones. Como el modelo de lazo es mejor en cuanto a la puntuación de RMSE validada (0,1121 frente a 0,1162), estoy ponderando el modelo de lazo al doble.
sub_avg <- data.frame(Id = test_labels, SalePrice = (predictions_XGB+2*predictions_lasso)/3)
head(sub_avg)
## Id SalePrice
## 1461 1461 115837.0
## 1462 1462 162434.9
## 1463 1463 181611.3
## 1464 1464 194191.4
## 1465 1465 199324.4
## 1466 1466 167746.4
write.csv(sub_avg, file = 'average.csv', row.names = F)