When we learned linear regression we learned the underlying model by looking at the equation. However, decision trees are not based on a mathematical equation. Instead, they are based on a series of conditions, so to understand how they work let's make one from scratch! We will explore the code that underlies decision trees, visualize the current step using a subset of the iris dataset, and then put this code into functions, so that we can pull it all together into our very own decision tree function.
Let's start by providing the same decision tree image we saw in the previous lesson, as we will be referencing it throughout this lesson.

And we will use the following subset of the iris dataset to visualize the decision tree as we build it.
| Sepal.Length | Petal.Length |
|---|---|
| 5.1 | 1.4 |
| 4.9 | 1.4 |
| 4.7 | 1.3 |
| 6.3 | 6 |
| 5.8 | 5.1 |
To begin, we need to consider, how will we store the information regarding the decision tree's nodes. In this application we will use a class to store the information for each node. The benefit of this is that it collects the information into one place, and allows us to create multiple instances of the class, each representing a different node.
We make a distinction of two types of nodes. Any node that has children - nodes connected to it below it, such as nodes: 1, 2, and 3 in the image - are called decision nodes. Nodes that have no children - nodes: 4, 5, and 6 - are called leaf nodes. So we'll construct these as two different classes. Let's go over the details of each class and map it to the code that constructs these in the below code editor.
if x ≤ 5, where 5 is the split value.Let's start making the decision tree by hand. In this application we will begin by choosing the split values, but later we will write a function that will choose the split values for us.
We will start by creating the root node, which is node 1 in the image above. The root node is a decision node, so we will create an instance of the DecisionNode class. We set the split value to 5. When we set the split value to 5, we are splitting the data based on the variable with values below or above it. In this particular instance, two rows - which we will call observations from now on - will go to the left node, and three observations will go to the right node.
Left data that goes to node 2
| Sepal.Length | Petal.Length |
|---|---|
| 4.9 | 1.4 |
| 4.7 | 1.3 |
Right data that goes to node 3
| Sepal.Length | Petal.Length |
|---|---|
| 5.1 | 1.4 |
| 6.3 | 6 |
| 5.8 | 5.1 |
The predicted value for either node is the average of the
outcome variable, which is petal length. So for node 2
this is:
We could continue with this until we reach the maximum depth of the tree, but let's stop here for now. We will want to generalize this process to find the best split value for us. But to determine what is the best split value, we need to choose a model evaluation metric, which will be the topic of the next section.
A model evaluation metric is a quantifiable measure used to assess the performance of a model, enabling comparison with other models. In this application we will choose the mean squared error (mse) as our model evaluation metric. The mean squared error is the average of the squared differences between the predicted value and the actual value. But in this case we will modify it to be the average of the squared differences between the outcome values in each node and the mean of these values. This is so we can save the task of prediction to the next lesson.
Let's write a function that will calculate the mean squared error
for us. We will call this function calculate_mse. It will take one
parameter, a numpy array of values, and return the mean squared
error.
Now that we have a model evaluation metric, we can write a function
that will find the best split value for us. We will call this function
find_best_split. Let's go over the code.
Now that we have a function that will find the best split value for us,
we can write a function that will build the tree for us. We will call
this function build_tree. Note that this function is a
type of function called a recursive function. These are a class
of functions that call themselves. They are usually more difficult to understand
than normal functions. So if you would care to learn about them or have some review
on it, then feel free to check out these two lessons on recurions:
Learn recursion's base case
and Learn recursion by studying
path walking on trees. Once you are comfortable with recursion then let's
go over the code.
best_split function and get the
split and mse values.We have now created a decision tree from scratch! We have learned how to create nodes using classes, how to find the best split value, and how to build the tree. Feel free to play around with the code and try larger depths to see how the tree changes. This is a simplified tree building process, but it is the foundation for the more complex kind of tree that is used in the scikit-learn library.