{ "cells": [ { "cell_type": "markdown", "id": "a4d6ce2f", "metadata": {}, "source": [ "# Linear discriminant trees\n", "\n", "`GuideClassifier` can split on linear combinations of features instead of one feature at a time. With `node_model=\"lda\"` each node selects its own features by one-way ANOVA F tests, splits on its first linear discriminant coordinate, and classifies its rows with linear discriminant analysis (LDA). With `node_model=\"lda_splits\"` the nodes split the same way, but each cut minimizes the Gini impurity of its children and the leaves predict class proportions, as in an ordinary classification tree. Both reproduce GUIDE's options of the same names.\n", "\n", "This notebook compares them with a constant-leaf GUIDE tree, scikit-learn's `DecisionTreeClassifier` (CART) and its `LinearDiscriminantAnalysis` on two scikit-learn datasets and on a synthetic problem whose class boundary is oblique." ] }, { "cell_type": "code", "execution_count": 1, "id": "f0963d66", "metadata": { "execution": { "iopub.execute_input": "2026-11-02T22:20:07.868568Z", "iopub.status.busy": "2026-11-02T22:20:07.868153Z", "iopub.status.idle": "2026-11-02T22:20:09.108476Z", "shell.execute_reply": "2026-11-02T22:20:09.107933Z" } }, "outputs": [], "source": [ "import numpy as np\n", "import pandas as pd\n", "from sklearn.datasets import load_breast_cancer, load_wine\n", "from sklearn.discriminant_analysis import LinearDiscriminantAnalysis\n", "from sklearn.model_selection import StratifiedKFold, cross_validate\n", "from sklearn.tree import DecisionTreeClassifier\n", "\n", "from guide import GuideClassifier\n", "\n", "rng = np.random.default_rng(0)\n", "n = 600\n", "X_obl = pd.DataFrame(rng.normal(size=(n, 4)), columns=[\"x1\", \"x2\", \"x3\", \"x4\"])\n", "score = X_obl[\"x1\"] + X_obl[\"x2\"] - 0.5 * X_obl[\"x3\"]\n", "y_obl = pd.Series(np.select([score < -0.8, score < 0.8], [\"low\", \"mid\"], \"high\"))\n", "\n", "datasets = {\n", " \"wine\": load_wine(return_X_y=True, as_frame=True),\n", " \"breast cancer\": load_breast_cancer(return_X_y=True, as_frame=True),\n", " \"oblique (synthetic)\": (X_obl, y_obl),\n", "}" ] }, { "cell_type": "markdown", "id": "f52cab45", "metadata": {}, "source": [ "## Cross-validated comparison\n", "\n", "Ten-fold cross-validated accuracy and log loss (lower is better), with the mean number of leaves. The GUIDE trees are pruned by their own internal cross-validation. LDA nodes default to `min_samples_leaf=10`, GUIDE's default for this model." ] }, { "cell_type": "code", "execution_count": 2, "id": "c4e0f7bc", "metadata": { "execution": { "iopub.execute_input": "2026-11-02T22:20:09.109747Z", "iopub.status.busy": "2026-11-02T22:20:09.109630Z", "iopub.status.idle": "2026-11-02T22:20:14.879320Z", "shell.execute_reply": "2026-11-02T22:20:14.878860Z" } }, "outputs": [ { "data": { "text/html": [ "
| \n", " | \n", " | accuracy | \n", "log loss | \n", "leaves | \n", "
|---|---|---|---|---|
| dataset | \n", "model | \n", "\n", " | \n", " | \n", " |
| wine | \n", "GUIDE constant | \n", "0.905 | \n", "0.487 | \n", "6.4 | \n", "
| GUIDE lda | \n", "0.989 | \n", "0.020 | \n", "1.0 | \n", "|
| GUIDE lda_splits | \n", "0.950 | \n", "0.232 | \n", "3.5 | \n", "|
| CART | \n", "0.854 | \n", "3.580 | \n", "7.4 | \n", "|
| LDA | \n", "0.989 | \n", "0.019 | \n", "NaN | \n", "|
| breast cancer | \n", "GUIDE constant | \n", "0.942 | \n", "0.249 | \n", "7.1 | \n", "
| GUIDE lda | \n", "0.958 | \n", "0.149 | \n", "1.7 | \n", "|
| GUIDE lda_splits | \n", "0.967 | \n", "0.158 | \n", "2.1 | \n", "|
| CART | \n", "0.933 | \n", "1.211 | \n", "14.3 | \n", "|
| LDA | \n", "0.956 | \n", "0.130 | \n", "NaN | \n", "|
| oblique (synthetic) | \n", "GUIDE constant | \n", "0.840 | \n", "0.693 | \n", "33.6 | \n", "
| GUIDE lda | \n", "0.987 | \n", "0.103 | \n", "5.7 | \n", "|
| GUIDE lda_splits | \n", "0.982 | \n", "0.110 | \n", "3.0 | \n", "|
| CART | \n", "0.842 | \n", "2.768 | \n", "41.3 | \n", "|
| LDA | \n", "0.945 | \n", "0.172 | \n", "NaN | \n", "