Section author: Jonathon Love
7. Adding Plots
In this section, we’ll add a plot to the t-test analysis we’ve been
developing in this series. A plot is another item to appear in the
results, so we’ll add another entry into our ttest.r.yaml
file:
- name: ttest
title: Independent Samples T-Test
jrs: "1.1"
items:
- name: ttest
title: Independent Samples T-Test
type: Table
rows: 1
columns:
- name: var
title: ""
type: text
- name: t
type: number
- name: df
type: integer
- name: p
type: number
format: zto,pvalue
- name: plot
title: Descriptives Plot
type: Image
width: 400
height: 300
renderFun: .plot
Same as before, we define an item with a name, title and a type; in this case the type is Image. Additionally, we define renderFun which is the name of the function responsible for rendering the image. Whatever we specify as the render function, we must add as a private member function to ttestClass in ttest.b.R:
#' @export
ttestClass <- R6::R6Class(
"ttestClass",
inherit = ttestBase,
private = list(
.run = function() {
formula <- paste(self$options$dep, '~', self$options$group)
formula <- as.formula(formula)
results <- t.test(formula, self$data)
table <- self$results$ttest
table$setRow(rowNo=1, values=list(
var=self$options$dep,
t=results$statistic,
df=results$parameter,
p=results$p.value
))
},
.plot=function(image, ...) { # <-- the plot function
})
)
7.1. Adding ggplot2
We’re going to use ggplot2
for plotting, so make sure you have that
installed. To use ggplot2 in this package/module, we need to add some
entries into the DESCRIPTION and NAMESPACE files. We add ggplot2 to the
imports line in the DESCRIPTION, so it reads:
Imports: jmvcore, R6, ggplot2
and we’ll add the following line into NAMESPACE:
import(ggplot2)
These entries are standard for using R code from other packages in a package. More information is available in Writing R Extensions.
Now we have ggplot2 ready, we can proceed with using it in our analysis.
7.2. Implementing Plots
In jamovi modules, plotting occurs in two stages; first the data for the plot is prepared, then the plot is rendered. The two stages mean that if the image is resized, or the user requests a different file format, only the rendering needs to be performed again — the data preparation needs only to occur once.
For the t-test, we’re going to plot a mean for each of the groups, and
the standard errors. In ggplot2
, we’re required to assemble these
‘plot points’ into a data frame, which we will do as follows:
means <- aggregate(formula, self$data, mean)[,2]
ses <- aggregate(formula, self$data, function(x) sd(x)/sqrt(length(x)))[,2]
sel <- means - ses # standard error lower bound
seu <- means + ses # standard error upper bound
levels <- base::levels(self$data[[self$options$group]])
plotData <- data.frame(level=levels, mean=means, sel=sel, seu=seu)
## level mean sel seu
## 1 OJ 20.66333 19.45733 21.86934
## 2 VC 16.96333 15.45417 18.47250
This plot data we assign to the image using the setState()
function:
image <- self$results$plot
image$setState(plotData)``
Next, we’ll add the plotting code into the .plot()
function we
created:
.plot=function(image, ...) {
plotData <- image$state
plot <- ggplot(plotData, aes(x=level, y=mean)) +
geom_errorbar(aes(ymin=sel, ymax=seu, width=.1)) +
geom_point(aes(x=level, y=mean)) +
labs(title=self$options$dep)
print(plot)
TRUE
}
The plot function accepts an argument image
, which corresponds to
the image object we called setState()
on. We can retrieve the state
object from this image with image$state
, which we can see is being
assigned to plotData
.
Following this are a number of calls to ggplot2
functions. A full
discussion of how to use ggplot2 is well and truly beyond the scope
of this document, but there are many excellent resources available
online.
Next we explicitly print the ggplot object. When using ggplot
interactively in an R session, calling ggplot()
leads to the
creation of the plot, however, when calling ggplot from inside a
function, it is necessary to explicitly call print()
.
The final statement is TRUE
which is the return value. Don’t forget
this! Returning true notifies the rendering system that you have plotted
something. If you don’t return true, your plot will not appear. There
are situations where the user may not have specified enough information
for plotting, in which case the function should return FALSE
.
So this is our final ttest.b.R
file:
#' @export ttestClass <- R6::R6Class("ttestClass", inherit = ttestBase, private = list(
.run = function() {
formula <- paste(self$options$dep, '~', self$options$group)
formula <- as.formula(formula)
results <- t.test(formula, self$data)
table <- self$results$ttest
table$setRow(rowNo=1, values=list(
var=self$options$dep,
t=results$statistic,
df=results$parameter,
p=results$p.value
))
means <- aggregate(formula, self$data, mean)[,2]
ses <- aggregate(formula, self$data, function(x) sd(x)/sqrt(length(x)))[,2]
sel <- means - ses # standard error lower bound
seu <- means + ses # standard error upper bound
levels <- base::levels(self$data[[self$options$group]])
plotData <- data.frame(level=levels, mean=means, sel=sel, seu=seu)
image <- self$results$plot
image$setState(plotData)
},
.plot=function(image, ...) {
plotData <- image$state
plot <- ggplot(plotData, aes(x=level, y=mean)) +
geom_errorbar(aes(ymin=sel, ymax=seu, width=.1)) +
geom_point(aes(x=level, y=mean)) +
labs(title=self$options$dep)
print(plot)
TRUE
})
)
And these are our final results, including the plot: