# Estimating Decision Tree Models

The *rxDTree* function in RevoScaleR fits tree-based models using a binning-based recursive partitioning algorithm. The resulting model is similar to that produced by the recommended R package *rpart*. Both classification-type trees and regression-type trees are supported; as with *rpart*, the difference is determined by the nature of the response variable: a factor response generates a classification tree; a numeric response generates a regression tree.

### The rxDTree Algorithm

Decision trees are effective algorithms widely used for classification and regression. Building a decision tree generally requires that all continuous variables be sorted in order to decide where to split the data. This sorting step becomes time and memory prohibitive when dealing with large data. Various techniques have been proposed to overcome the sorting obstacle, which can be roughly classified into two groups: performing data pre-sorting or using approximate summary statistic of the data. While pre-sorting techniques follow standard decision tree algorithms more closely, they cannot accommodate very large data sets. These big data decision trees are normally parallelized in various ways to enable large scale learning: data parallelism partitions the data either horizontally or vertically so that different processors see different observations or variables and task parallelism builds different tree nodes on different processors.

The *rxDTree* algorithm is an approximate decision tree algorithm with horizontal data parallelism, especially designed for handling very large data sets. It uses histograms as the approximate compact representation of the data and builds the decision tree in a breadth-first fashion. The algorithm can be executed in parallel settings such as a multicore machine or a distributed environment with a master-worker architecture. Each worker gets only a subset of the observations of the data, but has a view of the complete tree built so far. It builds a histogram from the observations it sees, which essentially compresses the data to a fixed amount of memory. This approximate description of the data is then sent to a master with constant low communication complexity independent of the size of the data set. The master integrates the information received from each of the workers and determines which terminal tree nodes to split and how. Since the histogram is built in parallel, it can be quickly constructed even for extremely large data sets.

With *rxDTree*, you can control the balance between time complexity and prediction accuracy by specifying the maximum number of bins for the histogram. The algorithm builds the histogram with roughly equal number of observations in each bin and takes the boundaries of the bins as the candidate splits for the terminal tree nodes. Since only a limited number of split locations are examined, it is possible that a suboptimal split point is chosen causing the entire tree to be different from the one constructed by a standard algorithm. However, it has been shown analytically that the error rate of the parallel tree approaches the error rate of the serial tree, even though the trees are not identical. You can set the number of bins in the histograms to control the tradeoff between accuracy and speed: a large number of bins allows a more accurate description of the data and thus more accurate results, whereas a small number of bins reduces time complexity and memory usage.

When integer predictors for which the number of bins equals or exceeds the number of observations, the rxDTree algorithm produces the same results as the standard sorting algorithms.

### A Simple Classification Tree

In a previous article, we fit a simple logistic regression model to rpart’s kyphosis data. That model is easily recast as a classification tree using *rxDTree* as follows:

```
data("kyphosis", package="rpart")
kyphTree <- rxDTree(Kyphosis ~ Age + Start + Number, data = kyphosis,
cp=0.01)
kyphTree
Call:
rxDTree(formula = Kyphosis ~ Age + Start + Number, data = kyphosis,
cp = 0.01)
Data: kyphosis
Number of valid observations: 81
Number of missing observations: 0
Tree representation:
n= 81
node), split, n, loss, yval, (yprob)
* denotes terminal node
1) root 81 17 absent (0.79012346 0.20987654)
2) Start>=8.5 62 6 absent (0.90322581 0.09677419)
4) Start>=14.5 29 0 absent (1.00000000 0.00000000) *
5) Start< 14.5 33 6 absent (0.81818182 0.18181818)
10) Age< 55 12 0 absent (1.00000000 0.00000000) *
11) Age>=55 21 6 absent (0.71428571 0.28571429)
22) Age>=111 14 2 absent (0.85714286 0.14285714) *
23) Age< 111 7 3 present (0.42857143 0.57142857) *
3) Start< 8.5 19 8 present (0.42105263 0.57894737) *
```

Recall our conclusions from fitting this model earlier with rxCube: the probability of the post-operative complication Kyphosis seems to be greater if the Start is a cervical vertebra and as more vertebrae are involved in the surgery. Similarly, it appears that the dependence on age is non-linear: it first increases with age, peaks in the range 5-9, and then decreases again.

The rxDTree model seems to confirm these earlier conclusions—for Start < 8.5, 11 of 19 observed subjects developed Kyphosis, while none of the 29 subjects with Start >= 14.5 did. For the remaining 33 subjects, Age was the primary splitting factor, and as we observed earlier, ages 5 to 9 had the highest probability of developing Kyphosis.

The returned object *kyphTree* is an object of class *rxDTree*. The *rxDTree* class is modeled closely on the *rpart* class, so that objects of class *rxDTree* have most essential components of an *rpart* object: frame, cptable, splits, etc. By default, however, *rxDTree* objects do not inherit from class *rpart*. You can, however, use the *rxAddInheritance* function to add *rpart* inheritance to *rxDTree* objects.

### A Simple Regression Tree

As a simple example of a regression tree, consider the *mtcars* data set and let’s fit gas mileage (*mpg*) using displacement (*disp*) as a predictor:

```
# A Simple Regression Tree
mtcarTree <- rxDTree(mpg ~ disp, data=mtcars)
mtcarTree
Call:
rxDTree(formula = mpg ~ disp, data = mtcars)
Data: mtcars
Number of valid observations: 32
Number of missing observations: 0
Tree representation:
n= 32
node), split, n, deviance, yval
* denotes terminal node
1) root 32 1126.0470 20.09063
2) disp>=163.5 18 143.5894 15.99444 *
3) disp< 163.5 14 292.1343 25.35714 *
```

There’s a clear split between larger cars (those with engine displacement greater than 163.5 cubic inches) and smaller cars.

### A Larger Regression Tree Model

As a more complex example, we return to the censusWorkers data. We create a regression tree predicting wage income from age, sex, and weeks worked, using the perwt variable as probability weights:

```
# A Larger Regression Tree Model
censusWorkers <- file.path(rxGetOption("sampleDataDir"),
"CensusWorkers.xdf")
rxGetInfo(censusWorkers, getVarInfo=TRUE)
incomeTree <- rxDTree(incwage ~ age + sex + wkswork1, pweights = "perwt",
maxDepth = 3, minBucket = 30000, data = censusWorkers)
incomeTree
Call:
rxDTree(formula = incwage ~ age + sex + wkswork1, data = censusWorkers,
pweights = "perwt", minBucket = 30000, maxDepth = 3)
File: C:\Program Files\Microsoft\MRO-for-RRE\8.0\R-3.2.2\ library\RevoScaleR\SampleData\CensusWorkers.xdf
Number of valid observations: 351121
Number of missing observations: 0
Tree representation:
n= 351121
node), split, n, deviance, yval
* denotes terminal node
1) root 351121 1.177765e+16 35788.47
2) sex=Female 161777 2.271425e+15 26721.09
4) wkswork1< 51.5 56874 5.757587e+14 19717.74 *
5) wkswork1>=51.5 104903 1.608813e+15 30505.87
10) age< 34.5 31511 2.500078e+14 25836.32 *
11) age>=34.5 73392 1.338235e+15 32576.74 *
3) sex=Male 189344 9.008506e+15 43472.71
6) age< 31.5 48449 6.445334e+14 27577.80 *
7) age>=31.5 140895 8.010642e+15 49221.82
14) wkswork1< 51.5 34359 1.550839e+15 37096.62 *
15) wkswork1>=51.5 106536 6.326896e+15 53082.08 *
```

The primary split here (not surprising given our analysis of this data set in the Tutorial: Analyzing US census data with RevoScaleR) is sex; women on average earn substantially less than men. The additional splits are also not surprising; older workers earn more than younger workers, and those who work more hours tend to earn more than those who work fewer hours.

### Controlling the Model Fit

The *rxDTree* function has a number of options for controlling the model fit. Most of these control parameters are familiar to *rpart* users, but the defaults have been modified in some cases to better support large data tree models. A full listing of these options can be found in the *rxDTree* help file, but the following have been found in our testing to be the most useful at controlling the time required to fit a model with *rxDTree*:

*xVal*: controls the number of folds used to perform cross-validation. The default of 2 allows for some pruning; once you have closed in a model you may want to increase the value for final fitting and pruning.*maxDepth*: sets the maximum depth of any node of the tree. Computations grow rapidly more expensive as the depth increases, so we recommend a maxDepth of 10 to 15.*maxCompete*: specifies the number of “competitor splits” retained in the output. By default,*rxDTree*sets this to 0, but a setting of 3 or 4 can be useful for diagnostic purposes in determining why a particular split was chosen.*maxSurrogate*: specifies the number of surrogate splits retained in the output. Again, by default*rxDTree*sets this to 0. Surrogate splits are used to assign an observation when the primary split variable is missing for that observation.*maxNumBins*: controls the maximum number of bins used for each variable. Managing the number of bins is important in controlling memory usage. The default is to use the larger of 101 and the square root of the number of observations for small to moderate size data sets (up to about one million observations), but for larger sets to use 1001 bins. For small data sets with continuous predictors, you may find that you need to increase the*maxNumBins*to obtain models that resemble those from rpart.

For large data sets (100000 or more observations), you may need to adjust the following parameters to obtain meaningful models:

*cp*: a complexity parameter and sets the bar for how much a split must reduce the complexity before being accepted. We have set the default to 0 and recommend using*maxDepth*and*minBucket*to control your tree sizes. If you want to specify a*cp*value, start with a conservative value, such as rpart’s 0.01; if you don’t see an adequate number of splits, decrease the*cp*by powers of 10 until you do. For our large airline data, we have found interesting models begin with a*cp*of about 1e-4.*minSplit*,*minBucket*: determine how many observations must be in a node before a split is attempted (*minSplit*) and how many must remain in a terminal node (*minBucket*).

### Large Data Tree Models

Scaling decision trees to very large data sets is possible with *rxDTree* but should be done with caution—the wrong choice of model parameters can easily lead to models that take hours or longer to estimate, even in a distributed computing environment. For example, in the Tutorial: Load and analyze a large airline data set with RevoScaleR, we estimated linear models using the large airline data and used the variable *Origin* as a predictor in several models. The *Origin* variable is a factor variable with 373 levels with no obvious ordering. Incorporating this variable into an *rxDTree* model that is performing more than two level classification can easily consume hours of computation time. To prevent such unintended consequences, *rxDTree* has a parameter *maxUnorderedLevels*, which defaults to 32; in the case of *Origin*, this parameter would flag an error. However, a factor variable of “Region” which groups the airports of *Origin* by location may well be a useful proxy, and can be constructed to have only a limited number of levels. Numeric and ordered factor predictors are much more easily incorporated into the model.

As an example of a large data classification tree, consider the following simple model using the 7% subsample of the full airline data (uses the variable *ArrDel15* indicating flights with an arrival delay of 15 minutes or more):

```
# Large Data Tree Models
bigDataDir <- "C:/MRS/Data"
sampleAirData <- file.path(bigDataDir, "AirOnTime7Pct.xdf")
airlineTree <- rxDTree(ArrDel15 ~ CRSDepTime + DayOfWeek, data = sampleAirData,
blocksPerRead = 30, maxDepth = 5, cp = 1e-5)
```

The

`blocksPerRead`

argument is ignored if run locally using R Client. Learn more...

The default cp of 0 produces a very large number of splits; specifying *cp* = 1e-5 produces a more manageable set of splits in this model:

```
airlineTree
Call:
rxDTree(formula = ArrDel15 ~ CRSDepTime + DayOfWeek, data = sampleAirData,
maxDepth = 5, cp = 1e-05, blocksPerRead = 30)
File: C:\MRS\Data\AirOnTime7Pct.xdf
Number of valid observations: 10186272
Number of missing observations: 213483
Tree representation:
n= 10186272
node), split, n, deviance, yval
* denotes terminal node
1) root 10186272 1630331.000 0.20008640
2) CRSDepTime< 13.1745 4941190 642452.000 0.15361830
4) CRSDepTime< 8.3415 1777685 189395.700 0.12123970
8) CRSDepTime>=0.658 1717573 178594.900 0.11787560
16) CRSDepTime< 6.7665 599548 52711.450 0.09740671
32) CRSDepTime>=1.625 578762 49884.260 0.09526714 *
33) CRSDepTime< 1.625 20786 2750.772 0.15698070 *
17) CRSDepTime>=6.7665 1118025 125497.500 0.12885220
34) DayOfWeek=Sun 134589 11722.540 0.09638975 *
35) DayOfWeek=Mon,Tues,Wed,Thur,Fri,Sat 983436 113613.80 0.13329490 *
9) CRSDepTime< 0.658 60112 10225.960 0.21736090
18) CRSDepTime>=0.2415 9777 1429.046 0.17776410 *
19) CRSDepTime< 0.2415 50335 8778.609 0.22505220 *
5) CRSDepTime>=8.3415 3163505 450145.400 0.17181290
10) CRSDepTime< 11.3415 1964400 268472.400 0.16335320
20) DayOfWeek=Sun 271900 30839.160 0.13043400
40) CRSDepTime< 9.7415 126700 13381.800 0.12002370 *
41) CRSDepTime>=9.7415 145200 17431.650 0.13951790 *
21) DayOfWeek=Mon,Tues,Wed,Thur,Fri,Sat 1692500 237291.300 0.16864170
42) DayOfWeek=Tues,Wed,Sat 835355 113384.500 0.16196470 *
43) DayOfWeek=Mon,Thur,Fri 857145 123833.200 0.17514890 *
11) CRSDepTime>=11.3415 1199105 181302.000 0.18567180
22) DayOfWeek=Mon,Tues,Wed,Sat,Sun 852016 124610.900 0.17790390
44) DayOfWeek=Tues,Sun 342691 48917.520 0.17250230 *
45) DayOfWeek=Mon,Wed,Sat 509325 75676.600 0.18153830 *
23) DayOfWeek=Thur,Fri 347089 56513.560 0.20474000 *
3) CRSDepTime>=13.1745 5245082 967158.500 0.24386220
6) DayOfWeek=Mon,Tues,Wed,Sat,Sun 3708992 651771.300 0.22746990
12) DayOfWeek=Sat 635207 96495.570 0.18681000
24) CRSDepTime>=20.2745 87013 12025.600 0.16564190 *
25) CRSDepTime< 20.2745 548194 84424.790 0.19016990 *
13) DayOfWeek=Mon,Tues,Wed,Sun 3073785 554008.600 0.23587240
26) CRSDepTime< 16.508 1214018 203375.700 0.21281150
52) CRSDepTime< 15.1325 709846 114523.300 0.20223400 *
53) CRSDepTime>=15.1325 504172 88661.120 0.22770400 *
27) CRSDepTime>=16.508 1859767 349565.800 0.25092610
54) DayOfWeek=Mon,Tues 928523 168050.900 0.23729730 *
55) DayOfWeek=Wed,Sun 931244 181170.600 0.26451500 *
7) DayOfWeek=Thur,Fri 1536090 311984.200 0.28344240
14) CRSDepTime< 15.608 445085 82373.020 0.24519140
28) CRSDepTime< 14.6825 273682 49360.240 0.23609880 *
29) CRSDepTime>=14.6825 171403 32954.030 0.25970960 *
15) CRSDepTime>=15.608 1091005 228694.300 0.29904720
30) CRSDepTime>=21.9915 64127 11932.930 0.24718140 *
31) CRSDepTime< 21.9915 1026878 216578.100 0.30228620
62) CRSDepTime< 17.0745 264085 53451.260 0.28182970 *
63) CRSDepTime>=17.0745 762793 162978.000 0.30936830 *
```

Looking at the fitted objects cptable component, we can look at whether we have overfitted the model:

```
airlineTree$cptable
CP nsplit rel error xerror xstd
1 1.270950e-02 0 1.0000000 1.0000002 0.0004697734
2 2.087342e-03 1 0.9872905 0.9873043 0.0004629111
3 1.785488e-03 2 0.9852032 0.9852215 0.0004625035
4 7.772395e-04 3 0.9834177 0.9834381 0.0004608330
5 6.545095e-04 4 0.9826404 0.9826606 0.0004605065
6 5.623968e-04 5 0.9819859 0.9820200 0.0004602950
7 3.525848e-04 6 0.9814235 0.9814584 0.0004602578
8 2.367018e-04 7 0.9810709 0.9811071 0.0004600062
9 2.274981e-04 8 0.9808342 0.9808700 0.0004597725
10 2.112635e-04 9 0.9806067 0.9806567 0.0004596187
11 2.097651e-04 10 0.9803955 0.9804365 0.0004595150
12 1.173008e-04 11 0.9801857 0.9803311 0.0004594245
13 1.124180e-04 12 0.9800684 0.9800354 0.0004592792
14 1.089414e-04 13 0.9799560 0.9800354 0.0004592792
15 9.890134e-05 14 0.9798471 0.9799851 0.0004592187
16 9.125152e-05 15 0.9797482 0.9798766 0.0004591605
17 4.687397e-05 16 0.9796569 0.9797504 0.0004591074
18 4.510554e-05 17 0.9796100 0.9797292 0.0004590784
19 3.603837e-05 18 0.9795649 0.9796812 0.0004590301
20 2.771093e-05 19 0.9795289 0.9796383 0.0004590247
21 1.577140e-05 20 0.9795012 0.9796013 0.0004590000
22 1.122899e-05 21 0.9794854 0.9795671 0.0004589736
23 1.025944e-05 22 0.9794742 0.9795560 0.0004589678
24 1.000000e-05 23 0.9794639 0.9795455 0.0004589660
```

We see a steady decrease in cross-validation error (xerror) as the number of splits increase, but note that at about nsplit=11 the rate of change slows dramatically. The optimal model is probably very near here. (The total number of passes through the data is equal to a base of *maxDepth* + 3, plus *xVal* times (*maxDepth* + 2), where *xVal* is the number of folds for cross-validation and *maxDepth* is the maximum tree depth. Thus a depth 10 tree with 4-fold cross-validation requires 13 + 48, or 61, passes through the data.)

To prune the tree back, use the *prune.rxDTree* function:

```
airlineTree4 <- prune.rxDTree(airlineTree, cp=1e-4)
airlineTree4
Call:
rxDTree(formula = ArrDel15 ~ CRSDepTime + DayOfWeek, data = sampleAirData,
maxDepth = 5, cp = 1e-05, blocksPerRead = 30)
File: C:\MRS\Data\AirOnTime7Pct.xdf
Number of valid observations: 10186272
Number of missing observations: 213483
Tree representation:
n= 10186272
node), split, n, deviance, yval
* denotes terminal node
1) root 10186272 1630331.00 0.20008640
2) CRSDepTime< 13.1745 4941190 642452.00 0.15361830
4) CRSDepTime< 8.3415 1777685 189395.70 0.12123970
8) CRSDepTime>=0.658 1717573 178594.90 0.11787560
16) CRSDepTime< 6.7665 599548 52711.45 0.09740671 *
17) CRSDepTime>=6.7665 1118025 125497.50 0.12885220 *
9) CRSDepTime< 0.658 60112 10225.96 0.21736090 *
5) CRSDepTime>=8.3415 3163505 450145.40 0.17181290
10) CRSDepTime< 11.3415 1964400 268472.40 0.16335320
20) DayOfWeek=Sun 271900 30839.16 0.13043400 *
21) DayOfWeek=Mon,Tues,Wed,Thur,Fri,Sat 1692500 237291.30 0.16864170 *
11) CRSDepTime>=11.3415 1199105 181302.00 0.18567180
22) DayOfWeek=Mon,Tues,Wed,Sat,Sun 852016 124610.90 0.17790390 *
23) DayOfWeek=Thur,Fri 347089 56513.56 0.20474000 *
3) CRSDepTime>=13.1745 5245082 967158.50 0.24386220
6) DayOfWeek=Mon,Tues,Wed,Sat,Sun 3708992 651771.30 0.22746990
12) DayOfWeek=Sat 635207 96495.57 0.18681000 *
13) DayOfWeek=Mon,Tues,Wed,Sun 3073785 554008.60 0.23587240
26) CRSDepTime< 16.508 1214018 203375.70 0.21281150
52) CRSDepTime< 15.1325 709846 114523.30 0.20223400 *
53) CRSDepTime>=15.1325 504172 88661.12 0.22770400 *
27) CRSDepTime>=16.508 1859767 349565.80 0.25092610
54) DayOfWeek=Mon,Tues 928523 168050.90 0.23729730 *
55) DayOfWeek=Wed,Sun 931244 181170.60 0.26451500 *
7) DayOfWeek=Thur,Fri 1536090 311984.20 0.28344240
14) CRSDepTime< 15.608 445085 82373.02 0.24519140 *
15) CRSDepTime>=15.608 1091005 228694.30 0.29904720
30) CRSDepTime>=21.9915 64127 11932.93 0.24718140 *
31) CRSDepTime< 21.9915 1026878 216578.10 0.30228620 *
```

If rpart is installed, *prune.rxDTree* acts as a method for the *prune* function, so you can call it more simply:

```
airlineTree4 <- prune(airlineTree, cp=1e-4)
```

For models fit with 2-fold or greater cross-validation, it is useful to use the cross-validation standard error (part of the cptable component) as a guide to pruning. The rpart function plotcp can be useful for this:

```
plotcp(rxAddInheritance(airlineTree))
```

This yields the following plot:

From this plot, it appears we can prune even further, to perhaps seven or eight splits. Looking again at the cptable, a cp of 2.5e-4 seems a reasonable pruning choice:

```
airlineTreePruned <- prune.rxDTree(airlineTree, cp=2.5e-4)
airlineTreePruned
Call:
rxDTree(formula = ArrDel15 ~ CRSDepTime + DayOfWeek, data = sampleAirData,
maxDepth = 5, cp = 1e-05, blocksPerRead = 30)
File: C:\MRS\Data\AirOnTime7Pct.xdf
Number of valid observations: 10186272
Number of missing observations: 213483
Tree representation:
n= 10186272
node), split, n, deviance, yval
* denotes terminal node
1) root 10186272 1630331.00 0.2000864
2) CRSDepTime< 13.1745 4941190 642452.00 0.1536183
4) CRSDepTime< 8.3415 1777685 189395.70 0.1212397
8) CRSDepTime>=0.658 1717573 178594.90 0.1178756 *
9) CRSDepTime< 0.658 60112 10225.96 0.2173609 *
5) CRSDepTime>=8.3415 3163505 450145.40 0.1718129 *
3) CRSDepTime>=13.1745 5245082 967158.50 0.2438622
6) DayOfWeek=Mon,Tues,Wed,Sat,Sun 3708992 651771.30 0.2274699
12) DayOfWeek=Sat 635207 96495.57 0.1868100 *
13) DayOfWeek=Mon,Tues,Wed,Sun 3073785 554008.60 0.2358724
26) CRSDepTime< 16.508 1214018 203375.70 0.2128115 *
27) CRSDepTime>=16.508 1859767 349565.80 0.2509261 *
7) DayOfWeek=Thur,Fri 1536090 311984.20 0.2834424
14) CRSDepTime< 15.608 445085 82373.02 0.2451914 *
15) CRSDepTime>=15.608 1091005 228694.30 0.2990472 *
```

### Handling Missing Values

The *removeMissings* argument to *rxDTree*, as in most RevoScaleR analysis functions, controls how the function deals with missing data in the model fit. If *TRUE*, all rows containing missing values for the response or any predictor variable are removed before model fitting. If *FALSE* (the default), only those rows for which the value of the response or all values of the predictor variables are missing are removed. Using *removeMissings=TRUE* is roughly equivalent to the effect of the *na.omit* function for *rpart*, in that if the file is written out, all rows containing NAs are removed. There is no equivalent for *rxDTree* to the *na.exclude* function, which pads the output with NAs for observations that cannot be predicted. Using *removeMissings=FALSE* is the equivalent of using the *na.rpart* or *na.pass* functions; the data is passed through unchanged, but rows that have no data for either all predictors or the response are excluded from the model.

### Prediction

As with other RevoScaleR analysis functions, prediction is performed using the *rxPredict* function, to which you supply a fitted model object and a set of new data (which may be the original data set, but in any event must contain the variables used in the original model).

The adult data set is a widely used machine learning data set, similar to the censusWorkers data we have already analyzed. The data set is available from the machine learning data repository at UC Irvine (http://archive.ics.uci.edu/ml/datasets/Adult) (and comes in two pieces: a training data set (adult.data) and a test data set (adult.test). This makes it ready-made for use in prediction. To run the following examples, download this data and add a .txt extension, so that you have adult.data.txt and adult.test.txt. (A third file, adult.names, gives a description of the variables; we use this in the code below as a source for the variable names, which are not part of the data files):

```
# Prediction
if (bHasAdultData){
bigDataDir <- "C:/MRS/Data"
adultDataFile <- file.path(bigDataDir, "adult.data.txt")
adultTestFile <- file.path(bigDataDir, "adult.test.txt")
newNames <- c("age", "workclass", "fnlwgt", "education",
"education_num", "marital_status", "occupation", "relationship",
"ethnicity", "sex", "capital_gain", "capital_loss", "hours_per_week",
"native_country", "income")
adultTrain <- rxImport(adultDataFile, stringsAsFactors = TRUE)
names(adultTrain) <- newNames
adultTest <- rxImport(adultTestFile, rowsToSkip = 1,
stringsAsFactors=TRUE)
names(adultTest) <- newNames
adultTree <- rxDTree(income ~ age + sex + hours_per_week, pweights = "fnlwgt",
data = adultTrain)
adultPred <- rxPredict(adultTree, data = adultTest, type="vector")
sum(adultPred == as.integer(adultTest$income))/length(adultTest$income)
} # End of bHasAdultData
[1] 0.7734169
```

The result shows that the fitted model accurately classifies about 77% of the test data.

When using *rxPredict* with *rxDTree* objects, you should keep in mind how it differs from *predict* with *rpart* objects. First, a *data* argument is always required—this can be either the original data or new data; there is no *newData* argument as in *rpart*. Prediction with the original data provides fitted values, not predictions, but the predicted variable name still defaults to *varname_Pred*.

### Visualizing Trees

The RevoTreeView package can be used to plot decision trees from *rxDTree* or *rpart* in an HTML page. Both classification and regression trees are supported. By plotting the tree objects returned by RevoTreeView’s *createTreeView* function in a browser, you can interact with your decision tree. The resulting tree’s HTML page can also be shared with other people or displayed on different machines using the package’s *zipTreeView* function.

As an example, consider a classification tree built from the *kyphosis* data that is included in the *rpart* package. It produces the following text output:

```
data("kyphosis", package="rpart")
kyphTree <- rxDTree(Kyphosis ~ Age + Start + Number,
data = kyphosis, cp=0.01)
kyphTree
Call:
rxDTree(formula = Kyphosis ~ Age + Start + Number, data = kyphosis,
cp = 0.01)
Data: kyphosis
Number of valid observations: 81
Number of missing observations: 0
Tree representation:
n= 81
node), split, n, loss, yval, (yprob)
* denotes terminal node
1) root 81 17 absent (0.79012346 0.20987654)
2) Start>=8.5 62 6 absent (0.90322581 0.09677419)
4) Start>=14.5 29 0 absent (1.00000000 0.00000000) *
5) Start< 14.5 33 6 absent (0.81818182 0.18181818)
10) Age< 55 12 0 absent (1.00000000 0.00000000) *
11) Age>=55 21 6 absent (0.71428571 0.28571429)
22) Age>=111 14 2 absent (0.85714286 0.14285714) *
23) Age< 111 7 3 present (0.42857143 0.57142857) *
3) Start< 8.5 19 8 present (0.42105263 0.57894737) *
```

Now, you can display an HTML version of the tree output by plotting the object produced by the *createTreeView* function. After running the preceding R code, run the following to load the *RevoTreeView* package and display an interactive decision tree in your browser:

```
library(RevoTreeView)
plot(createTreeView(kyphTree))
```

In this interactive tree, click on the circular split nodes to expand or collapse the tree branch. Clicking a node will expand and collapse the node to the last view of that branch. If you use a *CTRL + Click*, the tree displays only the children of the selected node. If you click *ALT + Click*, the tree displays all levels below the selected node. The square-shaped nodes, called leaf, or terminal nodes, cannot be expanded.

To get additional information, hover over the node to expose the node details such as its name, the next split variable, its value, the *n*, the predicted value, and other details such as loss or deviance.

You can also use the rpart *plot* and *text* methods with *rxDTree* objects, provided you use the *rxAddInheritance* function to provide rpart inheritance:

```
# Plotting Trees
plot(rxAddInheritance(airlineTreePruned))
text(rxAddInheritance(airlineTreePruned))
```

Provides the following plot: