<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Ranger | Nitin Gupta</title>
    <link>https://www.nitingupta.com/tags/ranger/</link>
      <atom:link href="https://www.nitingupta.com/tags/ranger/index.xml" rel="self" type="application/rss+xml" />
    <description>Ranger</description>
    <generator>Source Themes Academic (https://sourcethemes.com/academic/)</generator><language>en-us</language><copyright>© Nitin Gupta. All Rights Reserved.</copyright><lastBuildDate>Mon, 26 Dec 2016 00:00:00 +0000</lastBuildDate>
    <image>
      <url>https://www.nitingupta.com/img/icon-192.png</url>
      <title>Ranger</title>
      <link>https://www.nitingupta.com/tags/ranger/</link>
    </image>
    
    <item>
      <title>Ames Housing - Part 2 - Building Models</title>
      <link>https://www.nitingupta.com/casestudies/ames-housing-part2-models/</link>
      <pubDate>Mon, 26 Dec 2016 00:00:00 +0000</pubDate>
      <guid>https://www.nitingupta.com/casestudies/ames-housing-part2-models/</guid>
      <description>


&lt;p&gt;In a &lt;a href=&#34;https://www.nitingupta.com/casestudies/ames-housing-part1-eda/&#34;&gt;previous post&lt;/a&gt; in this series, we did an exploratory data analysis of the &lt;a href=&#34;http://www.amstat.org/publications/jse/v19n3/decock.pdf&#34;&gt;Ames Housing dataset&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In this post, we will build both linear and non-linear models and see how well they predict the &lt;code&gt;SalePrice&lt;/code&gt; of properties.&lt;/p&gt;
&lt;div id=&#34;evaluation-criteria&#34; class=&#34;section level2&#34;&gt;
&lt;h2&gt;Evaluation Criteria&lt;/h2&gt;
&lt;p&gt;Root-Mean-Squared-Error (RMSE) between the logarithm of the predicted value and the logarithm of the observed &lt;code&gt;SalePrice&lt;/code&gt; will be our evaluation criteria. Taking the log ensures that errors in predicting expensive and cheap houses will affect the result equally.&lt;/p&gt;
&lt;/div&gt;
&lt;div id=&#34;steps-for-building-models&#34; class=&#34;section level2&#34;&gt;
&lt;h2&gt;Steps for Building Models&lt;/h2&gt;
&lt;p&gt;Here are the steps for building models and determining the best hyperparameter combinations by K-fold cross validation:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Partition the training dataset into model training and validation sets. Use stratified sampling such that each partition has a similar distribution of the target variable - &lt;code&gt;SalePrice&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Define linear and non-linear models.&lt;/li&gt;
&lt;li&gt;For each model, create a space-filling (maximum entropy) grid of hyperparameter combinations.&lt;/li&gt;
&lt;li&gt;For each hyperparameter combination, fit a model on the training set and make predictions on the validation set. Repeat the process for all folds.&lt;/li&gt;
&lt;li&gt;Determine root mean squared errors (RMSE) and choose the best hyperparameter combination that corresponds to the minimum RMSE.&lt;/li&gt;
&lt;li&gt;Train each model with its best hyperparameter combination on the entire training set.&lt;/li&gt;
&lt;li&gt;Calculate RMSE of the each finalized model on the testing set.&lt;/li&gt;
&lt;li&gt;Finally, choose the best model that gives the least RMSE.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div id=&#34;partitioning-training-data&#34; class=&#34;section level2&#34;&gt;
&lt;h2&gt;Partitioning Training Data&lt;/h2&gt;
&lt;p&gt;We split the training data into 5 folds. Within each fold, 80% of the data is used for training models and 20% for validating the predicted values against the actual values.&lt;/p&gt;
&lt;p&gt;Let’s look at the distribution of the target variable across all folds:&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;cv_folds %&amp;gt;% 
  mutate(target = map(splits, function(Z) bind_rows(training(Z) %&amp;gt;% select(target) %&amp;gt;% mutate(type = &amp;quot;training&amp;quot;), 
                                                    testing(Z) %&amp;gt;% select(target) %&amp;gt;% mutate(type = &amp;quot;validation&amp;quot;)))) %&amp;gt;% 
  select(-splits) %&amp;gt;% 
  unnest(cols = c(target)) %&amp;gt;% 
  ggplot(aes(x = target)) +
  geom_histogram(bins = 30, color = &amp;quot;white&amp;quot;, fill = &amp;quot;#F37735&amp;quot;) +
  labs(x = &amp;quot;log(SalePrice)&amp;quot;) +
  facet_grid(type ~ id)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&#34;https://www.nitingupta.com/casestudies/ames-housing-part2-models/index_files/figure-html/plot_target_partitioning-1.png&#34; alt=&#34;&#34; width=&#34;672&#34; /&gt;&lt;/p&gt;
&lt;p&gt;By using stratified sampling, we ensure that the training and validation distributions of the target variable are similar.&lt;/p&gt;
&lt;/div&gt;
&lt;div id=&#34;linear-models&#34; class=&#34;section level2&#34;&gt;
&lt;h2&gt;Linear Models&lt;/h2&gt;
&lt;div id=&#34;ordinary-least-squares-regression&#34; class=&#34;section level3&#34;&gt;
&lt;h3&gt;Ordinary Least Squares Regression&lt;/h3&gt;
&lt;p&gt;Before creating any new features or indulging in more complex modelling methods, we will cross validate a simple linear model on the training data to establish a benchmark. If more complex approaches do not have a significant improvement in the model validation metrics, then they are not worthwhile to be pursued.&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;model_spec_lm&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;Linear Regression Model Specification (regression)

Computational engine: lm &lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;cv_results_lm&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;# A tibble: 1 × 1
  mean_rmse
      &amp;lt;dbl&amp;gt;
1     0.147&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;rmse_val &amp;lt;- cv_results_lm %&amp;gt;% pull(mean_rmse)&lt;/code&gt;&lt;/pre&gt;
&lt;div id=&#34;whats-notable&#34; class=&#34;section level4&#34;&gt;
&lt;h4&gt;What’s notable?&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;After training a linear model on all predictors, we get an RMSE of &lt;strong&gt;0.1468&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;This is the simplest and fastest model with no hyperparameters to tune.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&#34;regularized-linear-model&#34; class=&#34;section level3&#34;&gt;
&lt;h3&gt;Regularized Linear Model&lt;/h3&gt;
&lt;p&gt;We will use &lt;code&gt;glmnet&lt;/code&gt; that uses LASSO and Ridge Regression with regularization. We will do a grid search of the following hyperparameters that minimize RMSE:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;penalty&lt;/code&gt;: The total amount of regularization in the model.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mixture&lt;/code&gt;: The proportion of L1 regularization in the model.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;model_spec_glmnet&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;Linear Regression Model Specification (regression)

Main Arguments:
  penalty = tune()
  mixture = tune()

Computational engine: glmnet &lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let’s take a look at the top 10 RMSE values and hyperparameter combinations:&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;cv_results_glmnet %&amp;gt;% head(10)&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;# A tibble: 10 × 3
    penalty mixture mean_rmse
      &amp;lt;dbl&amp;gt;   &amp;lt;dbl&amp;gt;     &amp;lt;dbl&amp;gt;
 1 1.46e- 2  0.297      0.128
 2 1.22e- 2  0.657      0.129
 3 1.22e- 3  0.490      0.133
 4 1.09e- 2  0.0439     0.133
 5 3.47e- 4  0.992      0.134
 6 8.18e- 4  0.0856     0.137
 7 1.15e- 4  0.798      0.138
 8 7.37e- 7  0.0221     0.138
 9 1.23e-10  0.333      0.138
10 2.07e- 9  0.950      0.138&lt;/code&gt;&lt;/pre&gt;
&lt;div id=&#34;whats-notable-1&#34; class=&#34;section level4&#34;&gt;
&lt;h4&gt;What’s notable?&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;After hyperparameter tuning with cross validation, the best &lt;code&gt;glmnet&lt;/code&gt; model gives an RMSE of 0.128 with penalty = 0.0146 and mixture = 0.2974.&lt;/li&gt;
&lt;li&gt;It is a significant improvement over Ordinary Least Squares regression that had an RMSE of 0.1468.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;glmnet&lt;/code&gt; cross validation is fast, taking about 13 seconds to execute.&lt;/li&gt;
&lt;li&gt;But the presence of outliers can significantly affect its performance.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here a plot of the &lt;code&gt;glmnet&lt;/code&gt; hyperparameter grid along with the best hyperparameter combination:&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;eda_plot_scatter(param_grid_glmnet, &amp;quot;mixture&amp;quot;, &amp;quot;penalty&amp;quot;, alpha = 1, add_lm = F, yLog = T, yLabelsScale = scales::scientific) +
  geom_point(data = param_best_glmnet, aes(x = mixture, y = penalty), color = &amp;quot;red&amp;quot;) +
  ggrepel::geom_label_repel(data = param_best_glmnet %&amp;gt;% map_df(round, digits = 4), 
                            aes(x = mixture, y = penalty, label = glue::glue(&amp;quot;penalty: {penalty}, mixture:{mixture}&amp;quot;)),
                            family = &amp;quot;Roboto Condensed&amp;quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&#34;https://www.nitingupta.com/casestudies/ames-housing-part2-models/index_files/figure-html/plot_glmnet-1.png&#34; alt=&#34;&#34; width=&#34;672&#34; /&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&#34;non-linear-models&#34; class=&#34;section level2&#34;&gt;
&lt;h2&gt;Non-linear Models&lt;/h2&gt;
&lt;p&gt;Next, we will train a couple of tree-based algorithms, which are not very sensitive to outliers and skewed data.&lt;/p&gt;
&lt;div id=&#34;ranger-random-forest&#34; class=&#34;section level3&#34;&gt;
&lt;h3&gt;&lt;em&gt;ranger&lt;/em&gt; (Random Forest)&lt;/h3&gt;
&lt;p&gt;We use the &lt;code&gt;ranger&lt;/code&gt; implementation of random forests, which gives the same results as the &lt;code&gt;randomForest&lt;/code&gt; package but runs about 3 times faster. In each ensemble, we have 500 trees (random forest error levels off well before that on a dataset of this size) and do a grid search of the following hyperparameters:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;mtry&lt;/code&gt;: The number of predictors to randomly sample at each split.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;min_n&lt;/code&gt;: The minimum number of data points in a node required to further split the node.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;model_spec_rf&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;Random Forest Model Specification (regression)

Main Arguments:
  mtry = tune()
  trees = 500
  min_n = tune()

Engine-Specific Arguments:
  num.threads = 1

Computational engine: ranger &lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let’s take a look at the top 10 RMSE values and hyperparameter combinations:&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;cv_results_rf %&amp;gt;% head(10)&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;# A tibble: 10 × 3
   min_n  mtry mean_rmse
   &amp;lt;int&amp;gt; &amp;lt;int&amp;gt;     &amp;lt;dbl&amp;gt;
 1     2    84     0.134
 2     5   134     0.136
 3     9    36     0.136
 4    12   110     0.136
 5    15    55     0.136
 6    18   136     0.137
 7     8   199     0.138
 8    22    69     0.138
 9    19   179     0.139
10    29   123     0.139&lt;/code&gt;&lt;/pre&gt;
&lt;div id=&#34;whats-notable-2&#34; class=&#34;section level4&#34;&gt;
&lt;h4&gt;What’s notable?&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;After cross validation, we get the best RMSE of 0.134 with mtry = 84 and min_n = 2.&lt;/li&gt;
&lt;li&gt;This is no improvement in RMSE compared to &lt;code&gt;glmnet&lt;/code&gt;, and &lt;code&gt;ranger&lt;/code&gt; cross validation takes much longer to execute (about 371 seconds, versus 13 seconds for &lt;code&gt;glmnet&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here a plot of the &lt;code&gt;ranger&lt;/code&gt; hyperparameter grid along with the best hyperparameter combination:&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;eda_plot_scatter(param_grid_rf, &amp;quot;min_n&amp;quot;, &amp;quot;mtry&amp;quot;, alpha = 1, add_lm = F) +
  geom_point(data = param_best_rf, aes(x = min_n, y = mtry), color = &amp;quot;red&amp;quot;) +
  ggrepel::geom_label_repel(data = param_best_rf, aes(x = min_n, y = mtry, label = glue::glue(&amp;quot;mtry: {mtry}, min_n:{min_n}&amp;quot;)), family = &amp;quot;Roboto Condensed&amp;quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&#34;https://www.nitingupta.com/casestudies/ames-housing-part2-models/index_files/figure-html/plot_randomForest-1.png&#34; alt=&#34;&#34; width=&#34;672&#34; /&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&#34;xgboost&#34; class=&#34;section level3&#34;&gt;
&lt;h3&gt;&lt;em&gt;xgboost&lt;/em&gt;&lt;/h3&gt;
&lt;p&gt;In each ensemble we have 1000 trees and do a grid search of the following hyperparameters:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;min_n&lt;/code&gt;: The minimum number of data points in a node required to further split the node.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;tree_depth&lt;/code&gt;: The maximum depth or the number of splits of the tree.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;learn_rate&lt;/code&gt;: The rate at which the boosting algorithm adapts from one iteration to another.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;model_spec_xgboost&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;Boosted Tree Model Specification (regression)

Main Arguments:
  trees = 1000
  min_n = tune()
  tree_depth = tune()
  learn_rate = tune()

Engine-Specific Arguments:
  objective = reg:squarederror
  nthread = 1

Computational engine: xgboost &lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let’s take a look at the top 10 RMSE values and hyperparameter combinations:&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;cv_results_xgboost %&amp;gt;% head(10)&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;# A tibble: 10 × 4
   min_n tree_depth learn_rate mean_rmse
   &amp;lt;int&amp;gt;      &amp;lt;int&amp;gt;      &amp;lt;dbl&amp;gt;     &amp;lt;dbl&amp;gt;
 1    33          2    0.0289      0.126
 2    23          4    0.0254      0.127
 3     3          2    0.0171      0.127
 4    24          1    0.190       0.128
 5    38          9    0.0270      0.130
 6     4          1    0.0872      0.131
 7    23          9    0.0405      0.131
 8    32          9    0.00605     0.131
 9    19         11    0.00482     0.131
10    11          5    0.0549      0.131&lt;/code&gt;&lt;/pre&gt;
&lt;div id=&#34;whats-notable-3&#34; class=&#34;section level4&#34;&gt;
&lt;h4&gt;What’s notable?&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;After cross validation, we get the best RMSE of 0.126 with min_n = 33, tree_depth = 2 and learn_rate = 0.0289.&lt;/li&gt;
&lt;li&gt;This is the best cross validated RMSE of all the models, slightly ahead of &lt;code&gt;glmnet&lt;/code&gt; (0.128) and &lt;code&gt;ranger&lt;/code&gt; (0.134).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;xgboost&lt;/code&gt; cross validation takes longer to execute than that of &lt;code&gt;glmnet&lt;/code&gt;, but is faster than that of &lt;code&gt;ranger&lt;/code&gt; (about 168 seconds).&lt;/li&gt;
&lt;/ul&gt;
&lt;!-- Here a 3D plot of the `xgboost` hyperparameter grid: --&gt;
&lt;!-- &lt;center&gt; --&gt;
&lt;!-- ```{r plot_xgboost} --&gt;
&lt;!-- library(plotly) --&gt;
&lt;!-- plot_ly(param_grid_xgboost, x = ~min_n, y = ~tree_depth, z = ~learn_rate) %&gt;% --&gt;
&lt;!--   add_markers() %&gt;% --&gt;
&lt;!--   layout(font = list(family = &#34;Roboto Condensed&#34;), --&gt;
&lt;!--          title = list(text = &#34;Scatterplot of min_n, tree_depth and learn_rate&#34;, font = list(size = 22)), --&gt;
&lt;!--          scene = list(xaxis = list(title = &#39;min_n&#39;), --&gt;
&lt;!--                       yaxis = list(title = &#39;tree_depth&#39;), --&gt;
&lt;!--                       zaxis = list(title = &#39;learn_rate&#39;))) --&gt;
&lt;!-- ``` --&gt;
&lt;!-- &lt;/center&gt; --&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&#34;finalizing-models&#34; class=&#34;section level2&#34;&gt;
&lt;h2&gt;Finalizing Models&lt;/h2&gt;
&lt;p&gt;For each model, we found the combination of hyperparameters that minimize RMSE. Using those parameters, we can now train the same models on the entire training dataset. Finally, we can use the trained models to predict log(SalePrice) on the entire training set to see the actual v/s predicted log(SalePrice) results.&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;# Get predicted estimates
get_predicted &amp;lt;- function(model_final, dfNew) {

  # Predict on testing set
  vEstimates &amp;lt;- model_final %&amp;gt;%
    predict(new_data = dfNew) %&amp;gt;%
    pull(.pred)
  
  return(vEstimates)
}

dfResults_train &amp;lt;- dfTrain %&amp;gt;% 
  mutate(actual = log(SalePrice)) %&amp;gt;% 
  select(Id, actual) %&amp;gt;% 
  mutate(glmnet = get_predicted(model_final = model_final_glmnet, dfTrain_Proc),
         ranger = get_predicted(model_final = model_final_rf, dfTrain_Proc),
         xgboost = get_predicted(model_final = model_final_xgboost, dfTrain_Proc))&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;dfPlot &amp;lt;- dfResults_train %&amp;gt;% 
  gather(model, predicted, -actual, -Id) %&amp;gt;% 
  mutate(model = ordered(model, levels = c(&amp;quot;glmnet&amp;quot;,&amp;quot;ranger&amp;quot;,&amp;quot;xgboost&amp;quot;)))

dfPlot %&amp;gt;%
  rename(`log(SalePrice)` = actual) %&amp;gt;%
  eda_plot_actual_vs_predicted(&amp;quot;log(SalePrice)&amp;quot;, &amp;quot;predicted&amp;quot;, feature_model = &amp;quot;model&amp;quot;, feature_id = &amp;quot;Id&amp;quot;, nLabel = 3) +
  labs(title = &amp;quot;Actual v/s predicted log(SalePrice) on training set&amp;quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&#34;https://www.nitingupta.com/casestudies/ames-housing-part2-models/index_files/figure-html/plot_train-1.png&#34; alt=&#34;&#34; width=&#34;960&#34; /&gt;&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;# properties among the 3 largest errors of every model
vWorst_train &amp;lt;- dfPlot %&amp;gt;% 
  group_by(model) %&amp;gt;% 
  slice_max(abs(predicted - actual), n = 3) %&amp;gt;% 
  ungroup() %&amp;gt;% 
  count(Id) %&amp;gt;% 
  filter(n == n_distinct(dfPlot$model)) %&amp;gt;% 
  pull(Id)&lt;/code&gt;&lt;/pre&gt;
&lt;div id=&#34;whats-notable-4&#34; class=&#34;section level4&#34;&gt;
&lt;h4&gt;What’s notable?&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Both &lt;code&gt;ranger&lt;/code&gt; and &lt;code&gt;xgboost&lt;/code&gt; models fit the training data closely with the tuned parameters, as the predictions lie close to the straight line drawn at 45 degrees. &lt;code&gt;ranger&lt;/code&gt; fits it most closely, with a training RMSE of 0.051 versus 0.094 for &lt;code&gt;xgboost&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;glmnet&lt;/code&gt; model shows a couple of outliers with Ids &lt;strong&gt;524&lt;/strong&gt; and &lt;strong&gt;1299&lt;/strong&gt; whose predicted values are far in excess of their actual values. Even properties whose &lt;code&gt;SalePrice&lt;/code&gt; is at the lower end, show a wide dispersion in prediced values.&lt;/li&gt;
&lt;li&gt;The plots label the three largest errors of each model. The property with Id &lt;strong&gt;633&lt;/strong&gt; is among them for all three models, so it is worth checking for errors in its data.&lt;/li&gt;
&lt;li&gt;But the true performance can only be measured on unseen testing data.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&#34;performance-on-test-data&#34; class=&#34;section level2&#34;&gt;
&lt;h2&gt;Performance on Test Data&lt;/h2&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;dfResults_test &amp;lt;- dfTest %&amp;gt;% 
  mutate(actual = log(SalePrice)) %&amp;gt;% 
  select(Id, actual) %&amp;gt;% 
  mutate(glmnet = get_predicted(model_final = model_final_glmnet, dfTest_Proc),
         ranger = get_predicted(model_final = model_final_rf, dfTest_Proc),
         xgboost = get_predicted(model_final = model_final_xgboost, dfTest_Proc))&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;dfPlot &amp;lt;- dfResults_test %&amp;gt;% 
  gather(model, predicted, -actual, -Id) %&amp;gt;% 
  mutate(model = ordered(model, levels = c(&amp;quot;glmnet&amp;quot;,&amp;quot;ranger&amp;quot;,&amp;quot;xgboost&amp;quot;)))

dfPlot %&amp;gt;%
  rename(`log(SalePrice)` = actual) %&amp;gt;%
  eda_plot_actual_vs_predicted(&amp;quot;log(SalePrice)&amp;quot;, &amp;quot;predicted&amp;quot;, feature_model = &amp;quot;model&amp;quot;, feature_id = &amp;quot;Id&amp;quot;, nLabel = 3) +
  labs(title = &amp;quot;Actual v/s predicted log(SalePrice) on testing set&amp;quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&#34;https://www.nitingupta.com/casestudies/ames-housing-part2-models/index_files/figure-html/plot_test-1.png&#34; alt=&#34;&#34; width=&#34;960&#34; /&gt;&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;dfRMSE &amp;lt;- map(list(&amp;quot;glmnet&amp;quot;,&amp;quot;ranger&amp;quot;,&amp;quot;xgboost&amp;quot;), ~ rmse(dfResults_test, actual, !!sym(.x)) %&amp;gt;% mutate(model = .x)) %&amp;gt;%
  bind_rows() %&amp;gt;%
  mutate(cv_rmse = c(cv_results_glmnet$mean_rmse[1], cv_results_rf$mean_rmse[1], cv_results_xgboost$mean_rmse[1])) %&amp;gt;%
  select(-.estimator, -.metric) %&amp;gt;%
  rename(test_rmse = .estimate) %&amp;gt;%
  select(model, everything())

dfRMSE&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;# A tibble: 3 × 3
  model   test_rmse cv_rmse
  &amp;lt;chr&amp;gt;       &amp;lt;dbl&amp;gt;   &amp;lt;dbl&amp;gt;
1 glmnet      0.130   0.128
2 ranger      0.138   0.134
3 xgboost     0.131   0.126&lt;/code&gt;&lt;/pre&gt;
&lt;div id=&#34;whats-notable-5&#34; class=&#34;section level4&#34;&gt;
&lt;h4&gt;What’s notable?&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;All models have similar RMSE on the unseen testing set as their cross validated RMSE, which shows the cross validation process and hyperparameters worked very well.&lt;/li&gt;
&lt;li&gt;Records with Ids &lt;strong&gt;1537&lt;/strong&gt; and &lt;strong&gt;2217&lt;/strong&gt; are outliers, as none of the models are able to predict close to actual values.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;glmnet&lt;/code&gt; has the lowest test RMSE of 0.130, with &lt;code&gt;xgboost&lt;/code&gt; close behind at 0.131.&lt;/li&gt;
&lt;li&gt;However, &lt;code&gt;glmnet&lt;/code&gt; is sensitive to outliers, as we saw with Ids &lt;strong&gt;524&lt;/strong&gt; and &lt;strong&gt;1299&lt;/strong&gt; in the training set. Since &lt;code&gt;xgboost&lt;/code&gt; had the best cross validated RMSE and the difference in test RMSE is small, we could finalize &lt;code&gt;xgboost&lt;/code&gt;, which is robust to outliers and captures non-linear relationships, as the model that generalizes well on this dataset.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;model_final &amp;lt;- model_final_xgboost&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&#34;feature-importance&#34; class=&#34;section level2&#34;&gt;
&lt;h2&gt;Feature Importance&lt;/h2&gt;
&lt;p&gt;Even though &lt;code&gt;xgboost&lt;/code&gt; is not as easily interpretable as a linear model, we could use variable importance plots to determine the most important features selected by the model.&lt;/p&gt;
&lt;p&gt;Let’s take a look at the top 10 most important features of our finalized &lt;code&gt;xgboost&lt;/code&gt; model:&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;library(vip)

p1 &amp;lt;- vip(extract_fit_parsnip(model_final), aesthetics = list(fill = &amp;quot;#00AED8&amp;quot;)) +
  labs(title = &amp;quot;xgboost model Feature Importance in Training&amp;quot;) +
  theme_quant()

vFeaturesImp &amp;lt;- vip::vi(extract_fit_parsnip(model_final)) %&amp;gt;% top_n(10, Importance) %&amp;gt;% pull(Variable)

p2 &amp;lt;- eda_plot_correlation_matrix(dfTrain %&amp;gt;% select(SalePrice, one_of(vFeaturesImp)), 
                            outlineVar = &amp;quot;SalePrice&amp;quot;, plotTitle = &amp;quot;Correlations with SalePrice&amp;quot;)

cowplot::plot_grid(plotlist = list(p1, p2), nrow = 1)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&#34;https://www.nitingupta.com/casestudies/ames-housing-part2-models/index_files/figure-html/feature_importance-1.png&#34; alt=&#34;&#34; width=&#34;1152&#34; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Correlations of numerical features are plotted side-by-side. All of them have a correlation of 0.5 or more (in absolute terms) with &lt;code&gt;SalePrice&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;All of the top 10 features make sense. To evaluate &lt;code&gt;SalePrice&lt;/code&gt;, a buyer would definitely look at total square footage, overall quality, neighborhood, number of bathrooms, kitchen quality, age of property, garage size and finish, central air conditioning, fireplace quality, etc.&lt;/li&gt;
&lt;li&gt;This shows, our finalized model generalizes well and makes very reasonable choices in terms of features.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div id=&#34;new-property-premium&#34; class=&#34;section level2&#34;&gt;
&lt;h2&gt;New Property Premium&lt;/h2&gt;
&lt;p&gt;Among the top 10 features by importance in our final model, most of the features like square footage, neighborhood and number of bathrooms remain the same throughout the life of the property. Quality and condition of property does change but their evaluation is mostly subjective. The only other feature that cannot be disputed to change over time is &lt;code&gt;PropertyAge&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;So, how would the predicted &lt;code&gt;SalePrice&lt;/code&gt; differ if a property was newly constructed vis-a-vis the same property if it were constructed more than 30 years earlier, and all the times in between?&lt;/p&gt;
&lt;p&gt;We could pick a couple of properties at random, change &lt;code&gt;PropertyAge&lt;/code&gt; and see its impact on &lt;code&gt;SalePrice&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;getSampleProperty &amp;lt;- function(randomSeed) {
  
  set.seed(randomSeed)
  dfSample &amp;lt;- dfTest %&amp;gt;% 
    filter(PropertyAge &amp;gt;= 30) %&amp;gt;% 
    sample_n(1)
  
  dfSample &amp;lt;- tibble(PropertyAge = 1:dfSample$PropertyAge[1]) %&amp;gt;% expand_grid(dfSample %&amp;gt;% select(-PropertyAge))
}

dfSample &amp;lt;- getSampleProperty(1000) %&amp;gt;% bind_rows(getSampleProperty(2000))

# preprocess the sample data and generated predicted values from the final model
get_preprocessing_recipe(dfSample) %&amp;gt;% 
  bake(dfSample) %&amp;gt;% 
  get_predicted(model_final, .) %&amp;gt;% 
  as_tibble() %&amp;gt;% 
  bind_cols(dfSample) %&amp;gt;% 
  mutate(predicted = exp(value)) %&amp;gt;% 
  group_by(Id) %&amp;gt;% 
  ggplot(aes(x = PropertyAge, y = predicted)) +
  geom_point(color = &amp;quot;#00AED8&amp;quot;) +
  geom_line() +
  facet_wrap(~Id, scales = &amp;quot;free&amp;quot;) +
  scale_y_continuous(labels = scales::label_dollar(accuracy = 1)) +
  labs(y = &amp;quot;Predicted Sale Price&amp;quot;,
       title = &amp;quot;New Property Premium&amp;quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&#34;https://www.nitingupta.com/casestudies/ames-housing-part2-models/index_files/figure-html/property_appreciation-1.png&#34; alt=&#34;&#34; width=&#34;672&#34; /&gt;&lt;/p&gt;
&lt;p&gt;We can see there’s a small premium for a newly constructed property v/s an older property of the same build, quality and condition. This premium isn’t very much in a place like Ames, IA but we’d reckon it would be much higher in a larger metropolitan city.&lt;/p&gt;
&lt;/div&gt;
</description>
    </item>
    
  </channel>
</rss>
