Evaluating and Optimizing Decision Trees in R

After constructing a decision tree model, quantifying its predictive capability is a critical step in validating model effectiveness. For classification problems, we typically focus on the confusion matrix, accuracy, precision, recall, and the F1 score; for regression problems, mean squared error (MSE) or mean absolute error (MAE) are primarily relied upon. However, within this specific algorithm, there is an even more core and unique evaluation metric—Gini Impurity and Information Gain—which directly dictates the tree's splitting strategy.

Gini Impurity measures the probability that two randomly selected samples from a dataset belong to different classes. A lower value indicates higher data purity. In R, the rpart package defaults to using the Gini index as the splitting criterion because it generally performs better than Information Gain when handling imbalanced datasets. When evaluating overall model performance, in addition to training set metrics, one should pay more attention to the generalization capability on validation or test sets to avoid falsely high accuracy caused by overfitting.
A significant characteristic of decision trees is their tendency to learn noise in the training data excessively, leading to an abnormally complex tree structure, known as "overfitting." While such a model may perform perfectly on the training set, it often fails when faced with new data. The core solution to this problem is pruning, which simplifies the tree structure by removing nodes that contribute minimally to classification.

In R, the rpart package provides several parameters to control the degree of pruning:

  • cp (Complexity Parameter): This is the most commonly used global complexity parameter. A larger value allows for a greater minimum reduction, resulting in a simpler tree. The optimal cp value can be automatically searched using cross-validation.
  • split.digits: Used to limit the number of decimal places after the variable during splitting, thereby reducing the model's granularity.
  • minsplit and minbucket: These set the minimum number of samples required in a node to perform a split or to serve as a leaf node, helping to prevent unnecessary splits on sub-nodes with few samples.

Parameter Tuning Process Based on Cross-Validation

To find the optimal pruning parameters, we need to leverage cross-validation techniques. K-fold cross-validation is a robust method that randomly divides the dataset into K parts, using K-1 parts as the training set and the remaining part as the validation set in turns, repeating this process K times and taking the average performance.

Below is a complete tuning example based on the rpart package, demonstrating how to automatically search for the optimal complexity parameter:

library(rpart)
library(caret)

# Assume df is your dataframe and target is the target variable
# First, create a sequence containing all possible cp values
cp_values <- seq(0.01, 0.5, by = 0.01)

# Define a function for evaluation
evaluate_tree <- function(cp_val, data, target) {
  # Train the model
  model <- rpart(target ~ ., data = data, 
                 method = "class", 
                 cp = cp_val,
                 control = rpart.control(minsplit = 20))
  
  # Get prediction results
  predictions <- predict(model, newdata = data)
  
  # Calculate accuracy (here just as an example; in practice, use confusion matrix)
  accuracy <- sum(predictions == target) / nrow(data)
  
  return(accuracy)
}

# Execute cross-validation to find the best cp value
best_cp <- cp_values[which.max(sapply(cp_values, evaluate_tree, data = df, target = target))]

# Retrain the final model using the best parameters
final_model <- rpart(target ~ ., data = df, 
                     method = "class", 
                     cp = best_cp,
                     control = rpart.control(minsplit = 20))

# View the pruned tree structure
print(final_model, type = 3)
plot(final_model)

Through the code above, we systematically tested different cp values and automatically selected the parameter that yielded the highest accuracy on the validation set. This method not only improves the model's generalization ability but also enhances its interpretability, making it easier for business users to understand the decision logic.

Ensemble Learning: From Single Tree to Random Forest

When the performance of a single decision tree still fails to meet high-precision requirements, consider upgrading to an ensemble learning method. Random Forest is one of the most widely applied ensemble algorithms; it reduces variance and overfitting risk by constructing multiple decision trees and aggregating their prediction results.

In R, the randomForest package is the standard tool for implementing Random Forests. Unlike a single tree, Random Forest considers not only all features but also randomly selects a subset of feature variables at each split, increasing model diversity. Furthermore, it defaults to using "majority voting" (for classification) or "average" (for regression) to merge the outputs of multiple trees; this mechanism greatly smooths out the instability of individual trees.

Modeling with randomForest typically only requires specifying the data, response variable, and the number of trees:

library(randomForest)

# Train a Random Forest model
rf_model <- randomForest(target ~ ., data = df, 
                         mtry = sqrt(ncol(df)), # Attempt to select the square root of feature count as the optimal subset size
                         ntree = 500)          # Number of trees

# View model summary
print(rf_model)

# Perform prediction
rf_predictions <- predict(rf_model, newdata = df_test)

By comparing the performance of a single pruned decision tree against Random Forest, it is often found that the latter exhibits stronger robustness on complex datasets. Mastering the entire process from evaluation, pruning to ensemble optimization is an essential path to deeply understanding the machine learning capabilities in R.