library(psych)
library(psychTools)

Adventures in simple graphics

Graphic displays are among the most useful tools in R. Some of the basic graphic tools have been used in the psych package for displays of factor/cluster structures, models of linear regressions, etc. These all take advantage of some basic graphic tools.

The most basic function is plot which creates a graphic window with certain parameters (arguments).

From the plot help file

Arguments to be passed to methods, such as graphical parameters (see par). Many methods will accept the following arguments:

type what type of plot should be drawn. Possible types are

“p” for points,

“l” for lines,

“b” for both,

“c” for the lines part alone of “b”,

“o” for both ‘overplotted’,

“h” for ‘histogram’ like (or ‘high-density’) vertical lines,

“s” for stair steps,

“S” for other steps,

see ‘Details’ below,

“n” for no plotting.

All other types give a warning or an error; using, e.g., type = “punkte” being equivalent to type = “p” for S compatibility.

Note that some methods, e.g. plot.factor, do not accept this.

Other parameters passed to plot

xlim The size of the x axis ylim The size of the y axis

xlab The label for the x axis ylab The label for the y axis

main A title for the figure

axes Show the axes (defaults to TRUE)

frame.plot Show the frame (defaults to TRUE)

cex The size of the characters

pch Which plot character to use for data points

plot(0, type="n")

plot(x=1:10,y=10:1, xlab="X variable" , ylab= "Y variable", main="A title for this plot")

the text function will add text at an x, y location

plot(x=1:10,y=10:1, xlab="X variable" , ylab= "Y variable", main="A title for this plot")
text(2,4,"some text")
text(8,6, "more informative text")

# Psych graphics use core R graphics

tidyverse makes use of ggplot which is more powerful.

psych has two broad classes of graphics: those that draw diagrams and those that plot data.

We will examine the code behind the various diagram functions. fa.diagram, iclust.diagram, lmDiagram call the primitive functions in the diagram.

These primitives dia.rect, dia.ellipse, dia.arrow, dia.curved.arrow may be combined to make complex diagrams.

To use these functions, we first must initialize the plot function with margins.

Consider a simple figure

xlim=c(-2,10);   ylim=c(0,10)
#initiate plot
plot(NA,xlim=xlim,ylim=ylim,main="Demonstration of diagram functions", axes=FALSE, xlab="", ylab="")

That just creates an empty page. We do this again, with some shapes. We specify where on the page we want the shapes.

#first set up the margins and initiate the plot
xlim=c(-2,10);   ylim=c(0,10)
plot(NA,xlim=xlim,ylim=ylim,main="Demonstration of diagram functions",
axes=FALSE,xlab="",ylab="")
#place and name the objects
ul <- dia.rect(1,9,labels="upper left",xlim=xlim,ylim=ylim)
ml <- dia.rect(1,6,"middle left",xlim=xlim,ylim=ylim)
ll <- dia.rect(1,3,labels="lower left",xlim=xlim,ylim=ylim)
bl <- dia.rect(1,1,"bottom left",xlim=xlim,ylim=ylim)
lr <- dia.ellipse(7,3,"lower right",xlim=xlim,ylim=ylim,e.size=.07)
ur <- dia.ellipse(7,9,"upper right",xlim=xlim,ylim=ylim,e.size=.07)
mr <- dia.ellipse(7,6,"middle right",xlim=xlim,ylim=ylim,e.size=.07)
lm <- dia.triangle(4,1,"Lower Middle",xlim=xlim,ylim=ylim)
br <- dia.rect(9,1,"bottom right",xlim=xlim,ylim=ylim) 

Now, we want to connect these objects with curves and arrows.

#first set up the margins and initiate the plot
xlim=c(-2,10);   ylim=c(0,10)
plot(NA,xlim=xlim,ylim=ylim,main="Demonstration of diagram functions",
axes=FALSE,xlab="",ylab="")
#place and name the objects
ul <- dia.rect(1,9,labels="upper left",xlim=xlim,ylim=ylim)
ml <- dia.rect(1,6,"middle left",xlim=xlim,ylim=ylim)
ll <- dia.rect(1,3,labels="lower left",xlim=xlim,ylim=ylim)
bl <- dia.rect(1,1,"bottom left",xlim=xlim,ylim=ylim)
lr <- dia.ellipse(7,3,"lower right",xlim=xlim,ylim=ylim,e.size=.07)
ur <- dia.ellipse(7,9,"upper right",xlim=xlim,ylim=ylim,e.size=.07)
mr <- dia.ellipse(7,6,"middle right",xlim=xlim,ylim=ylim,e.size=.07)
lm <- dia.triangle(4,1,"Lower Middle",xlim=xlim,ylim=ylim)
br <- dia.rect(9,1,"bottom right",xlim=xlim,ylim=ylim) 
#connect them
dia.curve(from=ul$left,to=bl$left,"double headed",scale=-1)
dia.arrow(from=lr,to=ul$right,labels="right to left")
dia.arrow(from=ul,to=ur,labels="left to right")
dia.curved.arrow(from=lr,to=ll,labels ="right to left")
## NULL
dia.curved.arrow(to=ur,from=ul,labels ="left to right")
## NULL
dia.curve(ll$top,ul$bottom,"right",pos=4)  #for rectangles, specify where to point 
dia.arrow(from=lm, to= lr, "lower middle to lower right")
dia.curve(ur$right,lr$right,"top down",scale =2) #scale is size and curvature
dia.curve(ur$right,mr$right,"top down",scale =1) #scale is size and curvature

That is better, but lets clean it up a bit by changing the size, as well as fixing some of the text.

#first set up the margins and initiate the plot
xlim=c(-1,10);      ylim=c(1,10)
plot(NA,xlim=xlim,ylim=ylim,main="Demonstration of diagram functions",
      axes=FALSE,xlab="",ylab="")
#place and name the objects
ul <- dia.rect(1,9,labels="upper left",xlim=xlim,ylim=ylim)
ml <- dia.rect(1,6,"middle left",xlim=xlim,ylim=ylim)
ll <- dia.rect(1,3,labels="lower left",xlim=xlim,ylim=ylim)
bl <- dia.rect(1,1,"bottom left",xlim=xlim,ylim=ylim)
lr <- dia.ellipse(7,3,"lower right",xlim=xlim,ylim=ylim,e.size=.09)
ur <- dia.ellipse(7,9,"upper right",xlim=xlim,ylim=ylim,e.size=.08)
mr <- dia.ellipse(7,6,"middle right",xlim=xlim,ylim=ylim,e.size=.07) #too small
lm <- dia.triangle(4,1,"Lower Middle",xlim=xlim,ylim=ylim)
br <- dia.rect(9,1,"bottom right",xlim=xlim,ylim=ylim) 
#now connect the shapes
dia.curve(from=ul$left,to=bl$left,"double\nheaded",scale=-.8)
dia.arrow(from=lr,to=ul$right,labels="right to left")
dia.arrow(from=ul,to=ur,labels="left to\n right",gap.size=.25)
dia.curved.arrow(from=lr,to=ll,labels ="right to\n left")
## NULL
dia.curved.arrow(to=ur,from=ul,labels ="left to right")
## NULL
dia.curve(ll$top,ul$bottom,"right",pos=4)  #for rectangles, specify where to point 
dia.arrow(from=lm, to= lr, "lower middle to \nlower right")#use \n for new line
dia.curve(ur$right,lr$right,"top down",scale =2) #scale is size and curvature
dia.curve(ur$right,mr$right,"top down",scale =1) #scale is size and curvature

This figure is a bit squased. Can we increase the y dimension.

#first set up the margins and initiate the plot
xlim=c(-1,10);      ylim=c(0,10)
plot(NA,xlim=xlim,ylim=ylim,main="Demonstration of diagram functions",
      axes=FALSE,xlab="",ylab="")
#place and name the objects
ul <- dia.rect(1,9,labels="upper left",xlim=xlim,ylim=ylim)
ml <- dia.rect(1,6,"middle left",xlim=xlim,ylim=ylim)
ll <- dia.rect(1,3,labels="lower left",xlim=xlim,ylim=ylim)
bl <- dia.rect(1,1,"bottom left",xlim=xlim,ylim=ylim)
lr <- dia.ellipse(7,3,"lower right",xlim=xlim,ylim=ylim,e.size=.09)
ur <- dia.ellipse(7,9,"upper right",xlim=xlim,ylim=ylim,e.size=.08)
mr <- dia.ellipse(7,6,"middle right",xlim=xlim,ylim=ylim,e.size=.07) #too small
lm <- dia.triangle(4,1,"Lower Middle",xlim=xlim,ylim=ylim)
br <- dia.rect(9,1,"bottom right",xlim=xlim,ylim=ylim) 
#now connect the shapes
dia.curve(from=ul$left,to=bl$left,"double\nheaded",scale=-.8)
dia.arrow(from=lr,to=ul$right,labels="right to left")
dia.arrow(from=ul,to=ur,labels="left to\n right",gap.size=.25)
dia.curved.arrow(from=lr,to=ll,labels ="right to\n left")
## NULL
dia.curved.arrow(to=ur,from=ul,labels ="left to right")
## NULL
dia.curve(ll$top,ul$bottom,"right",pos=4)  #for rectangles, specify where to point 
dia.arrow(from=lm, to= lr, "lower middle to \nlower right")#use \n for new line
dia.curve(ur$right,lr$right,"top down",scale =2) #scale is size and curvature
dia.curve(ur$right,mr$right,"top down",scale =1) #scale is size and curvature

This is not pretty. It looks better if we don’t run this in R studio.

Developing functions to do graphics

Take examples from the help pages and modify them.

Consider the help page for `layout’. This discusses how to create multiple sub windows in one window.

We use their example and their example data:

def.par <- par(no.readonly = TRUE) # save default, for resetting...
##-- Create a scatterplot with marginal histograms -----

x <- pmin(3, pmax(-3, stats::rnorm(50)))
y <- pmin(3, pmax(-3, stats::rnorm(50)))
xhist <- hist(x, breaks = seq(-3,3,0.5), plot = FALSE)
yhist <- hist(y, breaks = seq(-3,3,0.5), plot = FALSE)
top <- max(c(xhist$counts, yhist$counts))
xrange <- c(-3, 3)
yrange <- c(-3, 3)
nf <- layout(matrix(c(2,0,1,3),2,2,byrow = TRUE), c(3,1), c(1,3), TRUE)
layout.show(nf)

par(mar = c(3,3,1,1))
plot(x, y, xlim = xrange, ylim = yrange, xlab = "", ylab = "")
par(mar = c(0,3,1,1))
barplot(xhist$counts, axes = FALSE, ylim = c(0, top), space = 0)
par(mar = c(3,0,1,1))
barplot(yhist$counts, axes = FALSE, xlim = c(0, top), space = 0, horiz = TRUE)

par(def.par)  #- reset to default

Use those same x and y data, and draw it again, with scatterHist from psych. scatterHist took the basic concepts from layout, and then added some bells and whistles.

scatterHist(x,y, main="data from layout example")

From pairs to pairs.panels

Learn to read the help pages for each function. Then Try the examples.

The pairs function in core R has a very helpful help menu with examples.

pairs(iris[1:4], main = "Anderson's Iris Data -- 3 species",
      pch = 21, bg = c("red", "green3", "blue")[unclass(iris$Species)])

Examine the code

To see the code for particular functions, you can try just typing the name of the function. If it is a method then you can ask for the various versions of that method.

pairs  #either show the code or show that it is a method
## function (x, ...) 
## UseMethod("pairs")
## <bytecode: 0x140207448>
## <environment: namespace:graphics>
methods(pairs) #show all the methods using `pairs`
## [1] pairs.compareFits* pairs.default*     pairs.formula*     pairs.lme*         pairs.lmList*     
## [6] pairs.panels*     
## see '?methods' for accessing help and source code

We can then examine the code for any of those methods by calling getAnywhere with the name of the method. e.g. getAnywhere(pairs.default) (not run)

This was done to create pairs.panels function.

From help menu:

The various examples from the help page for pairs can be incorporated in one function pairs.panels for psych.’

Shamelessly adapted from the pairs help page. Uses panel.cor, panel.cor.scale, and panel.hist, all taken from the help pages for pairs. Also adapts the ellipse function from John Fox’s car package.

pairs.panels is most useful when the number of variables to plot is less than about 6-10. It is particularly useful for an initial overview of the data.

To show different groups with different colors, use a plot character (pch) between 21 and 25 and then set the background color to vary by group. (See the second example).

pairs.panels(iris[1:4],bg=c("red","yellow","blue")[iris$Species],
  pch=21+as.numeric(iris$Species),main="Fisher Iris data by Species",hist.col="red")