├── .github ├── .gitignore └── workflows │ └── pkgdown.yaml ├── vignettes ├── .gitignore └── pruning.Rmd ├── LICENSE ├── _pkgdown.yml ├── .gitignore ├── .Rbuildignore ├── NAMESPACE ├── R ├── annotreer-package.R ├── annotree.R └── utils.R ├── annotreer.Rproj ├── DESCRIPTION ├── man ├── annotreer-package.Rd ├── annotree.Rd └── figures │ ├── README-unnamed-chunk-13-1.svg │ ├── README-unnamed-chunk-10-4.svg │ ├── README-unnamed-chunk-12-1.svg │ ├── README-unnamed-chunk-10-3.svg │ └── README-unnamed-chunk-11-1.svg ├── LICENSE.md ├── README.Rmd └── README.md /.github/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | -------------------------------------------------------------------------------- /vignettes/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | *.R 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | YEAR: 2025 2 | COPYRIGHT HOLDER: annotreer authors 3 | -------------------------------------------------------------------------------- /_pkgdown.yml: -------------------------------------------------------------------------------- 1 | url: https://jtr13.github.io/annotreer/ 2 | template: 3 | bootstrap: 5 4 | 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | .Rhistory 3 | .Rdata 4 | .httr-oauth 5 | .DS_Store 6 | .quarto 7 | inst/doc 8 | docs 9 | -------------------------------------------------------------------------------- /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^annotreer\.Rproj$ 2 | ^\.Rproj\.user$ 3 | ^README\.Rmd$ 4 | ^LICENSE\.md$ 5 | ^_pkgdown\.yml$ 6 | ^docs$ 7 | ^pkgdown$ 8 | ^\.github$ 9 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | export(annotree) 4 | importFrom(graphics,mtext) 5 | importFrom(stats,na.omit) 6 | importFrom(stats,setNames) 7 | -------------------------------------------------------------------------------- /R/annotreer-package.R: -------------------------------------------------------------------------------- 1 | #' @keywords internal 2 | "_PACKAGE" 3 | 4 | ## usethis namespace: start 5 | #' @importFrom graphics mtext 6 | #' @importFrom stats na.omit 7 | #' @importFrom stats setNames 8 | ## usethis namespace: end 9 | NULL 10 | -------------------------------------------------------------------------------- /vignettes/pruning.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Cost Complexity Pruning" 3 | output: rmarkdown::html_vignette 4 | vignette: > 5 | %\VignetteIndexEntry{Cost Complexity Pruning} 6 | %\VignetteEngine{knitr::rmarkdown} 7 | %\VignetteEncoding{UTF-8} 8 | --- 9 | 10 | ```{r, include = FALSE} 11 | knitr::opts_chunk$set( 12 | collapse = TRUE, 13 | comment = "#>" 14 | ) 15 | ``` 16 | 17 | ```{r setup} 18 | library(annotreer) 19 | ``` 20 | -------------------------------------------------------------------------------- /annotreer.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: No 4 | SaveWorkspace: No 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: UTF-8 11 | 12 | RnwWeave: Sweave 13 | LaTeX: pdfLaTeX 14 | 15 | AutoAppendNewline: Yes 16 | StripTrailingWhitespace: Yes 17 | LineEndingConversion: Posix 18 | 19 | BuildType: Package 20 | PackageUseDevtools: Yes 21 | PackageInstallArgs: --no-multiarch --with-keep.source 22 | PackageRoxygenize: rd,collate,namespace 23 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: annotreer 2 | Title: What the Package Does (One Line, Title Case) 3 | Version: 0.0.0.9000 4 | Authors@R: 5 | person("First", "Last", , "first.last@example.com", role = c("aut", "cre")) 6 | Description: What the package does (one paragraph). 7 | License: MIT + file LICENSE 8 | Encoding: UTF-8 9 | Roxygen: list(markdown = TRUE) 10 | RoxygenNote: 7.3.2 11 | URL: https://github.com/jtr13/annotreer, https://jtr13.github.io/annotreer/ 12 | BugReports: https://github.com/jtr13/annotreer/issues 13 | Imports: 14 | graphics, 15 | rpart.plot, 16 | stats 17 | Suggests: 18 | knitr, 19 | rmarkdown, 20 | rpart 21 | VignetteBuilder: knitr 22 | -------------------------------------------------------------------------------- /man/annotreer-package.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/annotreer-package.R 3 | \docType{package} 4 | \name{annotreer-package} 5 | \alias{annotreer} 6 | \alias{annotreer-package} 7 | \title{annotreer: What the Package Does (One Line, Title Case)} 8 | \description{ 9 | What the package does (one paragraph). 10 | } 11 | \seealso{ 12 | Useful links: 13 | \itemize{ 14 | \item \url{https://github.com/jtr13/annotreer} 15 | \item Report bugs at \url{https://github.com/jtr13/annotreer/issues} 16 | } 17 | 18 | } 19 | \author{ 20 | \strong{Maintainer}: First Last \email{first.last@example.com} 21 | 22 | } 23 | \keyword{internal} 24 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright (c) 2025 annotreer authors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/pkgdown.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | on: 4 | push: 5 | branches: [main, master] 6 | pull_request: 7 | release: 8 | types: [published] 9 | workflow_dispatch: 10 | 11 | name: pkgdown.yaml 12 | 13 | permissions: read-all 14 | 15 | jobs: 16 | pkgdown: 17 | runs-on: ubuntu-latest 18 | # Only restrict concurrency for non-PR jobs 19 | concurrency: 20 | group: pkgdown-${{ github.event_name != 'pull_request' || github.run_id }} 21 | env: 22 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 23 | permissions: 24 | contents: write 25 | steps: 26 | - uses: actions/checkout@v4 27 | 28 | - uses: r-lib/actions/setup-pandoc@v2 29 | 30 | - uses: r-lib/actions/setup-r@v2 31 | with: 32 | use-public-rspm: true 33 | 34 | - uses: r-lib/actions/setup-r-dependencies@v2 35 | with: 36 | extra-packages: any::pkgdown, local::. 37 | needs: website 38 | 39 | - name: Build site 40 | run: pkgdown::build_site_github_pages(new_process = FALSE, install = FALSE) 41 | shell: Rscript {0} 42 | 43 | - name: Deploy to GitHub pages 🚀 44 | if: github.event_name != 'pull_request' 45 | uses: JamesIves/github-pages-deploy-action@v4.5.0 46 | with: 47 | clean: false 48 | branch: gh-pages 49 | folder: docs 50 | -------------------------------------------------------------------------------- /man/annotree.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/annotree.R 3 | \name{annotree} 4 | \alias{annotree} 5 | \title{Create Annotated Labels for rpart Trees} 6 | \usage{ 7 | annotree( 8 | mod, 9 | show_classes = TRUE, 10 | show_gt = FALSE, 11 | show_cp = FALSE, 12 | show_pruned = FALSE, 13 | show_min = FALSE, 14 | show_leaf_dev = FALSE, 15 | show_internal_dev = FALSE, 16 | main = NULL 17 | ) 18 | } 19 | \arguments{ 20 | \item{mod}{an \code{rpart} object} 21 | 22 | \item{show_classes}{logical. If \code{TRUE} show class labels and counts. Defaults to \code{TRUE}.} 23 | 24 | \item{show_gt}{logical. If \code{TRUE} the g(t) term (misclassification reduction per split) is included. Defaults to \code{FALSE}.} 25 | 26 | \item{show_cp}{logical. If \code{TRUE} the complexity parameter (cp) value, calculated as g(t) divided by root node deviance is included. Defaults to \code{FALSE}.} 27 | 28 | \item{show_pruned}{logical. If \code{TRUE} the nodes slated for pruning are plotted in a distinct color. Defaults to \code{FALSE}.} 29 | 30 | \item{show_min}{logical. If \code{TRUE} the node with minimum g(t) is labeled "*minimum". Defaults to \code{FALSE}.} 31 | 32 | \item{show_leaf_dev}{logical. If \code{TRUE} leaves are labeled with "dev" (misclassified cases). Defaults to \code{FALSE}.} 33 | 34 | \item{show_internal_dev}{logical. If \code{TRUE} internal nodes are labeled with "dev" (misclassified cases). Defaults to \code{FALSE}.} 35 | 36 | \item{main}{An optional title for the plot.} 37 | } 38 | \description{ 39 | Generates custom labels for each node in an \code{rpart} classification tree to aid 40 | in understanding CART's pruning algorithm 41 | } 42 | \details{ 43 | This function is primarily used to create informative labels for internal nodes and leaves in classification trees. 44 | \itemize{ 45 | \item For leaf nodes, it displays the word \code{"leaf"} and the node's deviance. 46 | \item For internal nodes, it can display the g(t) equation, cp value, and optionally indicate the node with the smallest g(t). 47 | } 48 | } 49 | \examples{ 50 | library(rpart) 51 | kmod <- rpart(Kyphosis ~ Age + Number + Start, data = kyphosis, cp = 0, minsplit = 2) 52 | annotree(kmod) 53 | 54 | } 55 | -------------------------------------------------------------------------------- /R/annotree.R: -------------------------------------------------------------------------------- 1 | #' Create Annotated Labels for rpart Trees 2 | #' 3 | #' Generates custom labels for each node in an `rpart` classification tree to aid 4 | #' in understanding CART's pruning algorithm 5 | #' 6 | #' @param mod an `rpart` object 7 | #' @param show_classes logical. If `TRUE` show class labels and counts. Defaults to `TRUE`. 8 | #' @param show_gt logical. If `TRUE` the g(t) term (misclassification reduction per split) is included. Defaults to `FALSE`. 9 | #' @param show_cp logical. If `TRUE` the complexity parameter (cp) value, calculated as g(t) divided by root node deviance is included. Defaults to `FALSE`. 10 | #' @param show_pruned logical. If `TRUE` the nodes slated for pruning are plotted in a distinct color. Defaults to `FALSE`. 11 | #' @param show_min logical. If `TRUE` the node with minimum g(t) is labeled "*minimum". Defaults to `FALSE`. 12 | #' @param show_leaf_dev logical. If `TRUE` leaves are labeled with "dev" (misclassified cases). Defaults to `FALSE`. 13 | #' @param show_internal_dev logical. If `TRUE` internal nodes are labeled with "dev" (misclassified cases). Defaults to `FALSE`. 14 | #' @param main An optional title for the plot. 15 | #' 16 | #' @details 17 | #' This function is primarily used to create informative labels for internal nodes and leaves in classification trees. 18 | #' - For leaf nodes, it displays the word `"leaf"` and the node's deviance. 19 | #' - For internal nodes, it can display the g(t) equation, cp value, and optionally indicate the node with the smallest g(t). 20 | #' 21 | #' @examples 22 | #' library(rpart) 23 | #' kmod <- rpart(Kyphosis ~ Age + Number + Start, data = kyphosis, cp = 0, minsplit = 2) 24 | #' annotree(kmod) 25 | #' 26 | #' @export 27 | 28 | 29 | annotree <- function(mod, show_classes = TRUE, 30 | show_gt = FALSE, show_cp = FALSE, show_pruned = FALSE, 31 | show_min = FALSE, show_all_dev = FALSE, 32 | show_leaf_dev = FALSE, 33 | show_internal_dev = FALSE, main = NULL) { 34 | if(show_classes & (mod$method != "class")) { 35 | message("`show_classes` only works with classification trees.") 36 | show_classes <- FALSE 37 | } 38 | if (show_all_dev) { 39 | show_leaf_dev <- TRUE 40 | show_internal_dev <- TRUE 41 | } 42 | 43 | mod$frame <- annotate_gt(mod) 44 | mod$frame$nn <- as.numeric(rownames(mod$frame)) 45 | mod$frame$label <- create_label(mod, show_classes = show_classes, 46 | show_gt = show_gt, show_cp = show_cp, 47 | show_leaf_dev = show_leaf_dev, 48 | show_internal_dev = show_internal_dev, 49 | show_min = show_min) 50 | mod$frame$colors <- gt_calc_colors(mod, show_pruned = show_pruned) 51 | rpart.plot::prp(mod, extra = 1, type = 2, 52 | node.fun = node.label, 53 | nn = TRUE, box.col = mod$frame$colors) 54 | if (!is.null(main)) mtext(main, side = 3, line = 3, font = 2, cex = 1.5) 55 | 56 | } 57 | 58 | 59 | -------------------------------------------------------------------------------- /R/utils.R: -------------------------------------------------------------------------------- 1 | # from rpart.plot 2 | get.class.stats <- function(x) 3 | { 4 | # columns of yval2 for e.g. a two-level response are: fitted n1 n2 prob1 prob2 5 | yval2 <- x$frame$yval2 6 | if(NCOL(yval2) < 5) 7 | stop("is.class.response(x) yet frame$yval2 is not ", 8 | "a matrix with five or more columns", call. = FALSE) 9 | fitted <- yval2[, 1] # fitted level as an integer 10 | if(NCOL(yval2) %% 2 == 0) { # new style yval2? 11 | stopifnot(colnames(yval2)[length(colnames(yval2))] == "nodeprob") 12 | nlev <- (ncol(yval2) - 2) / 2 13 | } else # old style yval2 14 | nlev <- (ncol(yval2) - 1) / 2 15 | stopifnot(nlev > 1) 16 | stopifnot(floor(nlev) == nlev) 17 | n.per.lev <- yval2[, 1 + (1:nlev), drop=FALSE] 18 | # aug 2012: commented out the following check because it 19 | # incorrectly fails when cases weights are used in the rpart model 20 | # stopifnot(sum(n.per.lev[1,]) == ntotal) # sanity check 21 | prob.per.lev <- yval2[, 1 + nlev + (1:nlev), drop=FALSE] 22 | # dec 2012: loosened following check to allow for numerical error 23 | stopifnot(abs(sum(prob.per.lev[1,]) - 1) < 1e-8) # sanity check 24 | list(yval2=yval2, 25 | fitted=fitted, 26 | nlev=nlev, 27 | # Aug 2019: ylevels is necessary for the multiclass model legend when 28 | # the last level in the response is unused in the training data and thus 29 | # does't appear in yval2 e.g. see "unusedlev" in the rpart.plot tests 30 | ylevels=attr(x, "ylevels"), 31 | n.per.lev=n.per.lev, 32 | prob.per.lev=prob.per.lev) 33 | } 34 | 35 | 36 | # labeling functions 37 | # 38 | # labels each node with class labels, class counts 39 | # and deviance (equal to smaller count) 40 | # default colors 41 | # 42 | 43 | node.fun.gt.calc <- function(x, labs, digits, varlen) x$frame$label 44 | 45 | node.label <- function(x, labs, digits, varlen) x$frame$label 46 | 47 | # annotate with g(t) (misclassification reduction / # of splits) 48 | # 49 | # Correctly find children using rpart's node numbering 50 | get_kids <- function(i, node_index) { 51 | this_node <- as.numeric(names(node_index)[i]) 52 | left <- as.character(this_node * 2) 53 | right <- as.character(this_node * 2 + 1) 54 | kids <- c() 55 | if (left %in% names(node_index)) kids <- c(kids, node_index[left]) 56 | if (right %in% names(node_index)) kids <- c(kids, node_index[right]) 57 | return(kids) 58 | } 59 | 60 | get_descendant_leaves <- function(frame, i, node_index) { 61 | out <- c() 62 | explore <- c(i) 63 | while (length(explore) > 0) { 64 | current <- explore[1] 65 | explore <- explore[-1] 66 | kids <- get_kids(current, node_index) 67 | if (length(kids) == 0) { 68 | out <- c(out, current) 69 | } else { 70 | explore <- c(explore, kids) 71 | } 72 | } 73 | return(out[frame$var[out] == ""]) 74 | } 75 | 76 | 77 | annotate_gt <- function(mod) { 78 | frame <- mod$frame 79 | is_leaf <- frame$var == "" 80 | 81 | dev <- frame$dev 82 | node_index <- setNames(seq_along(rownames(frame)), rownames(frame)) 83 | 84 | n <- nrow(frame) 85 | gt <- rep(NA, n) 86 | subdev <- rep(NA, n) 87 | numsplits <- rep(NA, n) 88 | for (i in seq_len(nrow(frame))) { 89 | if (!is_leaf[i]) { 90 | leaves <- get_descendant_leaves(frame, i, node_index) 91 | if (length(leaves) >= 2) { 92 | gt[i] <- (dev[i] - sum(dev[leaves])) / (length(leaves) - 1) 93 | subdev[i] <- sum(dev[leaves]) 94 | numsplits[i] <- length(leaves) 95 | } 96 | } 97 | } 98 | frame$gt <- gt 99 | frame$subdev <- subdev 100 | frame$numsplits <- numsplits 101 | return(frame) 102 | } 103 | 104 | get_descendants <- function(node, all_nodes) { 105 | # Recursive function to collect all descendants 106 | children <- c(2 * node, 2 * node + 1) 107 | descendants <- children[children %in% all_nodes] 108 | for (child in descendants) { 109 | descendants <- c(descendants, get_descendants(child, all_nodes)) 110 | } 111 | return(descendants) 112 | } 113 | 114 | 115 | gt_calc_colors <- function(mod, show_pruned = TRUE) { 116 | if (nrow(mod$frame) == 1) { 117 | return("#00A86B50") 118 | } else { 119 | mingt <- min(mod$frame$gt, na.rm = TRUE) 120 | mingt_nodes <- na.omit(mod$frame$nn[mod$frame$gt == mingt]) 121 | mingt_node_cp <- mod$frame$complexity[mod$frame$nn == mingt_nodes][1] 122 | to_be_pruned <- unique(unlist( 123 | lapply(mingt_nodes, get_descendants, all_nodes = mod$frame$nn) 124 | )) 125 | # c6e1ff is used in classification_pruning.qmd, be careful! 126 | colors <- rep("#00A86B50", nrow(mod$frame)) # default color 127 | colors[!is.na(mod$frame$gt)] <- "#c6e1ff" 128 | if (show_pruned) { 129 | colors[mod$frame$gt == mingt] <- "#c6c9ff" 130 | colors[mod$frame$nn %in% to_be_pruned] <- "grey90" 131 | } 132 | } 133 | colors 134 | } 135 | 136 | create_label <- function(mod, 137 | show_classes = TRUE, 138 | show_gt = TRUE, 139 | show_cp = TRUE, 140 | show_leaf_dev = TRUE, 141 | show_internal_dev = TRUE, 142 | show_min = TRUE) { 143 | 144 | # create leaf labels 145 | label <- "" 146 | 147 | if(show_classes) { 148 | class_stats <- get.class.stats(mod) 149 | label1 <- abbreviate(class_stats$ylevels[1], 6) 150 | label2 <- abbreviate(class_stats$ylevels[2], 6) 151 | label_class <- paste0(label1, strrep(" ", 13 - nchar(label1) - nchar(label2)), 152 | label2, "\n", class_stats$n.per.lev[,1], " ", 153 | class_stats$n.per.lev[,2], "\n") 154 | label <- paste0(label, label_class) 155 | } 156 | 157 | # deal with 1 node situation 158 | if (nrow(mod$frame) == 1) { 159 | return(paste(label, "dev: ", mod$frame$dev)) 160 | } 161 | 162 | if (show_leaf_dev) { 163 | label <- ifelse(mod$frame$var == "", 164 | paste0(label, "dev: ", round(mod$frame$dev), "\n"), 165 | label) 166 | } 167 | 168 | if (show_gt) { 169 | label_gt <- ifelse( 170 | mod$frame$var != "", 171 | paste0("g(t) = (", mod$frame$dev, "-", 172 | mod$frame$subdev, ") / \n (", 173 | mod$frame$numsplits, "-1) = ", 174 | round(mod$frame$gt, 2), "\n"), "") 175 | label <- paste0(label, label_gt) 176 | } 177 | 178 | if (show_cp) { 179 | label_cp <- ifelse(mod$frame$var != "", 180 | paste0("cp: ", round(mod$frame$gt/mod$frame$dev[1], 5), "\n"), "") 181 | label <- paste0(label, label_cp) 182 | } 183 | 184 | if (show_min) { 185 | mingt <- min(mod$frame$gt, na.rm = TRUE) 186 | label_showmin <- ifelse(!is.na(mod$frame$gt) & mod$frame$gt == mingt, "*minimum\n", "") 187 | label <- paste0(label, label_showmin) 188 | } 189 | 190 | if (show_internal_dev) { 191 | label_dev <- ifelse(mod$frame$var != "", 192 | paste0("dev: ", round(mod$frame$dev), "\n"), "") 193 | label <- paste0(label, label_dev) 194 | } 195 | sub("\\n+$", "", label) 196 | } 197 | 198 | -------------------------------------------------------------------------------- /README.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | output: github_document 3 | --- 4 | 5 | 6 | 7 | ```{r, include = FALSE} 8 | options(digits = 4) 9 | knitr::opts_chunk$set( 10 | eval = TRUE, 11 | collapse = TRUE, 12 | comment = "#>", 13 | dev = "svg", 14 | fig.width = 10, 15 | fig.height = 8, 16 | fig.retina = 2, 17 | fig.align = "center", 18 | fig.path = "man/figures/README-", 19 | out.width = "70%" 20 | ) 21 | ``` 22 | 23 | # annotreer 24 | 25 | 26 | 27 | 28 | This package provides one function, `annotree()`, which is a wrapper for `rpart.plot::prp()`. By employing customized labeling functions under the hood, `annotree()` helps us visualize complexity parameter calculations. These annotations are helpful for explaining CART's pruning algorithm. 29 | 30 | ## Installation 31 | 32 | Install with: 33 | 34 | ``` r 35 | remotes::install_github("jtr13/annotreer") 36 | ``` 37 | 38 | ## Example 39 | 40 | Did you ever create a classification tree with **rpart**, view the `cptable` returned and wonder how the columns were calculated? This package creates custom label functions and then calls `rpart.plot::prp()` to create tree diagrams that will aid in understanding how this table summarizes the pruning options. Let's start with an example. 41 | 42 | We create a large tree with `rpart::rpart()` and visualize it with `annotree()`. The data is from the **rpart** package: 43 | 44 | ```{r} 45 | library(rpart) 46 | head(kyphosis) 47 | ``` 48 | 49 | `Kyphosis` -- type of deformation `absent` or `present` after surgery 50 | 51 | `Age` -- in months 52 | 53 | `Number` -- the number of vertebrae involved 54 | 55 | `Start` -- the number of the first (topmost) vertebra operated on 56 | 57 | The goal is to classify the `Kyphosis` variable based on `Age`, `Number` and `Start`. By setting the parameter `show_classes = TRUE` in the call to `annotree()` below, the nodes are labeled with class labels and class counts. 58 | 59 | ```{r example, fig.width=10, fig.retina=2} 60 | kmod <- rpart(Kyphosis ~ Age + Number + Start, data = kyphosis, cp = 0, minsplit = 2) 61 | library(annotreer) 62 | annotree(kmod, show_classes = TRUE) 63 | ``` 64 | 65 | Since we set the parameters of `rpart()` to grow as large a tree as possible, the deviance for all of the leaf nodes is zero: every leaf node has a zero for one of the classes. We can make the deviance of the leaves more explict by setting `show_leaf_dev` to `TRUE`: 66 | 67 | ```{r} 68 | annotree(kmod, show_classes = TRUE, show_leaf_dev = TRUE) 69 | ``` 70 | 71 | Now we turn to the pruning process. `rpart()` returns a `cptable` which provides pruning options. Following the cost complexity pruning algorithm of CART (see Breiman, et al. 1984) the table does not show every possible subtree, only the ones produced by sequentially pruning the next weakest link until the tree is reduced to a single node. 72 | 73 | In this particular case, we are given 6 options shown in the `nsplit` column: 0, 1, 2, 5, 10, and 16 splits. Each of the larger subtrees contains the smaller ones, as we'll see when we get into the details of the pruning process. 74 | 75 | ```{r} 76 | kmod$cptable 77 | ``` 78 | 79 | The `CP` column shows the minimum complexity parameter for a tree of the corresponding number of splits. For example if we set the `cp` value to a number below `0.07843` and above `0.05882`, when pruning, the subtree will have 5 splits: 80 | 81 | ```{r, fig.height=6, out.width="60%"} 82 | annotree(prune(kmod, cp = .06), main = "Pruned with cp = .06") 83 | ``` 84 | 85 | ## How the cptable is created 86 | 87 | To understand how `rpart()` is created we'll start with some definitions: 88 | 89 | * A *branch* includes an internal node and all of its descendants. 90 | 91 | * *Pruning* a branch refers to removing all of the descendants of a node, i.e. collapsing them into the root node of the branch. 92 | 93 | * A *subtree* refers to the original tree after pruning. 94 | 95 | The algorithm for pruning a classification tree begins with calculating the deviance reduction *per split* for all internal nodes on the large tree. In this context, deviance means misclassified cases, which differs from the tree growing process in which the Gini index, not the number misclassified, is used to determine the next optimal split. 96 | 97 | The `CP` values in the table are a scaled version of $g(t)$, the cost complexity value, which is a direct measure of the misclassification reduction *per split*. For example, if it takes two splits to reduce the number of misclassified by one, then $g(t)$ would equal `0.5`. We'll work in terms of $g(t)$ since it's more intuitive, and return to `CP` later on. 98 | 99 | More formally, for all internal nodes of the tree, 100 | 101 | $g(t) = \frac{R(t) - R(T_t)}{|\tilde{T}_t| - 1},$ where 102 | 103 | * $t$ is a node in the decision tree, 104 | 105 | * $T_t$ is the subtree rooted at node $t$, including $t$ and all of its descendants, 106 | 107 | * $\tilde{T}_t$ is the set of terminal (leaf) nodes in the subtree $T_t$, 108 | 109 | * $R(t)$ is the risk at node $t$, in this case, the deviance or misclassified cases, 110 | 111 | * $R(T_t)$ is the total risk of the subtree $T_t$, meaning the sum of risks at all terminal nodes in $T_t$, and 112 | 113 | * $|\tilde{T}_t|$ is the number of terminal nodes (leaves) in the subtree $T_t$,. 114 | 115 | Less formally it's the misclassification reduction *per split*, or, 116 | 117 | $g(t)$ = (the difference between the deviance of node $t$ and the sum of the deviance of node $t$'s leaf descendents) / (number of splits in branch $t$) 118 | 119 | For example, let's consider the branch with root node 22 in the original large tree. We have: 120 | 121 | * deviance of branch: 0 (sum of misclassified in leaf nodes) 122 | 123 | * deviance of node: 2 (misclassified in node) 124 | 125 | * number of splits in branch: 4 (nodes 22, 44, 89, 45) 126 | 127 | Thus $g(t) = \frac{2 - 0}{4} = 0.5$ 128 | 129 | That is, it takes 4 splits to reduce the deviance from 2 to 0, which averages to a 0.5 reduction per split. 130 | 131 | The tree below displays $g(t)$ for all branches: 132 | 133 | ```{r} 134 | annotree(kmod, show_classes = FALSE, show_gt = TRUE, 135 | show_leaf_dev = TRUE, show_internal_dev = TRUE) 136 | ``` 137 | 138 | By setting `show_min` to `TRUE` we can add "*minimum" to the labels for node(s) with the minimum value for $g(t)$. Adding `show_pruned` changes the color of these nodes and their descendents: 139 | 140 | ```{r} 141 | annotree(kmod, show_classes = FALSE, show_gt = TRUE, 142 | show_leaf_dev = TRUE, show_internal_dev = TRUE, 143 | show_min = TRUE, show_pruned = TRUE) 144 | ``` 145 | 146 | The weakest links have $g(t) = 0.5$. 147 | 148 | In **rpart** these are scaled so that the root node has an error of 1, accomplished by dividing all values of `g(t)` by the deviance of the root node. In this case it is `17`, so the scaled complexity parameter (henceforth, cp) is `0.5/17 = 0.0294`. We will redraw the tree with the scaled values by changing `show_gt = TRUE` to `show_cp = TRUE`: 149 | 150 | ```{r} 151 | annotree(kmod, show_classes = FALSE, show_cp = TRUE, 152 | show_leaf_dev = TRUE, show_internal_dev = TRUE, 153 | show_min = TRUE, show_pruned = TRUE) 154 | ``` 155 | 156 | To save space, henceforth we will only show the scaled values. 157 | 158 | To determine the next threshold we need to recalculate $g(t)$ or `cp`. This is necessary because pruning the tree changes the cost reductions for the remaining nodes. We will set `cp` to a value slightly above the minimum; we choose `.03`. 159 | 160 | ```{r, fig.height=7} 161 | kmod2 <- prune(kmod, cp = .03) 162 | annotree(kmod2, show_classes = FALSE, show_cp = TRUE, 163 | show_leaf_dev = TRUE, show_internal_dev = TRUE, 164 | show_min = TRUE, show_pruned = TRUE) 165 | ``` 166 | 167 | We continue in this way until the tree is reduced to a single node: 168 | 169 | ```{r, fig.height=5} 170 | kmod3 <- prune(kmod2, cp = .06) 171 | annotree(kmod3, show_classes = FALSE, show_cp = TRUE, 172 | show_leaf_dev = TRUE, show_internal_dev = TRUE, 173 | show_min = TRUE, show_pruned = TRUE) 174 | ``` 175 | 176 | 177 | ```{r, fig.height=4} 178 | kmod4 <- prune(kmod3, cp = .08) 179 | annotree(kmod4, show_classes = FALSE, show_cp = TRUE, 180 | show_leaf_dev = TRUE, show_internal_dev = TRUE, 181 | show_min = TRUE, show_pruned = TRUE) 182 | ``` 183 | 184 | 185 | ```{r, fig.height=4} 186 | kmod5 <- prune(kmod4, cp = .12) 187 | annotree(kmod5, show_classes = FALSE, show_cp = TRUE, 188 | show_leaf_dev = TRUE, show_internal_dev = TRUE, 189 | show_min = TRUE, show_pruned = TRUE) 190 | ``` 191 | 192 | 193 | ```{r, fig.height=4} 194 | kmod6 <- prune(kmod5, cp = .18) 195 | annotree(kmod6, show_classes = TRUE, show_cp = TRUE, 196 | show_leaf_dev = TRUE, show_internal_dev = TRUE, 197 | show_min = TRUE, show_pruned = TRUE) 198 | ``` 199 | 200 | Note that the weakest links in the trees above correspond to the values in `cptable`. We have replicated the process by which the original tree is successively pruned at the new weakest link: 201 | 202 | ```{r} 203 | kmod$cptable 204 | ``` 205 | 206 | Again, the table shows the minimum `CP` value for a particular number of splits. (Due to rounding in the display, the actual number may be slightly higher.) 207 | 208 | The `rel error` displays the sum of the misclassified cases for a tree of the size indicated *divided by* the misclassified cases in the root node (`nsplit` = 0) to ensure that the `rel error` of a tree with no splits is one. 209 | 210 | In this case we have: 211 | 212 | ```{r} 213 | data.frame(nsplit = kmod$cptable[, "nsplit"], 214 | misclassified = 17*kmod$cptable[, "rel error"]) 215 | ``` 216 | 217 | which corresponds to the misclassified cases in the trees shown above. 218 | 219 | ## Choosing the `cp` value 220 | 221 | The `xerror` and `xstd` refer to 222 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | # annotreer 5 | 6 | 7 | 8 | 9 | 10 | This package provides one function, `annotree()`, which is a wrapper for 11 | `rpart.plot::prp()`. By employing customized labeling functions under 12 | the hood, `annotree()` helps us visualize complexity parameter 13 | calculations. These annotations are helpful for explaining CART’s 14 | pruning algorithm. 15 | 16 | ## Installation 17 | 18 | Install with: 19 | 20 | ``` r 21 | remotes::install_github("jtr13/annotreer") 22 | ``` 23 | 24 | ## Example 25 | 26 | Did you ever create a classification tree with **rpart**, view the 27 | `cptable` returned and wonder how the columns were calculated? This 28 | package creates custom label functions and then calls 29 | `rpart.plot::prp()` to create tree diagrams that will aid in 30 | understanding how this table summarizes the pruning options. Let’s start 31 | with an example. 32 | 33 | We create a large tree with `rpart::rpart()` and visualize it with 34 | `annotree()`. The data is from the **rpart** package: 35 | 36 | ``` r 37 | library(rpart) 38 | head(kyphosis) 39 | #> Kyphosis Age Number Start 40 | #> 1 absent 71 3 5 41 | #> 2 absent 158 3 14 42 | #> 3 present 128 4 5 43 | #> 4 absent 2 5 1 44 | #> 5 absent 1 4 15 45 | #> 6 absent 1 2 16 46 | ``` 47 | 48 | `Kyphosis` – type of deformation `absent` or `present` after surgery 49 | 50 | `Age` – in months 51 | 52 | `Number` – the number of vertebrae involved 53 | 54 | `Start` – the number of the first (topmost) vertebra operated on 55 | 56 | The goal is to classify the `Kyphosis` variable based on `Age`, `Number` 57 | and `Start`. By setting the parameter `show_classes = TRUE` in the call 58 | to `annotree()` below, the nodes are labeled with class labels and class 59 | counts. 60 | 61 | ``` r 62 | kmod <- rpart(Kyphosis ~ Age + Number + Start, data = kyphosis, cp = 0, minsplit = 2) 63 | library(annotreer) 64 | annotree(kmod, show_classes = TRUE) 65 | ``` 66 | 67 | 68 | 69 | Since we set the parameters of `rpart()` to grow as large a tree as 70 | possible, the deviance for all of the leaf nodes is zero: every leaf 71 | node has a zero for one of the classes. We can make the deviance of the 72 | leaves more explict by setting `show_leaf_dev` to `TRUE`: 73 | 74 | ``` r 75 | annotree(kmod, show_classes = TRUE, show_leaf_dev = TRUE) 76 | ``` 77 | 78 | 79 | 80 | Now we turn to the pruning process. `rpart()` returns a `cptable` which 81 | provides pruning options. Following the cost complexity pruning 82 | algorithm of CART (see Breiman, et al. 1984) the table does not show 83 | every possible subtree, only the ones produced by sequentially pruning 84 | the next weakest link until the tree is reduced to a single node. 85 | 86 | In this particular case, we are given 6 options shown in the `nsplit` 87 | column: 0, 1, 2, 5, 10, and 16 splits. Each of the larger subtrees 88 | contains the smaller ones, as we’ll see when we get into the details of 89 | the pruning process. 90 | 91 | ``` r 92 | kmod$cptable 93 | #> CP nsplit rel error xerror xstd 94 | #> 1 0.17647 0 1.0000 1.000 0.2156 95 | #> 2 0.11765 1 0.8235 1.118 0.2243 96 | #> 3 0.07843 2 0.7059 1.118 0.2243 97 | #> 4 0.05882 5 0.4706 1.118 0.2243 98 | #> 5 0.02941 10 0.1765 1.118 0.2243 99 | #> 6 0.00000 16 0.0000 1.118 0.2243 100 | ``` 101 | 102 | The `CP` column shows the minimum complexity parameter for a tree of the 103 | corresponding number of splits. For example if we set the `cp` value to 104 | a number below `0.07843` and above `0.05882`, when pruning, the subtree 105 | will have 5 splits: 106 | 107 | ``` r 108 | annotree(prune(kmod, cp = .06), main = "Pruned with cp = .06") 109 | ``` 110 | 111 | 112 | 113 | ## How the cptable is created 114 | 115 | To understand how `rpart()` is created we’ll start with some 116 | definitions: 117 | 118 | - A *branch* includes an internal node and all of its descendants. 119 | 120 | - *Pruning* a branch refers to removing all of the descendants of a 121 | node, i.e. collapsing them into the root node of the branch. 122 | 123 | - A *subtree* refers to the original tree after pruning. 124 | 125 | The algorithm for pruning a classification tree begins with calculating 126 | the deviance reduction *per split* for all internal nodes on the large 127 | tree. In this context, deviance means misclassified cases, which differs 128 | from the tree growing process in which the Gini index, not the number 129 | misclassified, is used to determine the next optimal split. 130 | 131 | The `CP` values in the table are a scaled version of $g(t)$, the cost 132 | complexity value, which is a direct measure of the misclassification 133 | reduction *per split*. For example, if it takes two splits to reduce the 134 | number of misclassified by one, then $g(t)$ would equal `0.5`. We’ll 135 | work in terms of $g(t)$ since it’s more intuitive, and return to `CP` 136 | later on. 137 | 138 | More formally, for all internal nodes of the tree, 139 | 140 | $g(t) = \frac{R(t) - R(T_t)}{|\tilde{T}_t| - 1},$ where 141 | 142 | - $t$ is a node in the decision tree, 143 | 144 | - $T_t$ is the subtree rooted at node $t$, including $t$ and all of its 145 | descendants, 146 | 147 | - $\tilde{T}_t$ is the set of terminal (leaf) nodes in the subtree 148 | $T_t$, 149 | 150 | - $R(t)$ is the risk at node $t$, in this case, the deviance or 151 | misclassified cases, 152 | 153 | - $R(T_t)$ is the total risk of the subtree $T_t$, meaning the sum of 154 | risks at all terminal nodes in $T_t$, and 155 | 156 | - $|\tilde{T}_t|$ is the number of terminal nodes (leaves) in the 157 | subtree $T_t$,. 158 | 159 | Less formally it’s the misclassification reduction *per split*, or, 160 | 161 | $g(t)$ = (the difference between the deviance of node $t$ and the sum of 162 | the deviance of node $t$’s leaf descendents) / (number of splits in 163 | branch $t$) 164 | 165 | For example, let’s consider the branch with root node 22 in the original 166 | large tree. We have: 167 | 168 | - deviance of branch: 0 (sum of misclassified in leaf nodes) 169 | 170 | - deviance of node: 2 (misclassified in node) 171 | 172 | - number of splits in branch: 4 (nodes 22, 44, 89, 45) 173 | 174 | Thus $g(t) = \frac{2 - 0}{4} = 0.5$ 175 | 176 | That is, it takes 4 splits to reduce the deviance from 2 to 0, which 177 | averages to a 0.5 reduction per split. 178 | 179 | The tree below displays $g(t)$ for all branches: 180 | 181 | ``` r 182 | annotree(kmod, show_classes = FALSE, show_gt = TRUE, 183 | show_leaf_dev = TRUE, show_internal_dev = TRUE) 184 | ``` 185 | 186 | 187 | 188 | By setting `show_min` to `TRUE` we can add “\*minimum” to the labels for 189 | node(s) with the minimum value for $g(t)$. Adding `show_pruned` changes 190 | the color of these nodes and their descendents: 191 | 192 | ``` r 193 | annotree(kmod, show_classes = FALSE, show_gt = TRUE, 194 | show_leaf_dev = TRUE, show_internal_dev = TRUE, 195 | show_min = TRUE, show_pruned = TRUE) 196 | ``` 197 | 198 | 199 | 200 | The weakest links have $g(t) = 0.5$. 201 | 202 | In **rpart** these are scaled so that the root node has an error of 1, 203 | accomplished by dividing all values of `g(t)` by the deviance of the 204 | root node. In this case it is `17`, so the scaled complexity parameter 205 | (henceforth, cp) is `0.5/17 = 0.0294`. We will redraw the tree with the 206 | scaled values by changing `show_gt = TRUE` to `show_cp = TRUE`: 207 | 208 | ``` r 209 | annotree(kmod, show_classes = FALSE, show_cp = TRUE, 210 | show_leaf_dev = TRUE, show_internal_dev = TRUE, 211 | show_min = TRUE, show_pruned = TRUE) 212 | ``` 213 | 214 | 215 | 216 | To save space, henceforth we will only show the scaled values. 217 | 218 | To determine the next threshold we need to recalculate $g(t)$ or `cp`. 219 | This is necessary because pruning the tree changes the cost reductions 220 | for the remaining nodes. We will set `cp` to a value slightly above the 221 | minimum; we choose `.03`. 222 | 223 | ``` r 224 | kmod2 <- prune(kmod, cp = .03) 225 | annotree(kmod2, show_classes = FALSE, show_cp = TRUE, 226 | show_leaf_dev = TRUE, show_internal_dev = TRUE, 227 | show_min = TRUE, show_pruned = TRUE) 228 | ``` 229 | 230 | 231 | 232 | We continue in this way until the tree is reduced to a single node: 233 | 234 | ``` r 235 | kmod3 <- prune(kmod2, cp = .06) 236 | annotree(kmod3, show_classes = FALSE, show_cp = TRUE, 237 | show_leaf_dev = TRUE, show_internal_dev = TRUE, 238 | show_min = TRUE, show_pruned = TRUE) 239 | ``` 240 | 241 | 242 | 243 | ``` r 244 | kmod4 <- prune(kmod3, cp = .08) 245 | annotree(kmod4, show_classes = FALSE, show_cp = TRUE, 246 | show_leaf_dev = TRUE, show_internal_dev = TRUE, 247 | show_min = TRUE, show_pruned = TRUE) 248 | ``` 249 | 250 | 251 | 252 | ``` r 253 | kmod5 <- prune(kmod4, cp = .12) 254 | annotree(kmod5, show_classes = FALSE, show_cp = TRUE, 255 | show_leaf_dev = TRUE, show_internal_dev = TRUE, 256 | show_min = TRUE, show_pruned = TRUE) 257 | ``` 258 | 259 | 260 | 261 | ``` r 262 | kmod6 <- prune(kmod5, cp = .18) 263 | annotree(kmod6, show_classes = TRUE, show_cp = TRUE, 264 | show_leaf_dev = TRUE, show_internal_dev = TRUE, 265 | show_min = TRUE, show_pruned = TRUE) 266 | ``` 267 | 268 | 269 | 270 | Note that the weakest links in the trees above correspond to the values 271 | in `cptable`. We have replicated the process by which the original tree 272 | is successively pruned at the new weakest link: 273 | 274 | ``` r 275 | kmod$cptable 276 | #> CP nsplit rel error xerror xstd 277 | #> 1 0.17647 0 1.0000 1.000 0.2156 278 | #> 2 0.11765 1 0.8235 1.118 0.2243 279 | #> 3 0.07843 2 0.7059 1.118 0.2243 280 | #> 4 0.05882 5 0.4706 1.118 0.2243 281 | #> 5 0.02941 10 0.1765 1.118 0.2243 282 | #> 6 0.00000 16 0.0000 1.118 0.2243 283 | ``` 284 | 285 | Again, the table shows the minimum `CP` value for a particular number of 286 | splits. (Due to rounding in the display, the actual number may be 287 | slightly higher.) 288 | 289 | The `rel error` displays the sum of the misclassified cases for a tree 290 | of the size indicated *divided by* the misclassified cases in the root 291 | node (`nsplit` = 0) to ensure that the `rel error` of a tree with no 292 | splits is one. 293 | 294 | In this case we have: 295 | 296 | ``` r 297 | data.frame(nsplit = kmod$cptable[, "nsplit"], 298 | misclassified = 17*kmod$cptable[, "rel error"]) 299 | #> nsplit misclassified 300 | #> 1 0 17 301 | #> 2 1 14 302 | #> 3 2 12 303 | #> 4 5 8 304 | #> 5 10 3 305 | #> 6 16 0 306 | ``` 307 | 308 | which corresponds to the misclassified cases in the trees shown above. 309 | 310 | ## Choosing the `cp` value 311 | 312 | The `xerror` and `xstd` refer to 313 | -------------------------------------------------------------------------------- /man/figures/README-unnamed-chunk-13-1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /man/figures/README-unnamed-chunk-10-4.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /man/figures/README-unnamed-chunk-12-1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | -------------------------------------------------------------------------------- /man/figures/README-unnamed-chunk-10-3.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | -------------------------------------------------------------------------------- /man/figures/README-unnamed-chunk-11-1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | --------------------------------------------------------------------------------