-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathplot.candisc.R
More file actions
217 lines (193 loc) · 7.71 KB
/
plot.candisc.R
File metadata and controls
217 lines (193 loc) · 7.71 KB
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
## plot method for a candisc object
# last revised: 10/29/2007 10:32:52 AM by MF
# --- Added code to draw boxplots and variable vectors when ndim==1
# --- Fixed bug when then term is an interaction
# last revised: 11/6/2008 by MF
# --- added suffix argument
# last revised: 12/16/2008 by JF
# --- fixed plotting of variable names in 1D case
# last revised: 12/18/2008 by MF
# --- better allocation of horizontal space in 1D case
# last revised: 2/11/2010 by MF
# --- allow 1D plots for ndim>1 by specifying which
# last revised: 6/11/2013 8:39:51 AM
# --- fixed bug in use of pch and col (thx: dcarlson@tamu.edu)
# --- now use vectors() to draw variable vectors
# --- added var.labels, var.cex
# --- added rev.axes arg
# --- added var.pos, same as in heplot.candisc
# last revised: 10/03/2017
# --- fixed bug in use of pch and col (thx: dcarlson@tamu.edu)
# last revised: 05/06/20
# --- add points.1d option
# revised: 08/29/2025
# --- use `col` to label the group ellipses
#' @describeIn candisc `"plot"` method.
#' @export
plot.candisc <- function (
x, # output object from candisc
which=1:2, # canonical dimensions to plot
conf=0.95, # confidence coverage of circles for class means
col, # vector of unique colors used for plotting the canonical scores
pch, # vector of unique point symbols
# pt.cex = par("lwd"), # size of point symbols
scale, # scale factor for variable vectors in can space
asp=1, # aspect ratio, to ensure equal units
var.col="blue",
var.lwd=par("lwd"),
var.labels,
var.cex = 1,
var.pos,
rev.axes=c(FALSE, FALSE),
ellipse=FALSE, # draw data ellipses for canonical scores?
ellipse.prob = 0.68,
fill.alpha=0.1,
prefix = "Can", # prefix for labels of canonical dimensions
suffix = TRUE, # add label suffix with can % ?
titles.1d = c("Canonical scores", "Structure"),
points.1d = FALSE,
... # extra args passed to plot, e.g., notch = TRUE for boxplot
) {
term <- x$term
factors <- x$factors
rev.axes <- rep(rev.axes, length.out=2)
nlev <- if(length(dim(x$means))>1) nrow(x$means) else length(x$means) # number of groups
if (missing(col)) col <- rep(palette()[-1], length.out=nlev)
fill.col <- heplots::trans.colors(col, fill.alpha)
if (missing(pch)) pch <- rep(1:18, length.out=nlev)
# ----------- 1D plot -------------
if (x$ndim < 2 || length(which)==1) {
which <- which[1]
### is there a better way to show the 1D distributions of canonical scores?
### Show the canonical structure coeffs -- vectors from 0
op <- par(no.readonly = TRUE) # save default, for resetting...
ng <- length(x$means)
structure <- as.vector(x$structure[,which])
canvar <- paste('Can', which, sep="") # names of canonical variable to plot
scores <- x$scores[, canvar]
if(isTRUE(rev.axes[1])) {
x$scores[, canvar] <- -x$scores[, canvar]
structure <- -structure
}
ns <- length(structure)
wid <- if (ns < 2*ng) c(2,1) else c(1.2,1)
#cat("ng:", ng, "\tns:", ns, "\twid:", wid, "\n")
layout(matrix(c(1,2),1,2), widths=wid)
par(mar=c(5,4,4,0)+.1)
if (is.logical(suffix) & suffix)
suffix <- paste( " (", round(x$pct[which],1), "%)", sep="" ) else suffix <- NULL
canlab <- paste(prefix, which, suffix, sep="")
# scores <- x$scores[,canvar]
formule <- formula( paste0(canvar, " ~", term) )
boxplot(formule, data=x$scores, ylab=canlab, xlab=term, col=fill.col, main=titles.1d[1], ...)
# add points?
if (points.1d) points(jitter(as.numeric(x$scores[, term]), factor=0.5), x$scores[,canvar],
col=col[x$scores[, term]],
pch=pch[x$scores[, term]] )
xx <- 1:ns
par(mar=c(5,0,4,1)+.1)
# TODO: should set ylim = c(0,1) or c(-1,1) and maybe draw tick marks
ylim <- range( c(structure,
if (all(structure > 0)) +1 else 0,
if (all(structure < 0)) -1 else 0)
)
xlim <- c(0.5, ns+0.5)
plot(xx, structure, type="n", ylab="", xlab="",
xaxt="n", yaxt="n", xlim=xlim, ylim=ylim, main=titles.1d[2])
arrows(xx, 0, xx, structure, length=.1, angle=15,
col=var.col, lwd=var.lwd )
abline(h=0, lty=2, col="gray")
vars <- if(missing(var.labels)) rownames(x$structure) else var.labels
adj1 <- as.vector(ifelse (structure > 0, 1, 0))
adj2 <- rep(-0.3, ns)
adj2[1] <- 1.1
for (i in 1:ns) text(xx[i], structure[i], paste(" ", vars[i] ," "),
adj=c(adj1[i], adj2[i]), col=var.col, srt=90, cex=var.cex, xpd=TRUE)
par(op)
return(invisible())
}
# ----------- 2D plot -------------
canvar <- paste('Can', which, sep="") # names of canonical variables to plot
if (is.logical(suffix) & suffix)
suffix <- paste( " (", round(x$pct[which],1), "%)", sep="" ) else suffix <- NULL
canlab <- paste(prefix, which, suffix, sep="")
nlev <- nrow(x$means) # number of groups
# TODO: can we be more clever about assigning default col & pch by taking the
# structure of x$factors into account?
if (missing(col)) col <- rep(palette(), length.out=nlev)
scores <- x$scores[,canvar]
means <- x$means[,which]
labels <- rownames(x$means)
structure <- x$structure[,which]
if(isTRUE(rev.axes[1])) {
scores[, 1] <- -scores[, 1]
means[, 1] <- -means[, 1]
structure[, 1] <- -structure[, 1]
}
if(isTRUE(rev.axes[2])) {
scores[, 2] <- -scores[, 2]
means[, 2] <- -means[, 2]
structure[, 2] <- -structure[, 2]
}
# use asp=1 to make the plot equally scaled
Ind <- if(length(factors)==1)
match(x$scores[,term], levels(x$scores[,term]))
else dataIndex(x$scores, term)
plot(scores, asp=asp,
xlab=canlab[1], ylab=canlab[2],
col=col[Ind], pch=pch[Ind], ...)
abline(h=0, v=0,
lty=2, col="grey")
if(ellipse) {
fill.col <- heplots::trans.colors(col, fill.alpha)
radius <- sqrt(qchisq(ellipse.prob, df = 2))
angles <- (0:60)*2*pi/60
circle <- radius * cbind( cos(angles), sin(angles))
for(i in 1:nlev) {
sigma <- var(scores[Ind==i,])
mu <- as.numeric(means[i,])
Q <- chol(sigma, pivot = TRUE)
order <- order(attr(Q, "pivot"))
ell <- sweep(circle %*% Q[, order], 2,
mu, FUN = "+")
# ell <- t( mu + t(circle %*% chol(sigma)))
polygon(ell, col=fill.col[i], border=col[i], lty=1)
}
}
points(means[,1], means[,2], col=col, pch="+", cex=2)
pos <- ifelse(means[,2]>0, 3, 1)
text(means[,1], means[,2],
col = col,
labels=labels, pos=pos)
# plot variable vectors
# DONE: replaced previous scaling with vecscale()
# maxrms <- function(x) { max(sqrt(apply(x^2, 1, sum))) }
if (missing(scale)) {
# vecmax <- maxrms(structure)
# scrmax <- maxrms(scores)
# scale <- floor( 0.9 * scrmax / vecmax )
scale <- vecscale(structure)
message("Vector scale factor set to ", round(scale, 3))
}
# DONE: replaced with call to vectors()
cs <- scale * structure
if(!missing(var.labels)) rownames(cs) <- var.labels
vectors(cs, col=var.col, cex=var.cex, lwd=var.lwd, pos=var.pos, xpd=TRUE)
### why doesn't this work???
circle <- function( center, radius, segments=41, ...) {
angles <- (0:segments) * 2 * pi/segments
unit.circle <- cbind(cos(angles), sin(angles))
circle <- t(as.numeric(center) + radius*t(unit.circle))
lines(circle, col = col, ...)
}
# plot confidence circles for canonical means
if ( conf>0 ) {
n <- as.vector(table(factors))
radii <- sqrt(qchisq(conf, 2) / n)
symbols(means, circles=radii, inches=FALSE, add=TRUE, fg=col)
# for (i in 1:nrow(means)) {
# circle(means[i,1:2], radii[i], ...)
# ellipse(as.vector(means[i,]), diag(2), radii[i], col=col, ...)
# }
}
}