title: Why null_model() perform better than any other regression methods in R tidymodels?
body: <p>I am testing several regression models using <a href="https://www.tidymodels.org/find/parsnip/" rel="nofollow noreferrer">Tidyverse's parsnip</a>.
  Initially the best performing one is <a href="https://parsnip.tidymodels.org/reference/rand_forest.html" rel="nofollow noreferrer">rand_forest()</a>, but after
  I add <a href="https://parsnip.tidymodels.org/reference/null_model.html" rel="nofollow noreferrer">null_model()</a>, it is the latter one that is best in terms of RMSE.</p>
  <p>All are done after parameter tuning and cross-validated resampling.</p>
  <p>Here is the result of <code>null_model()</code>:</p>
  <pre><code>&gt; show_best(null_grid_results, metric = &quot;rmse&quot;)
  # A tibble: 1 × 6
    .metric .estimator  mean     n std_err .config             
    &lt;chr&gt;   &lt;chr&gt;      &lt;dbl&gt; &lt;int&gt;   &lt;dbl&gt; &lt;chr&gt;               
  1 rmse    standard   0.421    10  0.0701 Preprocessor1_Model1
  &gt; collect_metrics(null_grid_results) %&gt;%
  +   filter(.metric == &quot;rmse&quot;) %&gt;%
  +   pull(mean) %&gt;% mean()
  [1] 0.4209793
  </code></pre>
  <p>And this is the random forest:</p>
  <pre><code>&gt; show_best(random_forest_grid_results, metric = &quot;rmse&quot;)
  # A tibble: 5 × 8
     mtry min_n .metric .estimator  mean     n std_err .config              
    &lt;int&gt; &lt;int&gt; &lt;chr&gt;   &lt;chr&gt;      &lt;dbl&gt; &lt;int&gt;   &lt;dbl&gt; &lt;chr&gt;                
  1  2971    28 rmse    standard   0.420    10  0.0700 Preprocessor1_Model15
  2   945    21 rmse    standard   0.420    10  0.0703 Preprocessor1_Model16
  3  1090    40 rmse    standard   0.420    10  0.0701 Preprocessor1_Model25
  4  2074    32 rmse    standard   0.420    10  0.0702 Preprocessor1_Model13
  5  1650    27 rmse    standard   0.420    10  0.0698 Preprocessor1_Model10
  &gt; collect_metrics(random_forest_grid_results) %&gt;%
  +   filter(.metric == &quot;rmse&quot;) %&gt;%
  +   pull(mean) %&gt;% mean()
  [1] 0.4369285
  </code></pre>
  <p>The code snippet I used for performing <code>null_model()</code> is this:</p>
  <pre><code>library(tidyverse)
  library(tidymodels)
  library(rules)
  library(baguette)
  tidymodels_prefer()
  library(doParallel)
  
  # Skip showing steps for getting:
  # prolif_feat_outcome_dat_train
  # prolif_feat_outcome_dat_folds
  
  null_model_spec &lt;-    null_model() %&gt;% 
    set_engine(&quot;parsnip&quot;) %&gt;% 
    set_mode(&quot;regression&quot;) %&gt;%  
    translate()
  
  null_model_feature_preproc_rec &lt;- recipe(prolif_outcome ~ ., data = prolif_feat_outcome_dat_train) %&gt;% 
    step_zv(all_predictors()) 
  
  null_model_wflow &lt;- workflow() %&gt;%
    add_model(null_model_spec) %&gt;%
    add_recipe(null_model_feature_preproc_rec )
  
  null_model_set &lt;- extract_parameter_set_dials(null_model_wflow) 
  
  grid_ctrl &lt;- control_grid(
    verbose = TRUE,
    save_pred = TRUE,
    parallel_over = &quot;everything&quot;,
    save_workflow = TRUE
  )
  nof_grid &lt;- 25
  ptm &lt;- proc.time()
  cls &lt;- makePSOCKcluster(parallel::detectCores(logical = FALSE))
  registerDoParallel(cls)
  
  set.seed(999)
  null_model_grid_results &lt;- null_model_wflow %&gt;%
    tune_grid(
      param_info = null_model_set,
      resamples = prolif_feat_outcome_dat_folds,
      grid = nof_grid,
      control = grid_ctrl
    )
  
  
  stopCluster(cls)
  proc.time() - ptm
  
  show_best(null_model_grid_results, metric = &quot;rmse&quot;)
  collect_metrics(null_model_grid_results) %&gt;%
    filter(.metric == &quot;rmse&quot;) %&gt;%
    pull(mean) %&gt;% mean()
  </code></pre>
  <p>And this is by <code>rand_forest()</code>:</p>
  <pre><code>random_forest_spec &lt;-   rand_forest(mtry = tune(), min_n = tune(), trees = 1000) %&gt;% 
    set_engine(&quot;ranger&quot;) %&gt;% 
    set_mode(&quot;regression&quot;) %&gt;% 
    translate()
  
  random_forest_feature_preproc_rec &lt;- recipe(prolif_outcome ~ ., data = prolif_feat_outcome_dat_train) %&gt;% 
    step_zv(all_predictors()) 
  
  random_forest_wflow &lt;- workflow() %&gt;%
    add_model(random_forest_spec) %&gt;%
    add_recipe(random_forest_feature_preproc_rec )
  
  random_forest_set &lt;- extract_parameter_set_dials(random_forest_wflow) 
  
  grid_ctrl &lt;- control_grid(
    verbose = TRUE,
    save_pred = TRUE,
    parallel_over = &quot;everything&quot;,
    save_workflow = TRUE
  )
  nof_grid &lt;- 25
  ptm &lt;- proc.time()
  cls &lt;- makePSOCKcluster(parallel::detectCores(logical = FALSE))
  registerDoParallel(cls)
  
  set.seed(999)
  random_forest_grid_results &lt;- random_forest_wflow %&gt;%
    tune_grid(
      param_info = random_forest_set,
      resamples = prolif_feat_outcome_dat_folds,
      grid = nof_grid,
      control = grid_ctrl
    )
  
  
  stopCluster(cls)
  proc.time() - ptm
  saveRDS(random_forest_grid_results, file = paste0(&quot;/home/ubuntu/storage1/find_best_model_for_prolif_predictions_tidymodels/data/&quot;, wanted_dose, &quot;.random_forest_grid_results.rds&quot; ))
  
  show_best(random_forest_grid_results, metric = &quot;rmse&quot;)
  collect_metrics(random_forest_grid_results) %&gt;%
    filter(.metric == &quot;rmse&quot;) %&gt;%
    pull(mean) %&gt;% mean()
  </code></pre>
  <p>I expected <code>null_model()</code> perform way worse than <code>rand_forest()</code>?
  My question is why <code>null_model()</code> perform best?
  Is my approach correct? If not what is the correct way to implement it?</p>