Building Decision Trees in MATLAB for Classification and Regression

Classification Tree with the Iris Dataset

Load and prepare the Fisher iris data, then split into training and test partitions after standardizing the feature matrix.

load fisheriris;
features = meas;           % 4 predictors
speciesLabel = species;     % response

% Standardize
[featStd, muVals, sigVals] = zscore(features);

% Hold-out partition
holdPart = cvpartition(speciesLabel, 'HoldOut', 0.3);
trainFeat = featStd(training(holdPart) ,:);
trainResp = speciesLabel(training(holdPart));
testFeat  = featStd(test(holdPart) ,:);
testResp  = speciesLabel(test(holdPart));

Train a CART classification tree, limiting tree depth and leaf size.

classTree = fitctree(trainFeat, trainResp, ...
    'PredictorNames', {'SepalLength','SepalWidth','PetalLength','PetalWidth'}, ...
    'MaxNumSplits', 10, ...
    'MinLeafSize', 5);

Visualize the tree structure and inspect it interactively.

figure;
view(classTree, 'Mode', 'graph');
title('Classification Tree Structure');

Evaluate on the hold-out set and show a confusion matrix.

predictedClasses = predict(classTree, testFeat);
acc = sum(strcmp(predictedClasses, testResp)) / numel(testResp);
fprintf('Hold-out accuracy: %.2f%%\n', acc * 100);

figure;
cmPlot = confusionchart(testResp, predictedClasses);
cmPlot.Title = 'Confusion Matrix';
cmPlot.XLabel = 'Predicted';
cmPlot.YLabel = 'Actual';

Manual Regression Tree

Synthesize a nonlinear dataset with two features and an interaction term. The target is a mix of polynomial and sinusoidal components plus Gaussian noise.

rng(42);
n = 500;
f1 = 2 + 8 * rand(n,1);        % [2, 10]
f2 = 1 + 6 * rand(n,1);        % [1, 7]
output = 20 + 5*f1 + 3*f2 + 2*f1.^2 + 1.5*sin(2*f2) + 3*randn(n,1);

figure;
scatter3(f1, f2, output, 40, output, 'filled');
xlabel('Feature A'); ylabel('Feature B'); zlabel('Response');
title('Synthetic Data');

A recursive tree builder splits nodes by minimizing MSE.

function nd = growTree(X, y, curDepth, maxDepth, minParent, minLeaf)
    nd.isLeaf = false;
    nd.predVal = mean(y);
    nd.nSamp   = length(y);

    if curDepth >= maxDepth || length(y) < minParent || var(y) < 1e-6
        nd.isLeaf = true;
        return;
    end

    [bestCol, bestCut] = pickSplit(X, y);

    leftMask  = X(:, bestCol) <= bestCut;
    rightMask = ~leftMask;

    nd.splitCol  = bestCol;
    nd.splitVal  = bestCut;
    nd.left      = growTree(X(leftMask ,:), y(leftMask),  curDepth+1, maxDepth, minParent, minLeaf);
    nd.right     = growTree(X(rightMask,:), y(rightMask), curDepth+1, maxDepth, minParent, minLeaf);
end

function [bestCol, bestVal] = pickSplit(X, y)
    nFeat = size(X, 2);
    bestErr = inf;

    for c = 1:nFeat
        candidates = unique(X(:,c));
        for t = candidates'
            leftIdx  = X(:,c) <= t;
            rightIdx = ~leftIdx;
            if sum(leftIdx) == 0 || sum(rightIdx) == 0
                continue;
            end
            mse = (sum((y(leftIdx) - mean(y(leftIdx))).^2) + ...
                   sum((y(rightIdx) - mean(y(rightIdx))).^2)) / length(y);
            if mse < bestErr
                bestErr = mse;
                bestCol = c;
                bestVal = t;
            end
        end
    end
end

Train the tree and evaluate RMSE on a hold-out partition.

allFeat = [f1, f2];
holdOut = cvpartition(size(allFeat,1), 'HoldOut', 0.3);
Xtr = allFeat(training(holdOut), :);
yTr = output(training(holdOut));
Xte = allFeat(test(holdOut), :);
yTe = output(test(holdOut));

regTree = growTree(Xtr, yTr, 0, 5, 10, 5);

% Simple recursive prediction function
predVals = nan(size(Xte,1),1);
for i = 1:size(Xte,1)
    predVals(i) = treePredict(regTree, Xte(i,:));
end

rmse = sqrt(mean((yTe - predVals).^2));
fprintf('Test RMSE: %.3f\n', rmse);

function val = treePredict(nd, row)
    if nd.isLeaf
        val = nd.predVal;
    else
        if row(nd.splitCol) <= nd.splitVal
            val = treePredict(nd.left, row);
        else
            val = treePredict(nd.right, row);
        end
    end
end

Visualize predicted vs. actual responses and a decision surface.

figure;
hold on;
plot(yTe, 'b', 'LineWidth', 1.5);
plot(predVals, 'r--', 'LineWidth', 1.5);
legend('Actual', 'Predicted');
title('Regression Results');
xlabel('Sample index'); ylabel('Response');
% Surface plot for two features
[gA, gB] = meshgrid(linspace(2,10,50), linspace(1,7,50));
Zsurf = zeros(size(gA));
for r = 1:size(gA,1)
    for c = 1:size(gA,2)
        Zsurf(r,c) = treePredict(regTree, [gA(r,c), gB(r,c)]);
    end
end
figure;
surf(gA, gB, Zsurf, 'EdgeColor', 'none');
hold on;
scatter3(f1, f2, output, 40, output, 'filled');
title('Decision Surface and Samples');
xlabel('Feature A'); ylabel('Feature B'); zlabel('Prediction / Response');

Common adjustments for different scenarios include replacing the dataset for medical diagnosis or industrial fault detection, modifying the tree builder to accept sequential features, and introducing domain-specific feature engineering such as debt-to-income ratios for credit risk modeling.

Troubleshoooting quick reference

Symptom Remedy
Overfitting Increase MinLeafSize or prune the tree
Large bias in predicsions Inspect feature relationships, add interaction terms
Slow training Lower MaxNumSplits or use parallel computation
Unbalanced leaf samples Try SplitCriterion='gdi' and re-tune

Tags: MATLAB Decision Trees Classification regression CART

Posted on Mon, 02 Nov 2026 16:10:03 +0000 by whitehat