Decision Trees
Best Split (Gini Scan)
Find the best 1-feature split by scanning candidate thresholds and choosing
the lowest weighted Gini impurity. For each threshold t: split into left
(X<t) and right (X≥t), compute weighted Gini. Library:
DecisionTreeClassifier(max_depth=1) reads tree_.threshold[0].
RESULT: best threshold.
By hand
X=[1,2,3,5,7,8], y=[0,0,0,1,1,1]. Candidates: t=2.5→wg=0.25, t=4.0→wg=0.0 (perfect split), t=6.0→wg=0.25. Best: 4.0.
naive.py
Replay: real traced execution (multi-file project)
X = [1, 2, 3, 5, 7, 8]
y = [0, 0, 0, 1, 1, 1]
n = len(X)
thresholds = [2.5, 4.0, 6.0]
best_t = None
best_wg = 1.0
for t in thresholds:
left_y = [y[i] for i in range(n) if X[i] < t]
right_y = [y[i] for i in range(n) if X[i] >= t]
nl = len(left_y)
nr = len(right_y)
c0l = left_y.count(0)
gl = 1 - (c0l / nl) ** 2 - ((nl - c0l) / nl) ** 2
c0r = right_y.count(0)
gr = 1 - (c0r / nr) ** 2 - ((nr - c0r) / nr) ** 2
wg = (nl / n) * gl + (nr / n) * gr
if wg < best_wg:
best_wg = wg
best_t = t
print('RESULT:', best_t)
X ← [1, 2, 3, 5, 7, 8]
1X = [1, 2, 3, 5, 7, 8]2y = [0, 0, 0, 1, 1, 1]values this step[1, 2, 3, 5, 7, 8]Xy ← [0, 0, 0, 1, 1, 1]
1X = [1, 2, 3, 5, 7, 8]2y = [0, 0, 0, 1, 1, 1]3n = len(X)values this step[0, 0, 0, 1, 1, 1]yn ← 6
2y = [0, 0, 0, 1, 1, 1]3n = len(X)4thresholds = [2.5, 4.0, 6.0]values this step6nthresholds ← [2.5, 4.0, 6.0]
3n = len(X)4thresholds = [2.5, 4.0, 6.0]5best_t = Nonevalues this step[2.5, 4.0, 6.0]thresholdsbest_t ← None
4thresholds = [2.5, 4.0, 6.0]5best_t = None6best_wg = 1.0values this stepNonebest_tbest_wg ← 1.0
5best_t = None6best_wg = 1.07for t in thresholds:values this step1.0best_wgt ← 2.5
6best_wg = 1.07for t in thresholds:8 left_y = [y[i] for i in range(n) if X[i] < t]values this step2.5tleft_y ← [0, 0]
7for t in thresholds:8 left_y = [y[i] for i in range(n) if X[i] < t]9 right_y = [y[i] for i in range(n) if X[i] >= t]values this step[0, 0]left_yright_y ← [0, 1, 1, 1]
8left_y = [y[i] for i in range(n) if X[i] < t]9right_y = [y[i] for i in range(n) if X[i] >= t]10nl = len(left_y)values this step[0, 1, 1, 1]right_ynl ← 2
9right_y = [y[i] for i in range(n) if X[i] >= t]10nl = len(left_y)11nr = len(right_y)values this step2nlnr ← 4
10nl = len(left_y)11nr = len(right_y)12c0l = left_y.count(0)values this step4nrc0l ← 2
11nr = len(right_y)12c0l = left_y.count(0)13gl = 1 - (c0l / nl) ** 2 - ((nl - c0l) / nl) ** 2values this step2c0lgl ← 0.0
12c0l = left_y.count(0)13gl = 1 - (c0l / nl) ** 2 - ((nl - c0l) / nl) ** 214c0r = right_y.count(0)values this step0.0glc0r ← 1
13gl = 1 - (c0l / nl) ** 2 - ((nl - c0l) / nl) ** 214c0r = right_y.count(0)15gr = 1 - (c0r / nr) ** 2 - ((nr - c0r) / nr) ** 2values this step1c0rgr ← 0.375
14c0r = right_y.count(0)15gr = 1 - (c0r / nr) ** 2 - ((nr - c0r) / nr) ** 216wg = (nl / n) * gl + (nr / n) * grvalues this step0.375grwg ← 0.25
15gr = 1 - (c0r / nr) ** 2 - ((nr - c0r) / nr) ** 216wg = (nl / n) * gl + (nr / n) * gr17if wg < best_wg:values this step0.25wgif wg < best_wg:
16wg = (nl / n) * gl + (nr / n) * gr17if wg < best_wg:18 best_wg = wgbest_wg ← 0.25
17if wg < best_wg:18 best_wg = wg19 best_t = tvalues this step1.0 → 0.25best_wgbest_t ← 2.5
18 best_wg = wg19 best_t = t20print('RESULT:', best_t)values this stepNone → 2.5best_tt ← 4.0
6best_wg = 1.07for t in thresholds:8 left_y = [y[i] for i in range(n) if X[i] < t]values this step2.5 → 4.0tleft_y ← [0, 0, 0]
7for t in thresholds:8 left_y = [y[i] for i in range(n) if X[i] < t]9 right_y = [y[i] for i in range(n) if X[i] >= t]values this step[0, 0] → [0, 0, 0]left_yright_y ← [1, 1, 1]
8left_y = [y[i] for i in range(n) if X[i] < t]9right_y = [y[i] for i in range(n) if X[i] >= t]10nl = len(left_y)values this step[0, 1, 1, 1] → [1, 1, 1]right_ynl ← 3
9right_y = [y[i] for i in range(n) if X[i] >= t]10nl = len(left_y)11nr = len(right_y)values this step2 → 3nlnr ← 3
10nl = len(left_y)11nr = len(right_y)12c0l = left_y.count(0)values this step4 → 3nrc0l ← 3
11nr = len(right_y)12c0l = left_y.count(0)13gl = 1 - (c0l / nl) ** 2 - ((nl - c0l) / nl) ** 2values this step2 → 3c0lgl = 1 - (c0l / nl) ** 2 - ((nl - c0l) / nl) ** 2
12c0l = left_y.count(0)13gl = 1 - (c0l / nl) ** 2 - ((nl - c0l) / nl) ** 214c0r = right_y.count(0)c0r ← 0
13gl = 1 - (c0l / nl) ** 2 - ((nl - c0l) / nl) ** 214c0r = right_y.count(0)15gr = 1 - (c0r / nr) ** 2 - ((nr - c0r) / nr) ** 2values this step1 → 0c0rgr ← 0.0
14c0r = right_y.count(0)15gr = 1 - (c0r / nr) ** 2 - ((nr - c0r) / nr) ** 216wg = (nl / n) * gl + (nr / n) * grvalues this step0.375 → 0.0grwg ← 0.0
15gr = 1 - (c0r / nr) ** 2 - ((nr - c0r) / nr) ** 216wg = (nl / n) * gl + (nr / n) * gr17if wg < best_wg:values this step0.25 → 0.0wgif wg < best_wg:
16wg = (nl / n) * gl + (nr / n) * gr17if wg < best_wg:18 best_wg = wgbest_wg ← 0.0
17if wg < best_wg:18 best_wg = wg19 best_t = tvalues this step0.25 → 0.0best_wgbest_t ← 4.0
18 best_wg = wg19 best_t = t20print('RESULT:', best_t)values this step2.5 → 4.0best_tt ← 6.0
6best_wg = 1.07for t in thresholds:8 left_y = [y[i] for i in range(n) if X[i] < t]values this step4.0 → 6.0tleft_y ← [0, 0, 0, 1]
7for t in thresholds:8 left_y = [y[i] for i in range(n) if X[i] < t]9 right_y = [y[i] for i in range(n) if X[i] >= t]values this step[0, 0, 0] → [0, 0, 0, 1]left_yright_y ← [1, 1]
8left_y = [y[i] for i in range(n) if X[i] < t]9right_y = [y[i] for i in range(n) if X[i] >= t]10nl = len(left_y)values this step[1, 1, 1] → [1, 1]right_ynl ← 4
9right_y = [y[i] for i in range(n) if X[i] >= t]10nl = len(left_y)11nr = len(right_y)values this step3 → 4nlnr ← 2
10nl = len(left_y)11nr = len(right_y)12c0l = left_y.count(0)values this step3 → 2nrc0l = left_y.count(0)
11nr = len(right_y)12c0l = left_y.count(0)13gl = 1 - (c0l / nl) ** 2 - ((nl - c0l) / nl) ** 2gl ← 0.375
12c0l = left_y.count(0)13gl = 1 - (c0l / nl) ** 2 - ((nl - c0l) / nl) ** 214c0r = right_y.count(0)values this step0.0 → 0.375glc0r = right_y.count(0)
13gl = 1 - (c0l / nl) ** 2 - ((nl - c0l) / nl) ** 214c0r = right_y.count(0)15gr = 1 - (c0r / nr) ** 2 - ((nr - c0r) / nr) ** 2gr = 1 - (c0r / nr) ** 2 - ((nr - c0r) / nr) ** 2
14c0r = right_y.count(0)15gr = 1 - (c0r / nr) ** 2 - ((nr - c0r) / nr) ** 216wg = (nl / n) * gl + (nr / n) * grwg ← 0.25
15gr = 1 - (c0r / nr) ** 2 - ((nr - c0r) / nr) ** 216wg = (nl / n) * gl + (nr / n) * gr17if wg < best_wg:values this step0.0 → 0.25wgif wg < best_wg:
16wg = (nl / n) * gl + (nr / n) * gr17if wg < best_wg:18 best_wg = wgfor t in thresholds:
6best_wg = 1.07for t in thresholds:8 left_y = [y[i] for i in range(n) if X[i] < t]stdout ← RESULT: 4.0
19 best_t = t20print('RESULT:', best_t)values this stepRESULT: 4.0stdout
With scikit-learn
DecisionTreeClassifier(max_depth=1) fits a single split and stores the
chosen threshold in clf.tree_.threshold[0].
library.py
from sklearn.tree import DecisionTreeClassifier
from dalib.display import set_display
set_display()
X = [[1], [2], [3], [5], [7], [8]]
y = [0, 0, 0, 1, 1, 1]
clf = DecisionTreeClassifier(max_depth=1, random_state=0)
clf.fit(X, y)
threshold = round(float(clf.tree_.threshold[0]), 4)
print('sklearn threshold:', threshold)
print('RESULT:', threshold)
sklearn threshold: 4.0
RESULT: 4.0
Implementation notes
- sklearn's split condition is feature ≤ threshold (left), > threshold
(right). The naive uses strict
<— equivalent here because no training sample equals 4.0 (the midpoint (3+5)/2=4.0 lies between feature values). - Candidate thresholds are midpoints between adjacent sorted feature values. Three candidates [2.5, 4.0, 6.0] cover the informative region; symmetric outer candidates give higher impurity and are omitted.
- Weighted Gini = (n_left/n)·Gini_left + (n_right/n)·Gini_right. At t=4.0 both children are pure (Gini=0) so weighted Gini=0 — the global minimum.
- Cross-reference:
gini-impurity(this chapter) for the per-node formula;decision-stump-predict(this chapter) applies the found threshold.