How to draw a custom made vector field in base R? - r

I want to generate a graph that would show a "vector field" in a plot using base R.
This section of the script will generate a graph with different circles (varying only their radius to make the base of the vector field).
r = 100 # Set the maximum radius to make an empty plot
x = function(ang) r* cos(ang) # To draw circles
y = function(ang) r* sin(ang) # To draw circles
nb = seq(from = 0,to = (2*pi),length.out = 100) # To have a sequence to draw the circles
plot(x(nb),y(nb), # Empty plot
asp = 1,
type = "n",
bg = "black",
col = "black",
pch =21, main = "Circle",
ylab = "Y values",
xlab ="X values")
abline(h=0,v=0) # Draw axes
for (i in seq(0,100,by = 5)) { # Draw a series of circles
r = i
points(x(nb),y(nb),
type = "l",
lwd = 1.0,
lty = 3)
}
# DRAWING TE VECTORS ----------------------
by = 10 # Define a "resolution" to see better the circles (This value will be smaller to be more precise)
changex = seq(0,100, by =by) # For each circle draw a radius with this sequence
current = -1 # This is to "flip" the orientation of the vectors
mag = current* seq(100,0, by = current*by)
arrows(x0 = changex, y0 = 0, # Draw the vectors
x1 = changex, y1 = mag,
code = 2,
length=0.1,
angle=40)
The rest of the code tries to print the vectors when changing the angle in the graph:
xycircle <- function(ang,r) { # function to draw position on the circle
x = r*cos(ang)
y = r*sin(ang)
return(list(x,y))
}
pilist = c(#0,1/4*pi,#1/2*pi, # List of PI values to go around the circle
#pi, #3/4*pi,
#3/2*pi,
2*pi)
for (pip in 1:length(pilist)) { # Going around the circle
ang = pilist[pip] # extract 1 angle value to draw
abline(a=0,b=tan(ang), lty = 3, lwd = 3) # Get a line that will show the angle selected
r = seq(0,100, by = by) # List of radius
mag = current* seq(-100,-0, by = by) # Magnitude of the vectors
for (i in 1:length(r)) { # Draw vectors when the angle changes
arrows(x0 = xycircle(ang,r[i])[[1]], # Base position of the vector (tangent to the circle)
y0 = xycircle(ang,r[i])[[2]],
x1 = cos(atan2(r[i],mag[i])-ang)*sqrt(r[i]^2+mag[i]^2), # Position of the tip of the vector (x)
y1 = sin(atan2(r[i],mag[i])-ang)*sqrt(r[i]^2+mag[i]^2), # Position of the tip of the vector (y)
code = 2, # Change the arrow head
length = 0.1,
angle = 40)
}
}
As you can see, when I complete a full revolution, the vectors are not aligned with the original vectors (they should...).
But when I start to turn around the circle (say 1/4*pi), it's perfect.
How would it be possible to make the vectors "turn" around the circle (based on angles) to make them rotate around the circle so that the vectors are always perpendicular to the circles (like the last graph, but all angles).

Finally, I got it to work
# parameter list ----------------------------------------------------------
by = 10
current = -1
invert.speed = TRUE
circleefrom = 10
circleeto = 28
# See 1 circle from 3 (2 for 4, 3 for 5...)
resolution = 15 # Which is also the number of arrows
resolution.circles = 100
maxcurrent = 10
mincurrent = 0
# Define functions --------------------------------------------------------
# Make a list of radius to draw various circles
r = seq(from = circleefrom, to = circleeto,
length.out = resolution)
# Make a function that will allow to draw circles and extract the values from the x,y position
xycircle <- function(ang,r) {
x = r*cos(ang)
y = r*sin(ang)
return(list(x,y))
}
# Samples enough point for the circles (100 at least)
nb = seq(from = 0, # starts at 0
to = (2*pi), # Does a FULL revolution around the circle
length.out = resolution.circles)
# Plot circles ------------------------------------------------------------
# Create empty canva
plot(x = xycircle(nb,max(r))[[1]],
y = xycircle(nb,max(r))[[2]],
asp = 1,
type = "n",
bg = "black",
col = "black",
pch =21, main = "Rheotaxis experiment",
ylab = "Y values",
xlab ="X values")
# Draw the axes
abline(h = 0,
v = 0)
# This will draw the circles
dbcicle = NULL
for (i in seq(from = circleefrom, to = circleeto,
length.out = resolution)) {
points(x = xycircle(nb,i)[[1]],
y = xycircle(nb,i)[[2]],
col = "blue",
type = "l",
lwd = 1.0,
lty = 3)
tmp = as.data.frame.list(x = c(xycircle(nb,i),i), col.names = c("x","y","radius"))
dbcicle = rbind(dbcicle,tmp)
}
# Add a black center to the design
plotrix::draw.circle(0, 0, radius = circleefrom,
nv = 1000,
border = NULL,
col = "black", lty = 1, lwd = 1)
# Add circle at the contour
plotrix::draw.circle(0, 0, radius = circleeto,
nv = 1000,
border = NULL,
col = NA, lty = 1, lwd = 1)
# Current specification ---------------------------------------------------
# Create the simulated current
if(invert.speed) {
mag = current * seq(from = mincurrent,
to = maxcurrent,
length.out = resolution + 1)
} else {
mag = current * seq(from = maxcurrent,
to = mincurrent,
length.out = resolution + 1)
}
# mag = current * rep(16,11)
# Draw the vecotrs of current --------------------------------------------
# Get different angle values
pilist = seq(from = 1/2*pi,
to = 2*pi,
by = 1/2*pi)
pilist = seq(from = 0,
to = 2*pi,
length.out = resolution)
dbcicle2 = NULL
for (pip in 1:length(pilist)) {
ang = pilist[pip]
abline(a=0,b=tan(ang), lty = 3, lwd = 3)
for (i in 1:length(r)) {
arrows(x0 = xycircle(ang,r[i])[[1]],
y0 = xycircle(ang,r[i])[[2]],
x1 = xycircle(c(ang-atan2(mag[i],r[i])),sqrt(r[i]^2+mag[i]^2))[[1]],
y1 = xycircle(c(ang-atan2(mag[i],r[i])),sqrt(r[i]^2+mag[i]^2))[[2]],
col = "blue",
code = 2,
length = 0.1,
angle = 40)
tmp = as.data.frame.list(x = c(xycircle(ang,r[i]),r[i],-1*mag[i],ang+pi/2),
col.names = c("x","y","radius","magnitude","ang"))
dbcicle2 = rbind(dbcicle2,tmp)
}
}

Related

Generalizing a 2D plot to 3D in R

I have a problem where I have data with (x,y) coordinates that I want to plot in the x-y plane. Furthermore, I have some box constraints such that -7 < x < 7 and -5 < y < 5 need to be drawn and checked. All points that fall outside of this box constraint I would like to color red. To do this I have the following code in R:
library(rgl)
x <- 7
y <- 5
data.x <- rnorm(10,0,5)
data.y <- rnorm(10,0,5)
plot(data.x, data.y, xlim = c(min(-x,data.x),max(x,data.x)),
ylim = c(min(-y,data.y),max(y,data.y)), pch = 19)
rect(-x, -y, x, y, col = "lightgrey")
idx <- abs(data.x) > x | abs(data.y) > y
points(data.x[idx], data.y[idx], col = "red", pch = 19)
points(data.x[!idx], data.y[!idx], col = "deepskyblue", pch = 19)
Now, where I am stuck, is on how to plot this type of data picture when I have a third group of data and a third constraint. I.e.,
### How to generalize when I have a third axis and constraint, i.e., a 3D cube
z <- 4
data.z <- rnorm(10, 0, 5)
So essentially I want to plot a box constraint as a cube in the x-y-z plane, and to color the points that fall outside the box constraint red again.
Also, I should say I understand there are functions for plottig 3d scatter plots in R, however, what I am struggling with is how to draw the 3D cube that defines the constraints.
The difficulty with a 3D plot such as this is being able to interpret the "depth" of the points in the image. An animated 3D image might be helpful here:
library(plot3D)
x <- 7
y <- 5
z <- 6
set.seed(123)
data.x <- rnorm(10, 0, 5)
data.y <- rnorm(10, 0, 5)
data.z <- rnorm(10, 0, 5)
in_out <- abs(data.x) > x | abs(data.y) > y | abs(data.z) > z
for(i in seq(0, 358, 2)) {
png(paste0("box", sprintf("%03d", i), ".png"))
box3D(-x, -y, -z, x, y, z, col = NA, border = "gray50", theta = i, phi = 15,
xlim = c(-10, 10), ylim = c(-10, 10), zlim = c(-10, 10),
axes = TRUE, ticktype = "detailed")
points3D(data.x, data.y, data.z, colvar = in_out, pch = 16, cex = 3,
add = TRUE, colkey = FALSE, col = c("lightblue", "red"))
dev.off()
}
library(magick)
list.files(pattern = 'box\\d+\\.png', full.names = TRUE) %>%
image_read() %>%
image_join() %>%
image_animate(fps=50) %>%
image_write("box.gif")
box.gif

R plot3d color gardient legend

I am having a 3D plot in which the points are colored acording to some extra vector. My problem is to add a color gradient legend. This is my code:
x = matrix(NA,100,6)
#x value
x[,1] = runif(100, 0, 10)
#y value
x[,2] = runif(100, 0, 10)
#z value
x[,3] = x[,1]+x[,2]
#additional value
x[,4] = runif(100, 0, 1)
#find out in which interval each additional value is
intervals = seq(0,1,1/10)
x[,5] = findInterval(x[,4], intervals)
colours = topo.colors(length(intervals))
x[,6] = colours[x[,5]]
library(rgl)
plot3d(as.numeric(x[,1]),as.numeric(x.stab.in[,2]), as.numeric(x[,3]),
type="p", col=x[,6], size=2, xlab = "x(t)", ylab = "y(t)",
zlab = "z(t)")
decorate3d(xlab = "x", ylab = "y", zlab = "z")
legend3d("topright", legend = intervals, pch = 16, col = colours, cex=1, inset=c(0.02))
grid3d(c("x", "y+", "z"),col = "gray")
The plot looks like this
but I want the legend in a gradient form. That means I don't want separate points for each color but one box in which the colors fade into each other.
Here is a possible solution if you are okay with using scatterplot3d package instead of rgl. It is basically same but non-interactive. Here is your code modified to produce your expected result.
x = matrix(NA,100,6)
#x value
x[,1] = runif(100, 0, 10)
#y value
x[,2] = runif(100, 0, 10)
#z value
x[,3] = x[,1]+x[,2]
#additional value
x[,4] = runif(100, 0, 1)
#find out in which interval each additional value is
intervals = seq(0,1,1/10)
x[,5] = findInterval(x[,4], intervals)
#produce gradient of colors
#you can define different colors (two or more)
gradient <- colorRampPalette(colors = c("yellow", "green", "blue"))
colours <- gradient(length(intervals))
x[,6] = colours[x[,5]]
library(scatterplot3d)
png('3d.png', width = 600, height = 400)
layout(matrix(1:2, ncol=2), width = c(3, 1), height = c(1, 1))
scatterplot3d(as.numeric(x[,1]),as.numeric(x[,2]), as.numeric(x[,3]), type = 'p',
cex.symbols = 1.25, color=x[,6], pch = 16, xlab = "x(t)", ylab = "y(t)", zlab = "z(t)")
plot(x = rep(1, 100), y = seq_along(x[,6]),
pch = 15, cex = 2.5,
col = gradient(length(x[,6])),
ann = F, axes = F, xlim = c(1, 2))
axis(side = 2, at = seq(1, nrow(x), length.out = 11),
labels = 1:11,
line = 0.15)
dev.off()
This will plot the following graph
Here is another solution if you want to plot a gradient on an interactive 3d plot, such as if you needed to animate the plot into a movie.
require(car)
require(rgl)
require(RColorBrewer)
require(mgcv)
require(magick) #Only for creating the animation of the plot as a gif
#Creating mock dataset
Example_Data <- data.frame(Axis1 = rnorm(100),
Axis2 = rnorm(100),
Axis3 = rnorm(100))
Example_Data$Value <- Example_Data$Axis1+Example_Data$Axis2
#Defining function that takes a vector of numeric values and converts them to
#a spectrum of rgb colors to help color my scatter3d plot
get_colors <- function(values){
v <- (values - min(values))/diff(range(values))
x <- colorRamp(rev(brewer.pal(11, "Spectral")))(v)
rgb(x[,1], x[,2], x[,3], maxColorValue = 255)
}
#Writing function that takes a vector of numeric values and a title and creates
#a gradient legend based on those values and the title and suitable for addition
#to a scatter3d plot via a call to bgplot3d()
#Note, I didn't have time to make this automatically adjust text position/size for different size
#plot windows, so values may need to be adjusted manually depending on the size of the plot window.
gradient_legend_3d <- function(values, title){
min_val <- min(values)
max_val <- max(values)
x <- colorRamp(brewer.pal(11, "Spectral"))((0:20)/20)
colors <- rgb(x[,1], x[,2], x[,3], maxColorValue = 255)
legend_image <- as.raster(matrix(colors, ncol=1))
plot(c(0,1),c(0,1),type = 'n', axes = F,xlab = '', ylab = '', main = '') #Generates a blank plot
text(x=0.92, y = seq(0.5, 1,l=5), labels = signif(seq(min_val, max_val,l=5), 2), cex = 1.5) #Creates the numeric labels on the scale
text(x = 0.85, y = 1, labels = title, adj = 1, srt = 90, cex = 1.5) #Determines where the title is placed
rasterImage(legend_image, 0.87, 0.5, 0.9,1) #Values can be modified here to alter where and how wide/tall the gradient is drawn in the plotting area
}
#Creating scatter3d plot
scatter3d(x = Example_Data$Axis1, y = Example_Data$Axis2, z = Example_Data$Axis3, xlab = "Axis1", ylab = "Axis2", zlab = "Axis3", surface = F, grid = F, ellipsoid = F, fogtype = "none", point.col = get_colors(Example_Data$Value))
#Changing size of plotting window and orientation to optimize for addition of static legend
#This may not work on another machine, so the window may need to be adjusted manually
par3d(windowRect = c(0,23,1536,824))
par3d(userMatrix = matrix(c(-0.98181450, -0.02413967, 0.18830180, 0, -0.03652956, 0.99736959, -0.06260729, 0, -0.18629514, -0.06834736, -0.98011345, 0, 0, 0, 0, 1), nrow = 4, ncol = 4, byrow = T))
#Adding legend
bgplot3d(gradient_legend_3d(Example_Data$Value, "Point Value"))
#Animating plot and saving as gif
movie3d(spin3d(axis = c(0,1,0), rpm = 5), duration = 12, dir = getwd(), fps = 5, convert = FALSE, clean = FALSE)
frames <- NULL
for(j in 0:60){
if(j == 1){
frames <- image_read(sprintf("%s%03d.png", "movie", j))
} else {
frames <- c(frames, image_read(sprintf("%s%03d.png", "movie", j)))
}
}
animation <- image_animate(frames, fps = 10, optimize = TRUE)
image_write(animation, path = "Example.gif")
for(j in 0:60){
unlink(sprintf("%s%03d.png", "movie", j))
}
See link to view 3d plot generated by this code:
gif of 3d plot with gradient color scale

How to create minimum bounding rectangle over complete dataset in R

Say I have a set of coordinates like this, for example:
m <- data.frame(replicate(2,sample(0:9,20,rep=TRUE)))
And I want to draw a box around all of the points so that it creates a minimum bounding rectangle.
a <- bounding.box.xy(m)
plot(m)
par(new=T)
plot(a, main="Minimum bounding rectangle")
But the box doesn't go around all of the points.
I am also interested in drawing a standard deviation circle/ellipse around these points but I don't know the function for this.
RECTANGLE
You can obtain the value of minimum and maximum x and y and then draw polygon using those values. Try this:
set.seed(42)
m <- data.frame(replicate(2,sample(0:9,20,rep=TRUE)))
lx = min(m$X1)
ux = max(m$X1)
ly = min(m$X2)
uy = max(m$X2)
plot(m)
title(main = "Minimum bounding rectangle")
polygon(x = c(lx, ux, ux, lx), y = c(ly, ly, uy, uy), lty = 2)
POLYGON
More discussion about drawing a curve around a set of points can be found here. One way is to exploit the chull command for creating convex hull.
First import the following function
plot_boundary <- function(x,y,offset = 0,lty = 1,lwd = 1,border = "black",col = NA){
# 'offset' defines how much the convex hull should be bumped out (or in if negative value)
# relative to centroid of the points. Typically value of 0.1 works well
BX = x + offset*(x-mean(x))
BY = y + offset*(y-mean(y))
k2 = chull(BX,BY)
polygon(BX[k2],BY[k2],lty = lty,lwd = lwd,border = border,col = col)
}
Then you can generate data and plot boundary around it.
set.seed(242)
m <- data.frame(replicate(2,sample(0:9,20,rep=TRUE)))
plot(m, xlim = c(0,10), ylim = c(0,10))
title(main = "Minimum bounding rectangle")
plot_boundary(x = m$X1, y = m$X2, lty = 2)
ELLIPSE
set.seed(42)
A = data.frame(x = rnorm(20, 25, 4), y = rnorm(20, 11, 3))
B = data.frame(x = rnorm(20, 12, 5), y = rnorm(20, 5, 7))
plot(rbind(A,B), type = "n", ylim = c(-10,20), xlim = c(0,40), asp = 1)
require(ellipse)
red_eli = ellipse(cor(A$x,A$y), scale = c(sd(A$x), sd(A$y)),
centre = c(mean(A$x), mean(A$y)))
blue_eli = ellipse(cor(B$x,B$y), scale = c(sd(B$x), sd(B$y)),
centre = c(mean(B$x), mean(B$y)))
points(A, pch = 19, col = "red")
points(B, pch = 18, col = "blue")
lines(red_eli, col = "red")
lines(blue_eli, col = "blue", lty = 2)

How to add threshold line when there are multiple graphs?

I am using a function which plots two graphs on the same picture. I want to add threshold line to the first graph and a different threshold on the second graph. I am using abline() function to do so. chr6 comes with a library as an example.
install.packages("GenWin")
library(GenWin)
chrom_num = 6
jpeg(filename = paste(chrom_num, ".jpg", sep=""), width = 1200, height = 800)
chr = splineAnalyze(chr6$Fst, chr6$Position, plotRaw = 1, plotWindows = 1, method = 4)
abline(0.3, 0, col = "green")
abline(6, 0, col = "green")
Both threshold lines show up on the second graph. How to prevent this? In other word is there a way to direct to which graph I am adding something?
If you want to add something, I think it would be better to make graphs by yourself from the analyzed data, chr (almost all code is picked out from splineAnalyze). This approach would enable you to customize the graph.
analyzed_data <- chr # All you need to do is changing these lines and data and col names of 1st plot()).
smoothness <- 100 # default value
jpeg(filename = paste("file_name", ".jpg", sep=""), width = 1200, height = 800)
par(mfrow = c(2,1))
# 1st graph
plot(Fst ~ Position, chr6, xlab = "Position (bp)", ylab = "Raw values")
with(analyzed_data,
lines(x = seq(0, max(rawSpline$x), by = smoothness),
y = predict(rawSpline, seq(0, max(rawSpline$x), by = smoothness)), col = "red")
)
abline(0.3, 0, col = "green")
# 2nd graph
with(analyzed_data,
plot(x = (windowData$WindowStop - windowData$WindowStart)/2 + windowData$WindowStart,
y = windowData$Wstat, xlab = "Position (bp)", ylab = "Spline Wstat", pch = 19)
)
abline(6, 0, col = "green")
dev.off()
Of course, you can do it using splineAnalyze(..., plotRaw = 1, plotWindows = 1, ...) and adding the lines.
jpeg(filename = paste("file_name2", ".jpg", sep=""), width = 1200, height = 800)
chr = splineAnalyze(chr6$Fst, chr6$Position, plotRaw = 1, plotWindows = 1, method = 4)
abline(6, 0, col = "green") # draw on 2nd panel
layout(matrix(c(2,1), ncol = 1)) # refocus 1st panel
par(new = T)
plot(Fst ~ Position, chr6, ann = F, type = "n", axes = F) # reproduce the coordinates
abline(0.3, 0, col = "green") # draw on 1st panel
dev.off()

Plot A Confusion Matrix with Color and Frequency in R

I want to plot a confusion matrix, but, I don't want to just use a heatmap, because I think they give poor numerical resolution. Instead, I want to also plot the frequency in the middle of the square. For instance, I like the output of this:
library(mlearning);
data("Glass", package = "mlbench")
Glass$Type <- as.factor(paste("Glass", Glass$Type))
summary(glassLvq <- mlLvq(Type ~ ., data = Glass));
(glassConf <- confusion(predict(glassLvq, Glass, type = "class"), Glass$Type))
plot(glassConf) # Image by default
However, 1.) I don't understand that the "01, 02, etc" means along each axis. How can we get rid of that?
2.) I would like 'Predicted' to be as the label of the 'y' dimension, and 'Actual' to be as the label for the 'x' dimension
3.) I would like to replace absolute counts by frequency / probability.
Alternatively, is there another package that will do this?
In essence, I want this in R:
http://www.mathworks.com/help/releases/R2013b/nnet/gs/gettingstarted_nprtool_07.gif
OR:
http://c431376.r76.cf2.rackcdn.com/8805/fnhum-05-00189-HTML/image_m/fnhum-05-00189-g009.jpg
The mlearning package seems quite inflexible with plotting confusion matrices.
Starting with your glassConf object, you probably want to do something like this:
prior(glassConf) <- 100
# The above rescales the confusion matrix such that columns sum to 100.
opar <- par(mar=c(5.1, 6.1, 2, 2))
x <- x.orig <- unclass(glassConf)
x <- log(x + 0.5) * 2.33
x[x < 0] <- NA
x[x > 10] <- 10
diag(x) <- -diag(x)
image(1:ncol(x), 1:ncol(x),
-(x[, nrow(x):1]), xlab='Actual', ylab='',
col=colorRampPalette(c(hsv(h = 0, s = 0.9, v = 0.9, alpha = 1),
hsv(h = 0, s = 0, v = 0.9, alpha = 1),
hsv(h = 2/6, s = 0.9, v = 0.9, alpha = 1)))(41),
xaxt='n', yaxt='n', zlim=c(-10, 10))
axis(1, at=1:ncol(x), labels=colnames(x), cex.axis=0.8)
axis(2, at=ncol(x):1, labels=colnames(x), las=1, cex.axis=0.8)
title(ylab='Predicted', line=4.5)
abline(h = 0:ncol(x) + 0.5, col = 'gray')
abline(v = 0:ncol(x) + 0.5, col = 'gray')
text(1:6, rep(6:1, each=6),
labels = sub('^0$', '', round(c(x.orig), 0)))
box(lwd=2)
par(opar) # reset par
The above code uses bits and pieces of the confusionImage function called by plot.confusion.
Here is a function for plotting confusion matrices I developed from jbaums excellent answer.
It is similar, but looks a bit nicer (IMO), and does not transpose the confusion matrix you feed it, which might be helpful.
### Function for plotting confusion matrices
confMatPlot = function(confMat, titleMy, shouldPlot = T) {
#' Function for plotting confusion matrice
#'
#' #param confMat: confusion matrix with counts, ie integers.
#' Fractions won't work
#' #param titleMy: String containing plot title
#' #return Nothing: It only plots
## Prepare data
x.orig = confMat; rm(confMat) # Lazy conversion to function internal variable name
n = nrow(x.orig) # conf mat is square by definition, so nrow(x) == ncol(x)
opar <- par(mar = c(5.1, 8, 3, 2))
x <- x.orig
x <- log(x + 0.5) # x<1 -> x<0 , x>=1 -> x>0
x[x < 0] <- NA
diag(x) <- -diag(x) # change sign to give diagonal different color
## Plot confusion matrix
image(1:n, 1:n, # grid of coloured boxes
# matrix giving color values for the boxes
# t() and [,ncol(x):1] since image puts [1,1] in bottom left by default
-t(x)[, n:1],
# ylab added later to avoid overlap with tick labels
xlab = 'Actual', ylab = '',
col = colorRampPalette(c("darkorange3", "white", "steelblue"),
bias = 1.65)(100),
xaxt = 'n', yaxt = 'n'
)
# Plot counts
text(rep(1:n, each = n), rep(n:1, times = n),
labels = sub('^0$', '', round(c(x.orig), 0)))
# Axis ticks but no lables
axis(1, at = 1:n, labels = rep("", n), cex.axis = 0.8)
axis(2, at = n:1, labels = rep("", n), cex.axis = 0.8)
# Tilted axis lables
text(cex = 0.8, x = (1:n), y = -0.1, colnames(x), xpd = T, srt = 30, adj = 1)
text(cex = 0.8, y = (n:1), x = +0.1, colnames(x), xpd = T, srt = 30, adj = 1)
title(main = titleMy)
title(ylab = 'Predicted', line = 6)
# Grid and box
abline(h = 0:n + 0.5, col = 'gray')
abline(v = 0:n + 0.5, col = 'gray')
box(lwd = 1, col = 'gray')
par(opar)
}
Example of output:

Resources