Introduction

The idea of “Linear Regression Trees” is to grow a tree, similar to a decision tree, in which every end node is associated with linear regression for some or all of the variables in the data.

The first idea and implementation was done by Ross Quinlan (of C4.5 fame) in his M5 program. The following packages contain implementations for building linear regression trees:

We will use the “Boston Housing” data as an example.

library(mlbench)
data("BostonHousing")
House = BostonHousing[, -14]
value = BostonHousing$medv

The ‘Cubist’ package

library(Cubist)

The cubist() function takes as input a data frame (or matrix) and numerical output. We can decide about the number of ‘committees’ it will apply.

mod1  = cubist(x = House, y = value)
mod10 = cubist(x = House, y = value, committees = 10)

The summary() shows us the rules of the generated model. We can see the rules of the tree and the regression equations at the nodes.

summary(mod1)

Call:
cubist.default(x = House, y = value)


Cubist [Release 2.07 GPL Edition]  Mon Dec  7 19:47:25 2020
---------------------------------

    Target attribute `outcome'

Read 506 cases (14 attributes) from undefined.data

Model:

  Rule 1: [101 cases, mean 13.84, range 5 to 27.5, est err 1.98]

    if
    nox > 0.668
    then
    outcome = -1.11 + 2.93 dis + 21.4 nox - 0.33 lstat + 0.008 b
              - 0.13 ptratio - 0.02 crim - 0.003 age + 0.1 rm

  Rule 2: [203 cases, mean 19.42, range 7 to 31, est err 2.10]

    if
    nox <= 0.668
    lstat > 9.59
    then
    outcome = 23.57 + 3.1 rm - 0.81 dis - 0.71 ptratio - 0.048 age
              - 0.15 lstat + 0.01 b - 0.0041 tax - 5.2 nox + 0.05 crim
              + 0.02 rad

  Rule 3: [43 cases, mean 24.00, range 11.9 to 50, est err 2.56]

    if
    rm <= 6.226
    lstat <= 9.59
    then
    outcome = 1.18 + 3.83 crim + 4.3 rm - 0.06 age - 0.11 lstat - 0.003 tax
              - 0.09 dis - 0.08 ptratio

  Rule 4: [163 cases, mean 31.46, range 16.5 to 50, est err 2.78]

    if
    rm > 6.226
    lstat <= 9.59
    then
    outcome = -4.71 + 2.22 crim + 9.2 rm - 0.83 lstat - 0.0182 tax
              - 0.72 ptratio - 0.71 dis - 0.04 age + 0.03 rad - 1.7 nox
              + 0.008 zn


Evaluation on training data (506 cases):

    Average  |error|               2.10
    Relative |error|               0.32
    Correlation coefficient        0.94


    Attribute usage:
      Conds  Model

       80%   100%    lstat
       60%    92%    nox
       40%   100%    rm
             100%    crim
             100%    age
             100%    dis
             100%    ptratio
              80%    tax
              72%    rad
              60%    b
              32%    zn


Time: 0.0 secs

Use “root mean squared error” (RMSE) to compare the two models. Asking for more committees wil improve the model significantly,

rmse(value, predict(mod1, House))
[1] 3.025956
rmse(value, predict(mod10, House))
[1] 2.53904

while the plot of actual versus fitted prices does not show such a clear advantage.

plot(value, predict(mod1, House), col = "black")
points(value, predict(mod10, House), col = "red")
grid()

lmtree in package ‘partykit’

’partykit has two functions, mob() and lmtree(). The algorithmic work is performed by mob(), while lmtree() simplifies the user call.

library(partykit)
Loading required package: grid
Loading required package: libcoin
Loading required package: mvtnorm

The formula for lmtree() in general looks like:

y ~ z1 + ... + zl or 
y ~ x1 + ... + xk | z1 + ... + zl

where the z1, ..., zl are the variables are used for building the tree, and the x1, ..., xk are used in the linear regressions; these two sets can be overlapping.

mod3 = lmtree(medv ~ . | .,data = BostonHousing)
rmse(value, predict(mod3, House))
[1] 3.241452
plot(value, predict(mod3, House), col = "black")
points(value, predict(mod10, House), col = "red")
grid()

# plot(value, abs(value - predict(mod10, House)), type ='h')

mape3  = round(100 * abs(value - predict(mod3,  House)) / value, 3)
mape10 = round(100 * abs(value - predict(mod10, House)) / value, 3)
plot(  value, mape10, pch = 20, col = "black")
points(value, mape3,  col = 2)
grid()

Comparison with Random Forest

For a comparison, we will apply a default Random Forest on the data and calculate the RMS error.

rf = randomForest(medv ~ ., data = BostonHousing)
rf

Call:
 randomForest(formula = medv ~ ., data = BostonHousing) 
               Type of random forest: regression
                     Number of trees: 500
No. of variables tried at each split: 4

          Mean of squared residuals: 10.07977
                    % Var explained: 88.06
rmse(value, predict(rf))
[1] 3.174865

Of course, this is not a reliable accuracy value, it only shows that Quinlans M5 in ‘Cubist’ generates an excellent fit.

LS0tCnRpdGxlOiAiTGluZWFyIHJlZ3Jlc3Npb24gdHJlZSBtb2RlbHMiCmF1dGhvcjogIkggVyBCb3JjaGVycyIKZGF0ZTogIkRlYyAyMDIwIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgpgYGB7ciBlY2hvPUZBTFNFfQpybXNlID0gZnVuY3Rpb24oeCwgeSkgc3FydChzdW0oKHgteSleMikgLyBsZW5ndGgoeCkpCmBgYAoKIyMgSW50cm9kdWN0aW9uCgpUaGUgaWRlYSBvZiAiTGluZWFyIFJlZ3Jlc3Npb24gVHJlZXMiIGlzIHRvIGdyb3cgYSB0cmVlLCBzaW1pbGFyIHRvIGEgZGVjaXNpb24gdHJlZSwgaW4gd2hpY2ggZXZlcnkgZW5kIG5vZGUgaXMgYXNzb2NpYXRlZCB3aXRoIGxpbmVhciByZWdyZXNzaW9uIGZvciBzb21lIG9yIGFsbCBvZiB0aGUgdmFyaWFibGVzIGluIHRoZSBkYXRhLgoKVGhlIGZpcnN0IGlkZWEgYW5kIGltcGxlbWVudGF0aW9uIHdhcyBkb25lIGJ5IFJvc3MgUXVpbmxhbiAob2YgQzQuNSBmYW1lKSBpbiBoaXMgTTUgcHJvZ3JhbS4gVGhlIGZvbGxvd2luZyBwYWNrYWdlcyBjb250YWluIGltcGxlbWVudGF0aW9ucyBmb3IgYnVpbGRpbmcgbGluZWFyIHJlZ3Jlc3Npb24gdHJlZXM6CgoqIFBhY2thZ2UgJ0N1YmlzdCcgd2l0aCB0aGUgYGN1YmlzdCgpYCBmdW5jdGlvblwKICAoQ3ViaXN0IHdhcyB0aGUgbmFtZSBvZiB0aGUgdG9vbCB0aGF0IFF1aW5sYW4gc29sZCB0aHJvdWdoIGhpcyBSdWxlUXVlc3QgY29tcGFueS4pCgoqIFBhY2thZ2UgJ3BhcnR5a2l0JyBwcm92aWRlcyBmdW5jdGlvbnMgYG1vYigpYCBhbmQgbG10cmVlKCkgZm9yIFwKICAiTW9kZWwtYmFzZWQgcmVjdXJzaXZlIHBhcnRpdGlvbmluZyBiYXNlZCBvbiBsZWFzdCBzcXVhcmVzIHJlZ3Jlc3Npb24uIgoKKiBgTTVQKClgICgiTTUgUHJpbWUiKSBpbiB0aGUgUldla2EgcGFja2FnZSAocGFydCBvZiB0aGUgV2VrYSBzb2Z0d2FyZSksXAogIGEgcmVpbXBsZW1lbnRhdGlvbiBvZiB0aGUgTTUgYWxnb3JpdGhtIGluIEphdmEuCgpXZSB3aWxsIHVzZSB0aGUgIkJvc3RvbiBIb3VzaW5nIiBkYXRhIGFzIGFuIGV4YW1wbGUuCgpgYGB7cn0KbGlicmFyeShtbGJlbmNoKQpkYXRhKCJCb3N0b25Ib3VzaW5nIikKSG91c2UgPSBCb3N0b25Ib3VzaW5nWywgLTE0XQp2YWx1ZSA9IEJvc3RvbkhvdXNpbmckbWVkdgpgYGAKCiMjIFRoZSAnQ3ViaXN0JyBwYWNrYWdlCgpgYGB7cn0KbGlicmFyeShDdWJpc3QpCmBgYAoKVGhlIGBjdWJpc3QoKWAgZnVuY3Rpb24gdGFrZXMgYXMgaW5wdXQgYSBkYXRhIGZyYW1lIChvciBtYXRyaXgpIGFuZCBudW1lcmljYWwgb3V0cHV0LiBXZSBjYW4gZGVjaWRlIGFib3V0IHRoZSBudW1iZXIgb2YgJ2NvbW1pdHRlZXMnIGl0IHdpbGwgYXBwbHkuCgpgYGB7cn0KbW9kMSAgPSBjdWJpc3QoeCA9IEhvdXNlLCB5ID0gdmFsdWUpCm1vZDEwID0gY3ViaXN0KHggPSBIb3VzZSwgeSA9IHZhbHVlLCBjb21taXR0ZWVzID0gMTApCmBgYAoKVGhlIGBzdW1tYXJ5KClgIHNob3dzIHVzIHRoZSBydWxlcyBvZiB0aGUgZ2VuZXJhdGVkIG1vZGVsLiBXZSBjYW4gc2VlIHRoZSBydWxlcyBvZiB0aGUgdHJlZSBhbmQgdGhlIHJlZ3Jlc3Npb24gZXF1YXRpb25zIGF0IHRoZSBub2Rlcy4KCmBgYHtyfQpzdW1tYXJ5KG1vZDEpCmBgYAoKVXNlICJyb290IG1lYW4gc3F1YXJlZCBlcnJvciIgKFJNU0UpIHRvIGNvbXBhcmUgdGhlIHR3byBtb2RlbHMuIEFza2luZyBmb3IgbW9yZSBjb21taXR0ZWVzIHdpbCBpbXByb3ZlIHRoZSBtb2RlbCBzaWduaWZpY2FudGx5LAoKYGBge3J9CnJtc2UodmFsdWUsIHByZWRpY3QobW9kMSwgSG91c2UpKQpybXNlKHZhbHVlLCBwcmVkaWN0KG1vZDEwLCBIb3VzZSkpCmBgYAoKd2hpbGUgdGhlIHBsb3Qgb2YgYWN0dWFsIHZlcnN1cyBmaXR0ZWQgcHJpY2VzIGRvZXMgbm90IHNob3cgc3VjaCBhIGNsZWFyIGFkdmFudGFnZS4KCmBgYHtyfQpwbG90KHZhbHVlLCBwcmVkaWN0KG1vZDEsIEhvdXNlKSwgY29sID0gImJsYWNrIikKcG9pbnRzKHZhbHVlLCBwcmVkaWN0KG1vZDEwLCBIb3VzZSksIGNvbCA9ICJyZWQiKQpncmlkKCkKYGBgCgojIyBgbG10cmVlYCBpbiBwYWNrYWdlICdwYXJ0eWtpdCcKCidwYXJ0eWtpdCBoYXMgdHdvIGZ1bmN0aW9ucywgYG1vYigpYCBhbmQgYGxtdHJlZSgpYC4gVGhlIGFsZ29yaXRobWljIHdvcmsgaXMgcGVyZm9ybWVkIGJ5IGBtb2IoKWAsIHdoaWxlIGBsbXRyZWUoKWAgc2ltcGxpZmllcyB0aGUgdXNlciBjYWxsLgoKYGBge3J9CmxpYnJhcnkocGFydHlraXQpCmBgYAoKVGhlIGZvcm11bGEgZm9yIGBsbXRyZWUoKWAgaW4gZ2VuZXJhbCBsb29rcyBsaWtlOgoKYGBgey5yfQp5IH4gejEgKyAuLi4gKyB6bCBvciAKeSB+IHgxICsgLi4uICsgeGsgfCB6MSArIC4uLiArIHpsCmBgYAoKd2hlcmUgdGhlIGB6MSwgLi4uLCB6bGAgYXJlIHRoZSB2YXJpYWJsZXMgYXJlIHVzZWQgZm9yIGJ1aWxkaW5nIHRoZSB0cmVlLCBhbmQgdGhlIGB4MSwgLi4uLCB4a2AgYXJlIHVzZWQgaW4gdGhlIGxpbmVhciByZWdyZXNzaW9uczsgdGhlc2UgdHdvIHNldHMgY2FuIGJlIG92ZXJsYXBwaW5nLgoKYGBge3J9Cm1vZDMgPSBsbXRyZWUobWVkdiB+IC4gfCAuLGRhdGEgPSBCb3N0b25Ib3VzaW5nKQpgYGAKCmBgYHtyfQpybXNlKHZhbHVlLCBwcmVkaWN0KG1vZDMsIEhvdXNlKSkKYGBgCgpgYGB7cn0KcGxvdCh2YWx1ZSwgcHJlZGljdChtb2QzLCBIb3VzZSksIGNvbCA9ICJibGFjayIpCnBvaW50cyh2YWx1ZSwgcHJlZGljdChtb2QxMCwgSG91c2UpLCBjb2wgPSAicmVkIikKZ3JpZCgpCmBgYAoKYGBge3J9CiMgcGxvdCh2YWx1ZSwgYWJzKHZhbHVlIC0gcHJlZGljdChtb2QxMCwgSG91c2UpKSwgdHlwZSA9J2gnKQoKbWFwZTMgID0gcm91bmQoMTAwICogYWJzKHZhbHVlIC0gcHJlZGljdChtb2QzLCAgSG91c2UpKSAvIHZhbHVlLCAzKQptYXBlMTAgPSByb3VuZCgxMDAgKiBhYnModmFsdWUgLSBwcmVkaWN0KG1vZDEwLCBIb3VzZSkpIC8gdmFsdWUsIDMpCnBsb3QoICB2YWx1ZSwgbWFwZTEwLCBwY2ggPSAyMCwgY29sID0gImJsYWNrIikKcG9pbnRzKHZhbHVlLCBtYXBlMywgIGNvbCA9IDIpCmdyaWQoKQpgYGAKCiMjIENvbXBhcmlzb24gd2l0aCBSYW5kb20gRm9yZXN0CgpGb3IgYSBjb21wYXJpc29uLCB3ZSB3aWxsIGFwcGx5IGEgZGVmYXVsdCBSYW5kb20gRm9yZXN0IG9uIHRoZSBkYXRhIGFuZCBjYWxjdWxhdGUgdGhlIFJNUyBlcnJvci4KCmBgYHtyfQpyZiA9IHJhbmRvbUZvcmVzdChtZWR2IH4gLiwgZGF0YSA9IEJvc3RvbkhvdXNpbmcpCnJmCmBgYAoKYGBge3J9CnJtc2UodmFsdWUsIHByZWRpY3QocmYpKQpgYGAKCk9mIGNvdXJzZSwgdGhpcyBpcyBub3QgYSByZWxpYWJsZSBhY2N1cmFjeSB2YWx1ZSwgaXQgb25seSBzaG93cyB0aGF0IFF1aW5sYW5zIE01IGluICdDdWJpc3QnIGdlbmVyYXRlcyBhbiBleGNlbGxlbnQgZml0Lgo=