liveBook · Manning

Threshold

As explained in Machine Learning Algorithms in Depth

A threshold is a cutoff value for a feature that determines how data points are divided during a tree split.

In a decision tree, a threshold is a cutoff on one feature that divides data points between two branches. Choosing both the feature and its threshold determines how the tree partitions the input space. section 5.6

Let X be an n × d input matrix. For each feature j, form candidate thresholds τj from midpoints of its observed values. Compare feature–threshold pairs by the cost of their resulting partitions, and choose the pair with the lowest cost. Repeat this choice recursively to grow the tree. The search is greedy: it does not solve the globally optimal partitioning problem. section 5.6

For classification, a useful split leaves regions with little class impurity. If πk is the fraction of points in a region belonging to class k, the Gini index measures expected classification error and reaches zero for a pure region. Entropy offers another measure of uncertainty. Tree depth also matters: continuing to split can overfit rather than improve generalization. section 5.6

Listing 5.5 CART decision tree algorithm
import numpy as np 
import matplotlib.pyplot as plt 

from sklearn.datasets import load_iris
from sklearn.metrics import accuracy_score
from sklearn.model_selection import train_test_split

class TreeNode():
    def __init__(self, gini, num_samples, num_samples_class, class_label):
     self.gini = gini  #1
     self.num_samples = num_samples  #2
     self.num_samples_class = num_samples_class #3
     self.class_label = class_label  #4
     self.feature_idx = 0  #5
     self.treshold = 0  #6
     self.left = None  #7
     self.right = None  #8

class DecisionTreeClassifier():
 def __init__(self, max_depth = None):
     self.max_depth = max_depth

 def best_split(self, X_train, y_train):
     m = y_train.size
     if (m <= 1):
         return None, None

     mk = [np.sum(y_train == k) for k in range(self
      .num_classes)]    #9

     best_gini = 1.0 – sum((n / m) ** 2 for n in mk)   #10
     best_idx, best_thr = None, None

     #iterate over all features
     for idx in range(self.num_features):

         thresholds, classes = zip(*sorted(zip(X[:, 
          idx], y)))   #11

         num_left = [0]*self.num_classes
         num_right = mk.copy()

         for i in range(1, m):    #12

             k = classes[i-1]

             num_left[k] += 1
             num_right[k] -= 1

             gini_left = 1.0 - sum(
                 (num_left[x] / i) ** 2 for x in range(self.num_classes)
             )

             gini_right = 1.0 - sum(
                 (num_right[x] / (m - i)) ** 2 for x in 
                  range(self.num_classes)
             )

             gini = (i * gini_left + (m - i) * gini_right) / m

             if thresholds[i] == thresholds[i - 1]:
                 continue

             if (gini < best_gini):
                 best_gini = gini
                 best_idx = idx
                 best_thr = (thresholds[i] + 
                  thresholds[i - 1]) / 2   #13
             #end if
         #end for
     #end for
     return best_idx, best_thr

 def gini(self, y_train):
     m = y_train.size
     return 1.0 - sum((np.sum(y_train == k) / m) ** 2 for k in 
      range(self.num_classes))

 def fit(self, X_train, y_train):
     self.num_classes = len(set(y_train))
     self.num_features = X_train.shape[1]
     self.tree = self.grow_tree(X_train, y_train)

 def grow_tree(self, X_train, y_train, depth=0):

     num_samples_class = [np.sum(y_train == k) for k in 
      range(self.num_classes)]
     class_label = np.argmax(num_samples_class)

     node = TreeNode(
         gini=self.gini(y_train),
         num_samples=y_train.size,
         num_samples_class=num_samples_class,
         class_label=class_label,
     )

     if depth < self.max_depth:   #14
         idx, thr = self.best_split(X_train, y_train)
         if idx is not None:
             indices_left = X_train[:, idx] < thr
             X_left, y_left = X_train[indices_left], y_train[indices_left]
             X_right, y_right = X_train[~indices_left], y_train[~indices_left]
             node.feature_index = idx
             node.threshold = thr
             node.left = self.grow_tree(X_left, y_left, depth + 1)
             node.right = self.grow_tree(X_right, y_right, depth + 1)

     return node

 def predict(self, X_test):
     return [self.predict_helper(x_test) for x_test in X_test]

 def predict_helper(self, x_test):
     node = self.tree
     while node.left:
         if x_test[node.feature_index] < node.threshold:
             node = node.left
         else:
             node = node.right
     return node.class_label


if __name__ == "__main__":

 #load data
 iris = load_iris()
 X = iris.data[:, [2,3]]
 y = iris.target

 X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, 
  random_state=42)

 print("decision tree classifier...")
 tree_clf = DecisionTreeClassifier(max_depth = 3)
 tree_clf.fit(X_train, y_train)

 print("prediction...")
 y_pred = tree_clf.predict(X_test)

 tree_clf_acc = accuracy_score(y_test, y_pred)
 print("test set accuracy: ", tree_clf_acc)

The CART implementation stores a feature index and threshold in each node and begins split selection in best_split. section 5.6

A tree-split threshold is distinct from a binary classifier’s classification threshold. Varying the latter changes predicted labels and therefore the confusion-matrix counts; it produces different points on ROC and precision–recall curves. section 5.6 section 5.2

How Machine Learning Algorithms in Depth covers it

How deep it goes
Defines midpoint candidates and impurity-based split selection, then shows CART code; stops short of globally optimal partitioning.
Examples
Uses CART pseudocode and a Python implementation with an iris dataset import.
What it assumes
Assumes feature matrices and class labels, while defining threshold candidates and class-proportion-based impurity.
What you can do after
Build it from scratch by generating candidate cutoffs, comparing split costs, and storing the chosen feature and threshold in tree nodes.
How it connects
Connects decision trees to Gini impurity, entropy, overfitting, and the distinct classification thresholds used in ROC analysis.

Also explained in

Compare how 13 books explain threshold →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Machine Learning Algorithms in Depth. The section links point to the parts of the book it draws on.