In this project you will be tasked to use machine learning and use the methods we have seen on the course on a realistic data set prepared for this course. We will guide you all along with some elements and provide some code already prepared. We propose a workflow with several guided step where you will fill-in some blanks and interpret the results.
Since fitting the Random Forest model with a large amount of observations and variables can be very computationally expensive, in the first part of the project you will explore the data and fit a LASSO model. Using these results you’ll have the possibility to select the variables to include in the Random Forest model.
We will provide you with some hyperparameters that you can use to prevent the training taking too long, as the main goal of the project is to become comfortable in running the code and interpreting the results.
The general workflow for this analysis follows these main steps:
All along this analysis we will guide you with some chunks of code and help you with the choice of hyperparameters and useful functions.
The data set can be used directly from the SIAP’s server. If you need, you can also download it an use it locally.
# Reading DHS survey data from SIAP's website
df <- read.csv("https://www.unsiap.or.jp/on_line/ML/M6-clean_data.csv")
If you feel like taking the exercise a bit further, you are of course encouraged to try to play around with some of the default parameters we set to see if you can improve the results!
The data set is a record of renting prices for different types of housing options. It contains 48751 observations and 14 variables.
Since some are categorical, and other are numerical, you can visualize their properties and statistical summaries separately. All variables are self-explanatory and their meaning can be understood quite easily.
datasummary_skim(df, type = "numeric")
| Unique (#) | Missing (%) | Mean | SD | Min | Median | Max | ||
|---|---|---|---|---|---|---|---|---|
| price | 649 | 0 | 163.7 | 421.8 | 0.0 | 105.0 | 10000.0 | |
| latitude | 18964 | 0 | 40.7 | 0.1 | 40.5 | 40.7 | 40.9 | |
| longitude | 14790 | 0 | -74.0 | 0.0 | -74.2 | -74.0 | -73.7 | |
| accommodates | 19 | 0 | 2.9 | 1.9 | 1.0 | 2.0 | 22.0 | |
| bedrooms | 12 | 0 | 1.2 | 0.8 | 0.0 | 1.0 | 21.0 | |
| beds | 17 | 0 | 1.5 | 1.1 | 0.0 | 1.0 | 16.0 | |
| cleaning_fee | 214 | 0 | 56.2 | 66.3 | 0.0 | 40.0 | 1200.0 | |
| minimum_nights | 110 | 0 | 7.9 | 21.6 | 1.0 | 3.0 | 1250.0 | |
| availability_365 | 366 | 0 | 120.7 | 140.4 | 0.0 | 63.0 | 365.0 | |
| review_scores_rating | 62 | 0 | 94.2 | 8.0 | 20.0 | 96.0 | 100.0 |
datasummary_skim(df, type = "categorical" )
| N | % | ||
|---|---|---|---|
| host_is_superhost | False | 39055 | 80.1 |
| True | 9696 | 19.9 | |
| neighborhood | A-Zone | 19604 | 40.2 |
| B-Zone | 1149 | 2.4 | |
| M-Zone | 21778 | 44.7 | |
| Q-Zone | 5859 | 12.0 | |
| S-Zone | 361 | 0.7 | |
| property_type | Appartment | 41746 | 85.6 |
| House | 5702 | 11.7 | |
| PrivateRoom | 1303 | 2.7 | |
| cancellation_policy | flexible | 15540 | 31.9 |
| moderate | 11323 | 23.2 | |
| strict | 21888 | 44.9 |
It is always a good practice to see whether some variables are correlated.
numeric <- select_if(df, is.numeric)
# We compute the correlation matrix of the covariates
corr_coef<-cor(numeric ,use = "p")
#And then plot it with nice options
ggcorrplot(corr_coef,
type = "lower", # lower triangle of the matrix only
hc.order = TRUE, # variable sorted from highest to lowest
outline.col = "white", #Color options
lab = TRUE) + ggtitle("Correlation between numerical variables")
To do: Interpret the results of the data exploration, is there anything that stands out in some of the plots? did you learn anything about the data that gives you an insight into if some variables will be more useful or less practical to include in the model?
With 14 variables and considering the correlation between the numerical variables, it may be interesting to try to reduce the number of regressors using a penalization method such as the LASSO.
Since the data set is quite large we only use half the observations (p = 0.5 in createDataPartition() controls this) for training the models, this will reduce the time you need to train. This can of course be increased or lowered if you want to experiment on the effect this has on your models.
# Splits data into training and testing sets
set.seed(777)
trainIndex <- createDataPartition(df$price, p = 0.5, list = FALSE, times = 1)
One must also prepare our Machine Learning framework and prepare our train data set separately from the one used for validation
train_data <- df[trainIndex,]
validation_data <- df[-trainIndex,]
As we have seen in the course, the LASSO method will be affected by the scale of the variables, so we need to scale the data set before we start. Of course this should apply to both our train and validation data sets.
# Scale the training and test data based on the training data mean and variance.
ScalingValues <- preProcess(train_data, method = c("center", "scale"))
train_data <- predict(ScalingValues, train_data)
validation_data <- predict(ScalingValues, validation_data)
Now we can fit the a regression model with LASSO regularization and interpret the results, this may give us some information about which variables might be useful in the Random Forest model later. We fit the regression model using a using cross-validation to ensure it is giving robust results.
As in many Machine Learning exercise, one must select hyperparameters to adjust the procedure and to ensure the best accuracy and performance. Here we want to select the regression model with a good balance of accuracy and a minimal number of regressors to avoid collinearity and over-fitting.
The grid search for the lambda parameter in the LASSO is tuned here between 0 and 0.003, in 5 regular increments. We also limited the number of models to 10. You may want to play with these parameters.
# Control variables
numbers <- 5
repeats <- 20
rcvTunes <- 10 # tune number of models
seed <- 123
# repeated cross validation
rcvSeeds <- setSeeds(method = "repeatedcv",
numbers = numbers, repeats = repeats,
tunes = rcvTunes, seed = seed)
# Controls for the CV
rcvControl <- trainControl(method = "repeatedcv",
number = numbers, repeats = repeats,
seeds = rcvSeeds)
set.seed(123)
lasso_fit <- train(price ~ .,
data = train_data,
method = "glmnet",
tuneGrid = expand.grid(alpha = 1,
# grid to search
lambda = seq(from =0,
to=0.003,
length = 5)),
trControl = rcvControl)
It always a good practice to visualize the outcome of the grid search to check whether the value of the hyperparameter (lambda here) affects the outcome and if there exist a value that is optimal.
ggplot(lasso_fit) +
ggtitle("Lasso Penalization") +
labs(x = "Regularization parameter (Lambda)")+
theme_minimal()
cbind(lasso_fit$bestTune$lambda) %>%
kable(digits=3, col.names = c("lambda (exp)")) %>%
kable_styling()
| lambda (exp) |
|---|
| 0.001 |
It appears that the best value for lambda is here 7.5^{-4}. (This result is automatically updated when you knit your notebook.)
Since our goal is to feed a Random Forest model, we can use the previous results and identify the variables and features selected by the LASSO procedure (when used with the right lambda). This can also be done by visualizing the feature importance of the LASSO regression:
We can first focus on the most important features (here selecting only features importance greater than 3) and refine the analysis later. Be careful that this may require some time.
theme_models <- theme_minimal()+ theme(plot.title = element_text(hjust = 0.5),
legend.position = "none")
lasso_varImp <- data.frame(variables = row.names(varImp(lasso_fit)$importance), varImp(lasso_fit)$importance)
# Below we set that we only show feature importance with a value larger than 3
# You can lower this if you want to see more variables, or increase it if you want to see fewer.
threshold = 2
lasso_varImp <- lasso_varImp[lasso_varImp$Overall > threshold,]
ggplot(data = lasso_varImp, mapping = aes(x=reorder(variables, Overall),
y=Overall,
fill=variables)) +
coord_flip() + geom_bar(stat = "identity", position = "dodge") +
theme_models +
labs(x = "", y = "") +
ggtitle("Feature Importance Lasso Regression")
To do: Interpret the results of the data exploration, is there anything that stands out in some of the plots? did you learn anything about the data that gives you an insight into if some variables will be more useful or less practical to include in the model?
We can also look at the models out of sample Root Mean Squared Error (RMSE), and compare this later to the results of the Random Forest:
lasso_preds <- predict(lasso_fit, validation_data)
rmse(actual = validation_data$price, predicted = lasso_preds)
## [1] 0.9991186
We are now ready to give it a try and fit the Random Forest, but for this we need to specify some variables.
So far we have only run with the formula y ~ ., this means that we give the algorithm access to all variables in the data set to fit the model. Based on the results so far in the project, it would be good to select a formula which you think could give good results (see below).1
To do: Select your variables for the model!
We have set the number of trees to 10 only, which is way too restrictive. However, this allows to let you try and keep the training relatively fast (it may still take some time though!). Based on the variable importance in the LASSO regression and which variables had the highest correlations with price, a good start is to use the model: \[ price = property\_type + neighborhood + accommodates + bedrooms + cancellation\_policy + cleaning\_fee \]
as this contains what seems to be the most important variables, and it will not be too complex to fit.
Remember that finding a good model can be an iterative process, don’t be discouraged if you don’t immediately find one that performs as well as you hoped.
# Modify this formula (keep the syntax!) to choose your regressors
MyRegressors <- "property_type + neighborhood + accommodates +
bedrooms +cancellation_policy + cleaning_fee"
Now, you can experiment with some other hyperparameters such as the number of trees used in the Random Forest model, as we have seen that this may be crucial in the performances of a Random Forest model.
To do: Change the number of trees (let’s say to 50 or 100), and run more experiments.
# Training this model may take some time!!!
Ntree <-10 # The number of trees affect greatly the duration of the process
#Formula using the regressors' list defined above
MyFormula <- as.formula(paste('price ~', MyRegressors))
# Change the formula below:
rf_fit <- train(MyFormula,
data = train_data,
method = "rf",
ntree = Ntree)
rf_fit
## Random Forest
##
## 24376 samples
## 6 predictor
##
## No pre-processing
## Resampling: Bootstrapped (25 reps)
## Summary of sample sizes: 24376, 24376, 24376, 24376, 24376, 24376, ...
## Resampling results across tuning parameters:
##
## mtry RMSE Rsquared MAE
## 2 0.9370730 0.1369970 0.2084868
## 6 0.9300933 0.1491148 0.2105905
## 11 0.9393542 0.1419820 0.2162355
##
## RMSE was used to select the optimal model using the smallest value.
## The final value used for the model was mtry = 6.
rf_preds <- predict(rf_fit, validation_data)
rmse(actual = validation_data$price, predicted = rf_preds)
## [1] 0.986717
With this model we find some slight improvement over the LASSO in terms of the RMSE.
It is now interesting to see if the feature importance of the two different approaches are similar or not:
theme_models <- theme_minimal()+ theme(plot.title = element_text(hjust = 0.5),
legend.position = "none")
rf_varImp <- data.frame(variables = row.names(varImp(rf_fit)$importance), varImp(rf_fit)$importance)
ggplot(data = rf_varImp, mapping = aes(x=reorder(variables, Overall),
y=Overall,
fill=variables)) +
coord_flip() + geom_bar(stat = "identity", position = "dodge") +
theme_models +
labs(x = "", y = "") +
ggtitle("Feature Importance Random Forest")
To do: Comment on your models performance, and its feature importance. Was the feature importance what you expected? Did the results of your model match what the results that you saw from the LASSO? Which is the best model?
You can also use a graphical comparison of the performances of the two models!
# This graphics allows the comparison of results (on the SAME scale)
# even if number of CV samples are different
lasso <- as.data.frame(lasso_fit$resample$RMSE)
rf <- as.data.frame(rf_fit$resample$RMSE)
y.min <- min(lasso, rf)
y.max <- max(lasso, rf)
p.lasso <- ggplot(lasso) +
aes(x = "", y = `lasso_fit$resample$RMSE`) +
geom_boxplot(fill = SIAP.color) +
labs( x = "LASSO", y= "RMSE") +
coord_cartesian(ylim = c(y.min, y.max))+
theme_minimal()
rf <- as.data.frame(rf_fit$resample$RMSE)
p.rf <- ggplot(rf) +
aes(x = "", y = `rf_fit$resample$RMSE`) +
geom_boxplot(fill = orange.color) +
coord_cartesian(ylim = c(y.min, y.max))+
labs( x = "Random Forest", y ="")+
theme_minimal()
grid.newpage()
grid.draw(cbind(ggplotGrob(p.lasso), ggplotGrob(p.rf), size = "last"))
If you want to run with a specific formula for the model you can provide it in the following way: price ~ bedrooms + cleaning_fee + more variables…, etc.↩︎