Return to Safety Graphics Home
Dot plots
Contributed by: Mat Soukup (email: Mat.Soukup@fda.hhs.gov)
Type of Data: continuous
Type of Analysis: univariate
Description and Purpose:
The dotplot is a useful graphical approach for plotting a quantitative variable with labels representing the quantitative measurement (e.g., when looking at adverse event rates, a label for the adverse event accompanies the rate). The dotplot is an alternative to plotting data using such displays as bar charts and pie charts, though it has advantages over such approaches in that it allows one the ability to plot data in which the baseline of the data is not zero.
Comparison of Multiple Graphing Techniques
To illustrate the use of multiple graphical displays suppose we want to illustrate the disposition of subjects at the end of trial. Table 1 depicts the disposition along with the percentage of subjects in each category.
Table 1: Disposition
Disposition |
Percentage |
Completed Study |
75.0% |
Adverse Event |
7.0% |
Lack of Efficacy |
8.0% |
Withdrew Consent |
6.0% |
Other |
4.0% |
Figure 1 depicts the data from Table 1 using three approaches: dotplot, pie chart, and barplot. In this example, we can see the limitations of the pie chart as it is difficult to interpret the numerical differences for subjects that did not complete the study. Both the dotplot and the barchart show the magnitude of the differences. However, the dotplot has an advantage over the barchart in that the labels are written horizontally for the disposition as well as the ink used in the plotting region only corresponds to the point of interest, in this case the percentage.
Figure 1: Disposition Graphical Displays
Figure 1
Application of Dotplots to Adverse Event Data
As an illustrative example, we can plot the percent of subjects that experience an adverse event (using MedDRA preferred terms) in a clinical trial (Figure 2). In this example, we see that the preferred terms are listed in alphabetical order.
Figure 2: Percentage of subjects reporting an adverse event (MedDRA preferred term)
Figure 2
To aid in the visualization, the ordering of the preferred terms can be done based on the corresponding percentage (Figure 3). This plot allows one to easily see that PRURITUS and APPLICATION SITE PRURITUS are reported in the most frequently which requires more inspection if using Figure 2.
Figure 3: Percentage of subjects reporting an adverse event (MedDRA preferred term) sorted by Frequency
Figure 3
In addition, to using a basic dotplot as shown in Figures 2 and 3, additional information can be incorporated into the dotplot. For example, we could sort, the preferred according to the system organ classification (Figure 4). In this figure, color is used to denote the different the location of the SOC and terms are plotted by SOC descending from most frequent PT. Note that in this case, the color could be suppressed and rather the graphic could be printed in black and white since the SOC is provided on left side of the graphic.
Figure 4: Percentage of subjects reporting an adverse event (MedDRA preferred term) sorted by Frequency and System Organ Classification
Figure 4
The dotplot also allows one to use different plotting characters to correspond to a grouping (i.e. a superposition of plotting symbols). In the clinical trial realm, this would often times correspond to the treatment assignment. Figure 5 is similar in appearance to Figure 3 but adding in information for alternate treatment arms.
Figure 5: Percentage of subjects reporting an adverse event (MedDRA preferred term) sorted by frequency and grouped by treatment assignment
Figure 5
To incorporate additional information into the dotplot, one can also panel the display on additional categorical variable. Figure 6 contains such a plot in which the preferred term is paneled on the investigator’s determination of whether the event was related to treatment or not.
Figure 6: Percentage of subjects reporting an adverse event (MedDRA preferred term) sorted by frequency and grouped by treatment assignment with panels for treatment relationship
Figure 6
Reference: Cleveland’s “The Elements of Graphing Data” contains an extensive discussion on the use and application of dotplots. This was particularly helpful in devising an approach for presenting the information.
Code (R 10.1):
%CODE{lang="java"}%
Coding Information
All figures were created using R version 10.1.
Required Libraries
library(lattice)
library(SASxport)
library(vcd) # for color scheme
library(plotrix) # for pie chart
Data Preparation Code for ADAE CDISC Pilot Data set
adae <- read.xport("C:/Research/CDISC-ADaMPilot/900171/m5/datasets/CDISCPILOT01/analysis/ADAE.xPT",
names.tolower=TRUE)
adsl <- read.xport("C:/Research/CDISC-ADaMPilot/900171/m5/datasets/CDISCPILOT01/analysis/ADSL.xPT",
names.tolower=TRUE)
dat <- data.frame(ID=adae$usubjid,PT=adae$aedecod,TRT=adae$trtp)
# For each subject get only the unique PT
uid <- unique(adae$usubjid)
pts <- NULL
trts <- NULL
for(i in 1:length(uid)){
sdat <- subset(dat, ID%in%uid[i])
upt <- unique(sdat$PT)
npt <- length(upt)
rows <- NULL
for(k in 1:npt){
ww <- which(sdat$PT%in%upt[k])
rows[k] <- ww[1]
}
pts[i] <- list(sdat$PT[rows])
trts[i] <- list(sdat$TRT[rows])
}
dat1 <- data.frame(PT=unlist(pts), TRT=unlist(trts))
ss <- with(dat1, table(PT, TRT))
sums <- apply(ss, 1, sum)
ww <- which(sums>0)
xPT <- ss[ww,]
# Get percents
pPT <- NULL
for(j in 1:dim(xPT)[1]){
pPT[j] <- list(100*xPT[j,]/c(84,84,84)) #N/group come from ADSL
}
matP <- round(do.call('rbind', pPT),1)
# remove PT which occur in less than p subjects
sump <- apply(matP, 1, sum)
wsum <- which(sump > 5.0)
matXp <- xPT[wsum,]
matPp <- matP[wsum,]
rownames(matPp) <- rownames(matXp)
colnames(matPp) <- colnames(matXp)
plotdat <- data.frame(PT = rep(rownames(matPp), 3),
TRT = rep(colnames(matPp), each=dim(matPp)[1]),
PCT = c(matPp[,1], matPp[,2], matPp[,3]))
Setting up Graphical Parameters
hclcolors <- rainbow_hcl(4, l=50, start=50, c=75)[c(3,2,4,1)]
colorpalette <- c(hclcolors[1:3], 'grey45')
new.back <- trellis.par.get("background")
new.back$col <- "white"
newcol <- trellis.par.get("superpose.symbol")
newcol$col <- colorpalette
new.pan <- trellis.par.get("strip.background")
new.pan$col <- c('gray90','white')
trellis.par.set("background", new.back)
trellis.par.set("superpose.symbol", newcol)
trellis.par.set("strip.background",new.pan)
Figure 1 Code
advdat <- data.frame(REAS=c('Completed Study','Adverse Event',
'Lack of Efficacy', 'Withdrew Consent', 'Other'),
PCT=c(75, 7, 8, 6, 4))
rr <- rank(advdat$PCT)
mylayout <- layout(matrix(c(1,2,3,4), byrow=TRUE, ncol=4),
widths=c(1/9,2/9,1/3, 1/3),
heights=c(3/4,3/4,3/4,3/4))
par(mar=c(5,1,7,0))
plot(x=c(0,1), y=c(1,dim(advdat)[1]), axes=FALSE, ann=FALSE, type='n', xlab='',
ylab='')
text(x=1, y=1:dim(advdat)[1], advdat$REAS[rr], adj=c(1,NA), cex=.9,
col='black')
par(mar=c(5,0,7,1))
plot(x=advdat$PCT[rr], y=1:dim(advdat)[1], type='n', pch=16, axes=FALSE,
xlab='Percent')
abline(h=1:dim(advdat)[1], col='grey80')
box()
points(x=advdat$PCT[rr], y=1:dim(advdat)[1], pch=16, col='black')
axis(1)
mtext('Dotplot', 3, line=1, font=2)
#dotplot(reorder(REAS,PCT) ~ PCT, data=advdat)
plot(1:5,type="n",axes=FALSE)
box()
floating.pie(3,3,advdat$PCT, col=c('red','blue','green4','orange','violet'))
mtext('Pie Chart', 3, line=1, font=2)
legend(x=1, y=5, legend=advdat$REAS, fill=c('red','blue','green4','orange','violet'),
text.col=c('red','blue','green4','orange','violet'))
par(mar=c(5,2,7,1))
barplot(advdat$PCT[rr], names.arg=advdat$REAS[rr], horiz=TRUE, xlab='Percent')
mtext('Barplot', 3, line=1, font=2)
Figure 2 Code
dathigh <- subset(plotdat, TRT%in%'Xanomeline High Dose')
dotplot(PT ~ PCT, groups=TRT, data=dathigh,
xlab="Percent Reporting Event",
pch=16, col='black')
Figure 3 Code
dotplot(reorder(PT,PCT) ~ PCT, groups=TRT, data=dathigh,
xlab="Percent Reporting Event",
pch=16, col='black')
Figure 4 Code
dathigh$SOC <- c('GASTROINTESTINAL DISORDERS', 'PSYCHIATRIC DISORDERS',
'GENERAL DISORDERS AND ADMINISTRATION SITE CONDITIONS',
'GENERAL DISORDERS AND ADMINISTRATION SITE CONDITIONS',
'GENERAL DISORDERS AND ADMINISTRATION SITE CONDITIONS',
'GENERAL DISORDERS AND ADMINISTRATION SITE CONDITIONS',
'GENERAL DISORDERS AND ADMINISTRATION SITE CONDITIONS',
'CARDIAC DISORDERS','CARDIAC DISORDERS',
'MUSCULOSKELETAL AND CONNECTIVE TISSUE DISORDERS',
'SKIN AND SUBCUTANEOUS TISSUE DISORDERS',
'PSYCHIATRIC DISORDERS',
'RESPIRATORY, THORACIC AND MEDIASTINAL DISORDERS',
'GASTROINTESTINAL DISORDERS', 'NERVOUS SYSTEM DISORDERS',
'INVESTIGATIONS', 'SKIN AND SUBCUTANEOUS TISSUE DISORDERS',
'GENERAL DISORDERS AND ADMINISTRATION SITE CONDITIONS',
'NERVOUS SYSTEM DISORDERS', 'SKIN AND SUBCUTANEOUS TISSUE DISORDERS',
'CARDIAC DISORDERS', 'RESPIRATORY, THORACIC AND MEDIASTINAL DISORDERS',
'INFECTIONS AND INFESTATIONS', 'GASTROINTESTINAL DISORDERS',
'GENERAL DISORDERS AND ADMINISTRATION SITE CONDITIONS',
'SKIN AND SUBCUTANEOUS TISSUE DISORDERS',
'SKIN AND SUBCUTANEOUS TISSUE DISORDERS','CARDIAC DISORDERS',
'SKIN AND SUBCUTANEOUS TISSUE DISORDERS','NERVOUS SYSTEM DISORDERS',
'NERVOUS SYSTEM DISORDERS',
'INFECTIONS AND INFESTATIONS','GASTROINTESTINAL DISORDERS')
sortmat <- function (Mat, Sort) {
m <- do.call("order", as.data.frame(Mat[, Sort]))
Mat[m, ]
} # Used to sort a matrix
dat2 <- sortmat(dathigh, c(4,3))
tt <- table(dat2$SOC)
plotcol <- rainbow(length(tt))
dat2$color <- rep(plotcol, tt)
mylayout <- layout(matrix(c(1,2,3), byrow=TRUE, ncol=3),
widths=c(2/7,3/7, 2/7))
par(mar=c(5,1,2,0))
plot(x=c(0,1), y=c(1,dim(dat2)[1]), axes=FALSE, ann=FALSE, type='n', xlab='', ylab='')
text(x=1, y=1:dim(dat2)[1], dat2$PT, adj=c(1,NA), cex=.9,
col=dat2$color)
par(mar=c(5,0,2,2))
plot(x=dat2$PCT, y=1:dim(dat2)[1], type='n', pch=16, axes=FALSE, xlab='Percent Reporting Event')
abline(h=1:dim(dat2)[1], col='grey80')
box()
points(x=dat2$PCT, y=1:dim(dat2)[1], pch=16, col=dat2$color)
axis(1)
par(mar=c(5,0,2,1))
plot(x=c(0,1), y=c(1,dim(dat2)[1]), axes=FALSE, ann=FALSE, type='n', xlab='', ylab='')
text(x=0, y=1:dim(dat2)[1], dat2$SOC, adj=c(0,NA), cex=.85,
col=dat2$color)
Figure 5 Code
dotplot(reorder(PT,PCT) ~ PCT, groups=TRT, data=plotdat,
xlab="Percent Reporting Event",
pch=1:3,
key=list(
points=list(
col=trellis.par.get("superpose.symbol")$col[1:3],
pch=1:3),
text=list(
lab=levels(plotdat$TRT),
col=trellis.par.get('superpose.symbol')$col[1:3]),
columns=3, title='Treatment'))
Figure 6 Code
# Simulate Data
set.seed(133)
notrelated <- NULL
for(i in 1:dim(plotdat)[1]){
notrelated[i] <- plotdat$PCT[i] - runif(1, 0, plotdat$PCT[i])
}
related <- plotdat$PCT - notrelated
plotdat2 <- data.frame(PT=rep(plotdat$PT, 2),
TRT=rep(plotdat$TRT, 2),
REL=factor(rep(c('Related','Not Related'), each=dim(plotdat)[1]),
levels=c('Related','Not Related')),
PCT=c(related, notrelated))
dotplot(reorder(PT,PCT) ~ PCT|REL, groups=TRT, data=plotdat2,
xlab="Percent Reporting Event",
pch=1:3,
key=list(
points=list(
col=trellis.par.get("superpose.symbol")$col[1:3],
pch=1:3),
text=list(
lab=levels(plotdat$TRT),
col=trellis.par.get('superpose.symbol')$col[1:3]),
columns=3, title='Treatment'))
%ENDCODE%