---
title: "R6Nomogram Basics"
format: 
  html:
    echo: true
    embed-resources: true
vignette: >
  %\VignetteIndexEntry{Basics.qmd}
  %\VignetteEngine{quarto::html}
  %\VignetteEncoding{UTF-8}
---

## Nomograms

Nomograms are a graphical representation of a model.  For other than
the simplest of models, nomograms can better explain what the model is doing,
compared to just interpreting coefficients.  Each predictor variable (and 
any interactions) is represented by a horizontal scale.  

To use a nomogram to make predictions, you look up the value
of the predictor on the scale, then go straight up to the top scale (called 
points) to identify the number of points.  You do this for each of the 
predictors, then sum the points and look up that value on a scale at the 
bottom of the plot and use that to predict the response value.

With modern computers we no longer need nomograms to help with the calculations,
but they are helpful for opening the black box of the model.  Things like 
transformations, polynomials, or splines of a predictor variable can more 
easily be interpreted from the plot than from a table of coefficients.  Link
functions on the response can also be visualized from the plot.  Read on for
an example to see how these plots can help with interpretation and explanation
of models.


This vignette covers the basics and some of the options for creating 
nomograms using the R6Nomogram package.  The idea is to create an 
initial nomogram, then, since it is an R6 object, use the methods
to try different changes and improvements until you produce a good looking
nomogram (the first one will probably need improvement).

The automated step work well for models with a `predict` method that has a 
`terms` option, such as `lm` and `glm`.  Another vignette will cover what to
do if there is no predict method, or it does not predict terms.

## The Model

We first need a model to visualize.  We will use the `mtcars` data set that
comes with R and fit a model to predict `mpg` (miles per gallon).

A few of the potential predictors make more sense as categorical, rather than
numeric, predictors, so we will make a copy of the data frame and change the
variables to factors.

```{r}
#| label: model

mtcars2 <- mtcars
mtcars2$cyl <- factor(mtcars2$cyl)
mtcars2$gear <- factor(mtcars2$gear)
mtcars2$vs <- factor(mtcars2$vs, levels=0:1, labels=c("V", "S"))

fit <- glm(mpg ~ poly(wt, 2) + poly(disp,2) + cyl*gear + vs,
           data=mtcars2, family=gaussian(link="inverse"))
summary(fit)
```

I do not claim that this is the best model (or even a good model) for predicting
`mpg`, but it illustrates most of the features of `R6Nomogram`.

Think about trying to explain the model based on the above summary to a 
non-statistician.

Using `glm` with the `gaussian` family and the "inverse" link means that the 
model actually fits the reciprocal of `mpg`, gallons per mile instead of miles
per gallon.

## First Plot

Since `glm` models are pretty straight forward, the automated parts of `R6Nomogram`
will work directly, we just create a new object based on the model:

```{r}
#| label: nom1

library(R6Nomogram)

n1 <- R6Nomogram$new(fit)
```

The steps taken are printed out.  Since it finished we do not have to worry 
about anything.  If there had been something tricky, then we would have
seen which step the problem occurred on, rerun everything up to that step,
and then manually did the rest.

Now we can call the `plot` method to see the first iteration of our plot 
(warning: this will not be pretty, we will improve it).

```{r}
#| label: nom2

n1$plot()
```

This gives the basic plot, we can start to see some of the patterns, but there
are some labels plotted on top of each other and there are some big gaps.  We
can improve the plot.

## Total Points and Response

The default plot does not show the linear predictor.  Sometimes it is distracting,
but other times it can grant insight.  In our model the linear predictor is
the gallons per mile variable and can be interesting.  We will tell it to plot
the linear predictor and also change the label for it and the response.  We
need to set the field `v.pos.r` to `NULL` so that the correct positions are 
computed the next time we plot.

```{r}
#| label: response

n1$plot.lp <- TRUE
n1$v.pos.r <- NULL
n1$lp.lab <- "G/M"
n1$resp.lab <- "M/G"
n1$plot()
```

## Categorical Labels

Next we can see that there are several labels plotting on top of each other on
the line for the interaction.  This is because "4" is the baseline for `cyl` 
and "3" is the baseline for `gear`, so all the interactions including either 
of those all have the same value.  We will replace one of the values with 
"Other" and the remaining ones with "" so that they will be blank.

```{r}
#| label: interaction

dput(n1$x.pretty.vals$`cyl:gear`)
n1$x.pretty.vals$`cyl:gear` <-
   c("Other", "", "", "", "6:4", "", "6:5", "8:5")
n1$plot()
```

## Tick Values

The values where to put the ticks was determined by the `pretty` and related 
functions, but they did not take into account the non-linearity.  Some of the 
scales have large blank areas where it would make sense to put more tick marks
and labels.  Let's take care of that now.

```{r}
#| label: ticks1

n1$pretty.y(seq(60, 200, by=10))
n1$pretty('wt', c(1.5, 2, 3, 3.5, 4, 4.25, 4.5, 
                 4.75, 5, 5.25, 5.4))
n1$pretty('disp', c(75, 100, 125, 150, 175, 200, 250,
                   300, 400, 450))
n1$plot()
```

## Additional Options

There is still overlap on the gallons per mile line, lets set some options to 
make these look better and also move all of the labels further from the lines
and make the tick marks a little longer.

```{r}
#| label: options

n1$options$text.par[['linear predictor']] <- list(cex=0.7)
n1$options$tik.len <- 0.4
n1$options$txt.pos <- 1.2
n1$options$signif.digits <- 3
n1$plot()
```

Now it looks much better than when we started.

## Predictions

We can do one more thing, demonstrate the predictive idea by giving the `plot`
method a row from our data (or a new, matching data frame).

```{r}
#| label: predict

oldpar <- par(oma=c(0,0,1,0))
n1$plot(predict = mtcars2[1,])
mtext(rownames(mtcars2)[1], line=1)

n1$plot(predict = mtcars2[20,])
mtext(rownames(mtcars2)[20], line=1)

n1$plot(predict = mtcars2[15,])
mtext(rownames(mtcars2)[15], line=1)

par(oldpar)
```

## Tables

We can approximate the number of points from the graph and see a specific set of
values when predicting.  But, we may want to have tables with more values that
we can look up.  

Since we will probably want more dense values for the tables, but we do not want
to mess up our plot, we will first clone the object, then redo the "tick" locations, then use the `table` method to show the tables.  These tables can also be saved
as a list so that they can be copied to a database or spreadsheet to use in
other look-up tools.

```{r}
#| label: tables

n2 <- n1$clone()

n2$pretty.y(seq(60, 200, by=5))
n2$pretty('wt', seq(1.5, 5.4, by=0.1))
n2$pretty('disp', seq(75, 450, by=25))
n2$tables()
```


## Model Structure

The nomogram is dependent on how you fit the model.  Two different
models that make the exact same predictions can result in different
nomograms.  Changing some options in how you fit the model may make
the nomogram easier (or harder) to interpret.

For example, the above model used the default treatment contrasts
for `cyl` and `gear` with 4 cylinders and 3 gears as the baseline
values.  Therefore, the interactions involving 4 cylinders or 3
gears all had the same value (which we combined into other above).  
We can refit the model with a different set of contrasts and see how
that changes the plot.


```{r}
#| label: model2

contrasts(mtcars2$cyl) <- contr.sum(3)
contrasts(mtcars2$gear) <- contr.sum(3)

fit2 <- glm(mpg ~ poly(wt, 2) + poly(disp,2) + cyl*gear + vs,
           data=mtcars2, family=gaussian(link="inverse"))
n3 <- R6Nomogram$new(fit2)
n3$options$tik.len <- 0.4
n3$options$txt.pos <- 1.2
n3$options$signif.digits <- 3
n3$plot.lp <- TRUE
n3$lp.lab <- "G/M"
n3$resp.lab <- "M/G"
n3$options$text.par[['linear predictor']] <- list(cex=0.8)

n3$plot()
```

Since we have to look at the interaction line in the nomogram anyways,
we could further simplify the plot by fitting a combination of `cyl`
and `gear` instead of separate main effects and interaction.

```{r}
#| label: model3

mtcars2$`cyl:gear` <- interaction(mtcars2$cyl, mtcars2$gear,
                                 sep=':')
fit3 <- glm(mpg ~ poly(wt, 2) + poly(disp, 2) +
             `cyl:gear`,
           data=mtcars2, family=gaussian(link='inverse'))
n4 <- R6Nomogram$new(fit3, verbose=FALSE)
n4$options$tik.len <- 0.4
n4$options$txt.pos <- 1.2
n4$options$signif.digits <- 3
n4$plot.lp <- TRUE
n4$lp.lab <- "G/M"
n4$resp.lab <- "M/G"
n4$x.y.offsets$`cyl:gear` <- c(-1, 1, 2, -1, 1, -1, 1, -2)
n4$options$text.par[['linear predictor']] <- list(cex=0.8)
n4$options$text.par[['cyl:gear']] <- list(col=c(
  c("red", "forestgreen", "blue")[c(1,1,1,2,2,3,3,3)]
))

n4$plot()
```

