0
set.seed(0)
## 2 response of 10 observations each
response <- matrix(rnorm(20), 10, 2)
## 3 covariates with 10 observations each
predictors <- matrix(rnorm(30), 10, 3)
fit <- lm(response ~ predictors)

I have been generating residual plots for the entire model using:

plot(fitted(fit),residuals(fit))

However, I would like to make individual plots for each predictor covariate. I can do them one at a time by:

f <- fitted(fit)
r <- residual(fit)
plot(f[,1],r[,1])

The issue with this approach however, is that it needs to be generalizable for data sets with more predictor covariates. Is there a way that I use plot while iterating through each column of (f) and (r)? Or is there a way that plot() can group each co-variate by colour?

Zheyuan Li
  • 71,365
  • 17
  • 180
  • 248
Martin James
  • 151
  • 2
  • 10
  • not really clear what your are trying to do. e.g. `rnorm(20)` did you actually mean `rnorm(1:20)`? what is in the `fit`? try `sammary(fit)` for your code and explain it. – Bulat Sep 18 '16 at 21:06
  • Thought I was pretty clear, and provided reproducible code. Fit is the linear model. – Martin James Sep 18 '16 at 21:11
  • Well, that reproducible code generates same values in all columns. Reproducible code that does not do what you think it is doing is not helpful. – Bulat Sep 18 '16 at 21:11
  • It doesn't matter though. The contents of the matrices is irrelevant to the question. – Martin James Sep 18 '16 at 21:13
  • what is the point of the code than? make a proper example using existing dataset, e.g. `data("iris")` , `head(iris)`. fitting matrix to matrix does not make much sense to me. – Bulat Sep 18 '16 at 21:17
  • What exactly is unclear about my question? I'm plotting fitted values for a model against their residuals. I'm asking how to programmatically do this for each column is a fitted() and residuals() summary. – Martin James Sep 18 '16 at 21:18
  • `response <- matrix(rnorm(1:20),20,200)` what is this? vector of independent values? should it it be just `rnorm(1:20)`? – Bulat Sep 18 '16 at 21:20
  • why do you think it's important in order to help answer my question? my question is about plot(), not about some philosophical discussion about rnorm() – Martin James Sep 18 '16 at 21:21
  • @forder You could check [these](http://stackoverflow.com/questions/tagged/lm) posts – akrun Sep 20 '16 at 06:16
  • 1
    Sure, you're more than welcome to comment or provide an answer. The question was downvoted because some people would prefer to be pedantic rather than helpful. I know, I don't get it either. It's a relatively straight forward question, and from what I understand (admittedly not a lot) is particularly important in assessing training error for linear models. – Martin James Sep 29 '16 at 04:06

2 Answers2

3

Make Sure you are using standardised residuals rather than raw residuals

I often see plot(fitted(fit), residuals(fit)) but it is statistically wrong. We use plot(fit) to generate diagnostic plot, because we need standardised residuals rather than raw ones. Read ?plot.lm for more. But plot method for "mlm" is poorly supported:

plot(fit)
# Error: 'plot.mlm' is not implemented yet

Define "rstandard" S3 method for "mlm"

plot.mlm is not supported for many reasons, one of which is the lack of rstandard.mlm. For "lm" and "glm" class, there is a generic S3 method "rstandard" to get standardised residuals:

methods(rstandard)
# [1] rstandard.glm* rstandard.lm*

There is no support for "mlm". So we shall fill this gap first.

It is not difficult to get standardised residuals. Let hii be diagonals of the hat matrix, the point-wise estimated standard error for residuals is sqrt(1 - hii) * sigma, where sigma = sqrt(RSS / df.residual) is estimated residual standard error. RSS is residual sum of squares; df.residual is residual degree of freedom.

hii can be computed from matrix factor Q of QR factorization of model matrix: rowSums(Q ^ 2). For "mlm", there is only one QR decomposition since the model matrix is the same for all responses, hence we only need to compute hii once.

Different response has different sigma, but they are elegantly colSums(residuals(fit) ^ 2) / df.residual(fit).

Now, let's wrap up those ideas to get our own "rstandard" method for "mlm":

## define our own "rstandard" method for "mlm" class
rstandard.mlm <- function (model) {
  Q <- with(model, qr.qy(qr, diag(1, nrow = nrow(qr$qr), ncol = qr$rank)))  ## Q matrix
  hii <- rowSums(Q ^ 2)  ## diagonal of hat matrix QQ'
  RSS <- colSums(model$residuals ^ 2)  ## residual sums of squares (for each model)
  sigma <- sqrt(RSS / model$df.residual)  ##  ## Pearson estimate of residuals (for each model)
  pointwise_sd <- outer(sqrt(1 - hii), sigma)  ## point-wise residual standard error (for each model)
  model$residuals / pointwise_sd  ## standardised residuals
  }

Note the use of .mlm in function name to tell R this is S3 method associated. Once we have defined this function, we can see it in "rstandard" method:

## now there are method for "mlm"
methods(rstandard)
# [1] rstandard.glm* rstandard.lm*  rstandard.mlm

To call this function, we don't have to explicitly call rstandard.mlm; calling rstandard is enough:

## test with your fitted model `fit`
rstandard(fit)
#          [,1]       [,2]
#1   1.56221865  2.6593505
#2  -0.98791320 -1.9344546
#3   0.06042529 -0.4858276
#4   0.18713629  2.9814135
#5   0.11277397  1.4336484
#6  -0.74289985 -2.4452868
#7   0.03690363  0.7015916
#8  -1.58940448 -1.2850961
#9   0.38504435  1.3907223
#10  1.34618139 -1.5900891

Standardised residuals are N(0, 1) distributed.


Getting residuals v.s. fitted plot for "mlm"

Your initial try with:

f <- fitted(fit); r <- rstandard(fit); plot(f, r)

is not a bad idea, provided that dots for different models can be identified from each other. So we can try using different point colours for different models:

plot(f, r, col = as.numeric(col(f)), pch = 19)

Graphical arguments like col, pch and cex can take vector input. I ask plot to use col = j for the r[,j] ~ f[,j], where j = 1, 2,..., ncol(f). Read "Color Specification" of ?par for what col = j means. pch = 19 tells plot to draw solid dots. Read basic graphcial parameters for various choices.

Finally you may want a legend. You can do

plot(f, r, col = as.numeric(col(f)), pch = 19, ylim = c(-3, 4))
legend("topleft", legend = paste0("response ", 1:ncol(f)), pch = 19,
       col = 1:ncol(f), text.col = 1:ncol(f))

In order to leave space for the legend box we extend ylim a little bit. As standardised residuals are N(0,1), ylim = c(-3, 3) is a good range. Should we want to place the legend box on the top left, we extend ylim to c(-3, 4). You can customize your legend even more via ncol, title, etc.

enter image description here


How many responses do you have?

If you have no more than a few responses, above suggestion works nicely. If you have plenty, plotting them in separate plot is suggested. A for loop as you found out is decent, except that you need split plotting region into different subplots, possibly using par(mfrow = c(?, ?)). Also set inner margin mar and outer margin oma if you take this approach. You may read How to produce a nicer plot for my categorical time series data in a matrix? for one example of doing this.

If you have even more responses, you might want a mixture of both? Say if you have 42 responses, you can do par(mfrow = c(2, 3)), then plot 7 responses in each subfigure. Now the solution is more opinion based.

Community
  • 1
  • 1
Zheyuan Li
  • 71,365
  • 17
  • 180
  • 248
-2

This is how I solved it.

for(i in 1:ncol(f)) {
    plot(f[,i],r[,i])
}

Mind blown.

ayhan
  • 70,170
  • 20
  • 182
  • 203
Martin James
  • 151
  • 2
  • 10
  • You are getting a plot for each response variable here. Again your code is not doing what you think it is, or your question was wrong. – Bulat Sep 19 '16 at 06:14
  • @forder I think this is a comment and not an answer. – akrun Sep 20 '16 at 06:19