I have been trying to create a risk radar using plotly and now plotrix, I have encountered limitiations with both (based on my need and also my skillset with R).
With plotly I had pretty much most of the things i wanted except the deal breaker was not being able to label the radial axis (Team A, Team B etc.)
My version of this using plotrix is almost where i need to be and just need some guidance to get me over the line?
I have 4 issues:
Can the title be moved to the left or right?
The radial? labels are bleeding into the chart circle making it hard
to read, can they be adjusted somehow?
Is it possible to change the font (size and/or colour) of the labels 0/30/60/90/180?
Is there anyway to add text to the plotted point, in my case i
wanted to have the RiskID as the labels
My chart looks like this:
library(plotrix)
# Build sample dataset
aRiskID <- c(1, 15, 23, 28, 35)
bRiskDays <- as.numeric(c(28, 15, 85, 153, 100))
cTheta <- as.integer(c(20, 80, 130, 240, 320))
dConsequence <- c("Major", "Major", "Minor", "Moderate", "Minor")
myRisks <- data.frame(RiskID = aRiskID, RiskDays = bRiskDays, Theta = cTheta, CurrentConsequence = dConsequence)
myLabels <- c("Team A", "Team B", "Team C", "Team D", "Team E", "Team F", "Team G", "Team H")
# Test different point colours
# initializing vector of colors as NA
colors_plot <- rep(NA,length(myRisks))
# set of conditions listed in the plot
colors_plot[myRisks$CurrentConsequence == "Major"] <- "black"
colors_plot[myRisks$CurrentConsequence == "Moderate"] <- "red"
colors_plot[myRisks$CurrentConsequence == "Minor"] <- "green"
# add more conditions as needed
# par(mar=c(2,5,5,5))
# plot the chart
radial.plot(myRisks$RiskDays,
myRisks$Theta,
start = pi/2,
clockwise = FALSE,
# start=pi/2,clockwise=TRUE,
show.grid.labels=1,
rp.type="s",
main="Risk Radar",
radial.lim=c(0,30,60,90,180),
radlab = TRUE,
point.symbols=17,
point.col=colors_plot,
cex = 2,
cex.axis = 0.25,
cex.lab = 0.25,
lwd=par("lwd"),mar=c(2,2,3,2),
# show.centroid=TRUE,
labels=myLabels)
I don't know where else to go with this and so any tips using plotrix or another charting package to achieve the end result would be great.
You should look at the functions radial.plot.labels and radial.grid
# plot the chart
radial.plot(myRisks$RiskDays,
myRisks$Theta,
start = pi/2,
clockwise = FALSE,
# start=pi/2,clockwise=TRUE,
show.grid.labels=1,
rp.type="s",
# main="Risk Radar",
radial.lim=c(0,30,60,90,180),
radial.labels = '',
radlab = TRUE,
point.symbols=17,
point.col=colors_plot,
cex = 2,
cex.axis = 0.25,
cex.lab = 0.25,
lwd=par("lwd"),mar=c(2,2,3,2),
# show.centroid=TRUE,
labels=NULL, label.pos = pi / 4 * 2:9)
# 1
mtext("Risk Radar", at = par('usr')[1], font = 2)
# 2
at <- c(0,30,60,90,180)
radial.plot.labels(max(at) + 35, pi / 4 * 2:9, labels = myLabels, radial.lim = at)
# 3
radial.plot.labels(at, pi / 2 * 3, labels = at, col = 1:5, cex = 1.5)
# 4
radial.plot.labels(myRisks$RiskDays, myRisks$Theta, start = pi/2,
clockwise = FALSE, labels = myRisks$RiskID)
If you really need perpendicular labels, you can use the radial.grid function or loop over the labels with separate rotations (srt). It's a real shame that srt isn't vectorized in text, it would make this a lot easier
th <- pi / 4 * 2:9
sapply(seq_along(th), function(ii) {
i <- ifelse((th[ii] > pi / 2) & (th[ii] < pi / 2 * 3), pi, 0)
radial.plot.labels(max(at) + 35, th[ii], labels = myLabels[ii],
radial.lim = at, srt = (th[ii] - i) * 180 / pi)
})
I accidentally made this lovely snowflake #accidental__aRt:
th <- pi / 4 * 2:9
sapply(th, function(x)
radial.plot.labels(max(at) + 35, pi / 4 * 2:9, labels = myLabels,
radial.lim = at, srt = x * 180 / pi))
Related
To expand upon visualize a list of colors/palette in R I am trying to display a series of custom colour palettes in R in a single figure. Is there a way that I can expand on one of the methods listed in the link to display the list of palettes below:
convert_coolers <- function(coolers_string){
strsplit(coolers_string, split = ", ")[[1]]
}
# diverging
storm_panels <- convert_coolers("#001219, #005f73, #0a9396, #94d2bd, #e9d8a6, #ee9b00, #ca6702, #bb3e03, #ae2012, #9b2226")
harry_tipper <- convert_coolers("#f72585, #b5179e, #7209b7, #560bad, #480ca8, #3a0ca3, #3f37c9, #4361ee, #4895ef, #4cc9f0")
firepit <- convert_coolers("#03071e, #370617, #6a040f, #9d0208, #d00000, #dc2f02, #e85d04, #f48c06, #faa307, #ffba08")
# sequences
the_deep <- convert_coolers("#03045e, #023e8a, #0077b6, #0096c7, #00b4d8, #48cae4, #90e0ef, #ade8f4, #caf0f8")
earth <- convert_coolers("#ede0d4, #e6ccb2, #ddb892, #b08968, #7f5539, #9c6644")
# categorical
pastal_rainbow <- convert_coolers("#ff595e, #ffca3a, #8ac926, #1982c4, #6a4c93")
fisherman <- convert_coolers("#353535, #3c6e71, #ffffff, #d9d9d9, #284b63")
in a figure resembling that displayed by RColorBrewer::display.brewer.all()? i.e. with palettes stacked as horizontal bars labelled to the left with the palette title.
I have been trying to dissect the method out from the RColorBrewer function but am finding that it depends too much on internal variables for me to understand what is going on.
I achieved what I set out to do by modifying RColorBrewer::display.brewer.all
Following directly on from the code in the question:
display_custom_palettes <- function(palette_list, palette_names){
nr <- length(palette_list)
nc <- max(lengths(palette_list))
ylim <- c(0, nr)
oldpar <- par(mgp = c(2, 0.25, 0))
on.exit(par(oldpar))
plot(1, 1, xlim = c(0, nc), ylim = ylim, type = "n", axes = FALSE,
bty = "n", xlab = "", ylab = "")
for (i in 1:nr) {
nj <- length(palette_list[[i]])
shadi <- palette_list[[i]]
rect(xleft = 0:(nj - 1), ybottom = i - 1, xright = 1:nj,
ytop = i - 0.2, col = shadi, border = "light grey")
}
text(rep(-0.1, nr), (1:nr) - 0.6, labels = palette_names, xpd = TRUE,
adj = 1)
}
plot.new()
palette_list <- list(storm_panels, harry_tipper, firepit, the_deep, earth, pastal_rainbow, fisherman)
palette_names <- c("storm panels", "harry tipper", "firepit", "the deep", "earth", "rainbow", "fisherman")
display_custom_palettes(palette_list, palette_names)
This question already has answers here:
Shading a kernel density plot between two points.
(5 answers)
Closed 7 years ago.
I've written code to plot density data for variations of an A/B test. I'd like to improve the visual by shading (with the fill being slightly transparent) the area below each curve. I'm currently using matplot, but understand ggplot might be a better option.
Any ideas? Thanks.
# Setup data frame - these are results from an A/B experiment
conv_data = data.frame(
VarNames = c("Variation 1", "Variation 2", "Variation 3") # Set variation names
,NumSuccess = c(1,90,899) # Set number of successes / conversions
,NumTrials = c(10,100,1070) # Set number of trials
)
conv_data$NumFailures = conv_data$NumTrials - conv_data$NumSuccess # Set number of failures [no conversions]
num_var = NROW(conv_data) # Set total number of variations
plot_col = rainbow(num_var) # Set plot colors
get_density_data <- function(n_var, s, f) {
x = seq(0,1,length.out=100) # 0.01,0.02,0.03...1
dens_data = matrix(data = NA, nrow=length(x), ncol=(n_var+1))
dens_data[,1] = x
# set density data
for(j in 1:n_var) {
# +1 to s[], f[] to ensure uniform prior
dens_data[,j+1] = dbeta(x, s[j]+1, f[j]+1)
}
return(dens_data)
}
density_data = get_density_data(num_var, conv_data$NumSuccess, conv_data$NumFailures)
matplot(density_data[,1]*100, density_data[,-1], type = "l", lty = 1, col = plot_col, ylab = "Probability Density", xlab = "Conversion Rate %", yaxt = "n")
legend("topleft", col=plot_col, legend = conv_data$VarNames, lwd = 1)
This produces the following plot:
# Setup data frame - these are results from an A/B experiment
conv_data = data.frame(
VarNames = c("Variation 1", "Variation 2", "Variation 3") # Set variation names
,NumSuccess = c(1,90,899) # Set number of successes / conversions
,NumTrials = c(10,100,1070) # Set number of trials
)
conv_data$NumFailures = conv_data$NumTrials - conv_data$NumSuccess # Set number of failures [no conversions]
num_var = NROW(conv_data) # Set total number of variations
plot_col = rainbow(num_var) # Set plot colors
get_density_data <- function(n_var, s, f) {
x = seq(0,1,length.out=100) # 0.01,0.02,0.03...1
dens_data = matrix(data = NA, nrow=length(x), ncol=(n_var+1))
dens_data[,1] = x
# set density data
for(j in 1:n_var) {
# +1 to s[], f[] to ensure uniform prior
dens_data[,j+1] = dbeta(x, s[j]+1, f[j]+1)
}
return(dens_data)
}
density_data = get_density_data(num_var, conv_data$NumSuccess, conv_data$NumFailures)
matplot(density_data[,1]*100, density_data[,-1], type = "l",
lty = 1, col = plot_col, ylab = "Probability Density",
xlab = "Conversion Rate %", yaxt = "n")
legend("topleft", col=plot_col, legend = conv_data$VarNames, lwd = 1)
## and add this part
for (ii in seq_along(plot_col))
polygon(c(density_data[, 1] * 100, rev(density_data[, 1] * 100)),
c(density_data[, ii + 1], rep(0, nrow(density_data))),
col = adjustcolor(plot_col[ii], alpha.f = .25))
Was able to answer own question with:
df = as.data.frame(t(conversion_data))
dfs = stack(df)
ggplot(dfs, aes(x=values)) + geom_density(aes(group=ind, colour=ind, fill=ind), alpha=0.3)
Please consider the following sample polar plot:
library(plotrix)
testlen <- c(rnorm(36)*2 + 5)
testpos <- seq(0, 350, by = 10)
polar.plot(testlen, testpos, main = "Test Polar Plot",
lwd = 3, line.col = 4, rp.type = "s")
I would like to add lines at angles 30 and 330 as well as 150 and 210 (from the center to the outside). I experimented with the line function but could not get it to work.
The calculations for exact placement are a bit goofy but using your test data
set.seed(15)
testlen<-c(rnorm(36)*2+5)
testpos<-seq(0,350,by=10)
polar.plot(testlen,testpos,main="Test Polar Plot",
lwd=3,line.col=4,rp.type="s")
You can add lines at 20,150,210,300 with
add.line <- c(30,330, 150,210)/360*2*pi
maxlength <- max(pretty(range(testlen)))-min(testlen)
segments(0, 0, cos(add.line) * maxlength, sin(add.line) * maxlength,
col = "red")
And that makes the following plot
You can just use the rp.type = "r" argument and add = TRUE. So, something like
library(plotrix)
set.seed(1)
testlen <- c(rnorm(36)*2 + 5)
testpos <- seq(0,350, by = 10)
polar.plot(testlen, testpos, main = "Test Polar Plot",
lwd = 3, line.col = 4, rp.type = "s")
followed by
pos <- c(30, 330, 150, 210)
len <- c(10, 10, 10, 10)
polar.plot(lengths = len, polar.pos = pos,
radial.lim = c(0, 15),
lwd = 2, line.col = 2, rp.type = "r", add = TRUE)
yields your desired output.
I work in a laboratory and we have to produce day to day Levey-Jennings charts and I was wondering if there is an easy way produce Levey-Jennings chart using R.
Well, I googled and didn't find one on CRAN, but maybe Levey-Jennings charts also go by another name? Anyway, here's a low tech one that you can tweak that I just made following the description on Wikipedia:
# make a data series
my.stat <- rnorm(100,sd=2.5)
# get its standard dev:
my.sd <- sd(my.stat)
# convert series to distance in sd:
my.lj.stat <- (my.stat - mean(my.stat)) / my.sd
plot(1:100, my.lj.stat, type = "o", pch = 19, col = "blue", ylab = "sd", xlab = "observation",
main = paste("mean value of", round(mean(my.stat),3),"\nstandard deviation of",round(my.sd,3)))
# a low tech L-J chart function:
LJchart <- function(series, ...){
xbar <- mean(series)
se <- sd(series)
conv.series <- (my.stat - xbar) / se
plot(1:length(series), conv.series, type = "o", pch = 19, col = "blue", ylab = "sd", xlab = "observation",
main = paste("mean value of", round(xbar,3), "\nstandard deviation of", round(se,3)), ...)
}
LJchart(rnorm(100,sd=2.5))
[Edit: adding a shaded region for the 1 sd zone, inspired by Seth's comment]
This one also has more flexible args I guess, but I'm not too experienced with the use of ... when different functions share the ..., but trying it out with this example it doesn't break:
LJchart <- function(series, ...){
xbar <- mean(series)
se <- sd(series)
conv.series <- (my.stat - xbar) / se
plot(1:length(series), conv.series, type = "n", ...)
rect(0, -1, length(series)+1, 1, col = gray(.9), border = NA)
lines(1:length(series), conv.series, ...)
points(1:length(series), conv.series, ...)
if (! "main" %in% names(list(...))) {
title(paste("mean value of", round(xbar,3), "\nstandard deviation of", round(se,3)))
}
}
LJchart(rnorm(100,sd=2.5), xlab = "observations", ylab = "sd", col = "blue", pch = 19)
For plotting I prefer ggplot2 over standard graphics. Therefore, here is my solution using ggplot2:
theme_set(theme_bw())
dat = data.frame(value = rnorm(100,sd=2.5))
dat = within(dat, {
value_scaled = scale(value, scale = sd(value))
obs_idx = 1:length(value)
})
ggplot(aes(x = obs_idx, y = value_scaled), data = dat) +
geom_ribbon(ymin = -1, ymax = 1, alpha = 0.1) +
geom_line() + geom_point()
Which yields:
For the uninitiated: Levey-Jenning's chart is a chart used to manage quality-control samples, especially in a medical laboratory. The Y axis is no. of SDs, and X axis should be timestamps.
Modified from Tim Riffe's answer from above. This should be more suited for laboratory use.
# LJchart
# modified from Tim Riffe's answer on StackOverflow
#
# Version history:
# 1.1 Added support for timestamp on each datapoint
# Added rectangle to delineate the 2SD boundary, limited the scope to 3 SD
#
# Usage:
# LJchart( [Series of values], [Series of timestamp], [Manufacturer set mean], [Manufacturer set SD] )
# e.g.
# creatinineLV1 <- c(52, 51, 48, 51, 42, 48, 46, 44, 45, 51, 51,
# 46, 50, 45, 52, 41, 58, 45, 44, 44, 42, 47,
# 45, 43, 48, 43, 47, 47, 48)
# timeCRLV1 <- c(41267.41106, 41267.51615, 41267.64512, 41267.683,
# 41268.32005, 41269.55979, 41269.62026, 41269.88109,
# 41270.20442, 41270.5897, 41270.61914, 41270.66589,
# 41270.76311, 41271.43517, 41271.58534, 41271.69562,
# 41271.75682, 41272.43492, 41272.51768, 41272.53,
# 41272.59527, 41273.38759, 41273.46314, 41273.49382,
# 41273.6311, 41273.66563, 41273.78007, 41273.82463,
# 41273.88547)
# > LJchart(creatinineLV1, timeCRLV1, 50, 6)
LJchart <- function(series1, series2, meanx, sdx){
xbar <- mean(series1)
se <- sd(series1)
conv.series <- (series1 - meanx) / sdx
plot(series2, conv.series, type = "n", ylim=c(-3,+3))
rect(0, -2, max(series2)+1, 2, col = gray(.9), border = NA)
rect(0, -1, max(series2)+1, 1, col = gray(.8), border = NA)
lines(series2, conv.series)
points(series2, conv.series)
title(paste("calculated mean value of", round(xbar,3),
"\ncalculated standard deviation of", round(se,3)))
}
I'm working on developing some scripts for this type of chart>
Check the script. The main data in "value" vector.
All comments "##/#" may be erased.
value<-rnorm(100,1000,200) ##create list of numbers, "scan()" may be used for real observations
nmbrs<-length(value) ## determine the length of vector
obrv<-1:length(value) ## create list of observations
par(xpd=FALSE)
sd1<-sd(value[1:20])*1 ## 1 standart deviation
sd2<-sd(value[1:20])*2 ## 2 standart deviations
sd3<-sd(value[1:20])*3 ## 3 standart deviations
usd1<-mean(value)+sd1 ## upper limit
lsd1<-mean(value)-sd1 ## lower limit
lsd2<-mean(value)-sd2 ## lower limit
usd2<-mean(value)+sd2 ## upper limit
usd3<-mean(value)+sd3 ## upper limit
lsd3<-mean(value)-sd3 ## lower limit
## ploting the grid
plot(obrv,value,type="n",xlab="Observations",ylab="Value",ylim=c(lsd3-sd1,usd3+sd1))
abline(h=mean(value),col=2,lty=1)
abline(h=usd1,col=3,lty=3)
abline(h=lsd1,col=3,lty=3)
abline(h=usd2,col=4,lty=2)
abline(h=lsd2,col=4,lty=2)
abline(h=usd3,col=6,lty=1)
abline(h=lsd3,col=6,lty=1)
## 20 first values for L-G chart for QC limits
for (i in 1:20)
{
points(obrv[i],value[i],col="black")
}
lines(obrv[1:20],value[1:20],col="red")
## if over mean - "red", under mean - "blue"
for (i in 21:nmbrs)
{
points(obrv[i],value[i],col="blue")
segments(obrv[i-1],value[i-1],obrv[i],value[i],col="blue")
}
# 1s points - blue; 2s points - red
#if (value[i]<usd1 || value[i]>lsd1) points(obrv[i],value[i],col="blue")
#if (value[i]>usd1 || value[i]<lsd1) points(obrv[i],value[i],col="red")
#12s violation rule
#if (value[i]>usd1 || value[i]<usd1) text(30, usd3, "12s violation")
#if (value[i]>usd1 || value[i]<usd1) text(30, usd3, "12s violation")
#segments(obrv[i-1],value[i-1],obrv[i],value[i],col="blue")
#if (value[i]>usd1) break
#}
#legend placement - might be omited
#legend(1,min(value)-sd1*0.2,bg=8,c("mean","sd1","sd2","sd3"),lty=c(1,3,2,1),lwd=c(2.5,2.5,2.5,2.5),col=c(2,3,4,6),cex=0.8)
I have a data set of item difficulties that correspond to items on a questionnaire that looks like this:
## item difficulty
## 1 ITEM_01_A 2.31179818
## 2 ITEM_02_B 1.95215238
## 3 ITEM_03_C 1.93479536
## 4 ITEM_04_D 1.62610855
## 5 ITEM_05_E 1.62188759
## 6 ITEM_06_F 1.45137544
## 7 ITEM_07_G 0.94255210
## 8 ITEM_08_H 0.89941812
## 9 ITEM_09_I 0.72752197
## 10 ITEM_10_J 0.61792597
## 11 ITEM_11_K 0.61288399
## 12 ITEM_12_L 0.39947791
## 13 ITEM_13_M 0.32209970
## 14 ITEM_14_N 0.31707701
## 15 ITEM_15_O 0.20902108
## 16 ITEM_16_P 0.19923607
## 17 ITEM_17_Q 0.06023317
## 18 ITEM_18_R -0.31155481
## 19 ITEM_19_S -0.67777282
## 20 ITEM_20_T -1.15013758
I want to make an item map of these items that looks similar (not exactly) to this (I created this in word but it lacks true scaling as I just eyeballed the scale). It's not really a traditional statistical graphic and so I don't really know how to approach this. I don't care what graphics system this is done in but I am more familiar with ggplot2 and base.
I would greatly appreciate a method of plotting this sort of unusual plot.
Here's the data set (I'm including it as I was having difficulty using read.table on the dataframe above):
DF <- structure(list(item = c("ITEM_01_A", "ITEM_02_B", "ITEM_03_C",
"ITEM_04_D", "ITEM_05_E", "ITEM_06_F", "ITEM_07_G", "ITEM_08_H",
"ITEM_09_I", "ITEM_10_J", "ITEM_11_K", "ITEM_12_L", "ITEM_13_M",
"ITEM_14_N", "ITEM_15_O", "ITEM_16_P", "ITEM_17_Q", "ITEM_18_R",
"ITEM_19_S", "ITEM_20_T"), difficulty = c(2.31179818110545, 1.95215237740899,
1.93479536058926, 1.62610855327073, 1.62188759115818, 1.45137543733965,
0.942552101641177, 0.899418119889782, 0.7275219669431, 0.617925967008653,
0.612883990709181, 0.399477905189577, 0.322099696946661, 0.31707700560997,
0.209021078266059, 0.199236065264793, 0.0602331732900628, -0.311554806052955,
-0.677772822413495, -1.15013757942119)), .Names = c("item", "difficulty"
), row.names = c(NA, -20L), class = "data.frame")
Thank you in advance.
Here is a quick example:
ggplot(DF, aes(x=1, y=difficulty, label = item)) +
geom_text(size = 3) +
scale_y_continuous(breaks = DF$difficulty, minor_breaks = NULL, labels = sprintf("%.02f", DF$difficulty)) +
scale_x_continuous(breaks = NULL) +
opts(panel.grid.major = theme_blank())
but sometimes two items are too narrow so overlapped. You may do like this:
m <- 0.1
nd <- diff(rev(DF$difficulty))
nd <- c(0, cumsum(ifelse(nd < m, m, nd)))
DF$nd <- rev(rev(DF$difficulty)[1] + nd)
ggplot(DF, aes(x=1, y=nd, label = item)) +
geom_text(size = 3) +
scale_y_continuous(breaks = DF$nd, labels = sprintf("%.02f", DF$difficulty), DF$difficulty, minor_breaks = NULL) +
scale_x_continuous(breaks = NULL) +
opts(panel.grid.major = theme_blank())
Here is a solution with base graphics.
# Compute the position of the labels to limit overlaps:
# move them as little as possible, but keep them
# at least .1 units apart.
library(quadprog)
spread <- function(b, eps=.1) {
stopifnot(b == sort(b))
n <- length(b)
Dmat <- diag(n)
dvec <- b
Amat <- matrix(0,nr=n,nc=n-1)
Amat[cbind(1:(n-1), 1:(n-1))] <- -1
Amat[cbind(2:n, 1:(n-1))] <- 1
bvec <- rep(eps,n-1)
r <- solve.QP(Dmat, dvec, Amat, bvec)
r$solution
}
DF <- DF[ order(DF$difficulty), ]
DF$position <- spread(DF$difficulty, .1)
ylim <- range(DF$difficulty)
plot( NA,
xlim = c(.5,2),
ylim = ylim + .1*c(-1,1)*diff(ylim),
axes=FALSE, xlab="", ylab=""
)
text(.9, DF$position, labels=round(DF$difficulty,3), adj=c(1,0))
text(1.1, DF$position, labels=DF$item, adj=c(0,0))
arrows(1,min(DF$position),1,max(DF$position),code=3)
text(1,min(DF$position),labels="Easier",adj=c(.5,2))
text(1,max(DF$position),labels="More difficult",adj=c(.5,-1))
text(.9, max(DF$position),labels="Difficulty",adj=c(1,-2))
text(1.1,max(DF$position),labels="Item", adj=c(0,-2))
My own attempt but I think I'm going to like Vincent's solution much better as it looks like my original specification.
DF <- DF[order(DF$difficulty), ]
par(mar=c(1, 1, 3, 0)+.4)
plot(rep(1:2, each=10), DF$difficulty, main = "Item Map ",
ylim = c(max(DF$difficulty)+1, min(DF$difficulty)-.2),
type = "n", xlab="", ylab="", axes=F, xaxs="i")
text(rep(1.55, 20), rev(DF$difficulty[c(T, F)]),
DF$item[c(F, T)], cex=.5, pos = 4)
text(rep(1, 20), rev(DF$difficulty[c(F, T)]),
DF$item[c(T, F)], cex=.5, pos = 4)
par(mar=c(0, 0, 0,0))
arrows(1.45, 2.45, 1.45, -1.29, .1, code=3)
text(rep(1.52, 20), DF$difficulty[c(T, F)],
rev(round(DF$difficulty, 2))[c(T, F)], cex=.5, pos = 2)
text(rep(1.44, 20), DF$difficulty[c(F, T)],
rev(round(DF$difficulty, 2))[c(F, T)], cex=.5, pos = 2)
text(1.455, .5, "DIFFICULTY", cex=1, srt = -90)
text(1.45, -1.375, "More Difficult", cex=.6)
text(1.45, 2.5, "Easier", cex=.6)
par(mar=c(0, 0, 0,0))