Reputation: 8626
I am building a so-called swimmers plot, with continuous X axis, categorical Y axis, and different point-type elements:
require(data.table)
require(ggplot2)
dt1 <- fread('
patient Time Result
patientA 0 Negative
patientA 2 Negative
patientA 4 Positive
patientB 1 Positive
patientB 7 Positive
patientC -1 Positive
patientC 2 Negative
patientC 3 Negative
patientC 6 Negative
')
ggplot(dt1, aes(x=Time, y=patient))+
theme(panel.grid=element_blank())+
geom_segment(aes(x=xmin,xend=xmax,yend=patient), dt1[,.(xmin=min(Time),xmax=max(Time)),by=patient] )+
geom_point(aes(shape=Result),size=3, fill='white') +
scale_shape_manual(values=c(21,19))
Now imagine I have two values at the same timepoint, and I want to draw them alongside, to have something like this:
I'm not sure what's a good way to achieve that. So far I tried adding position_dodge()
but it results to the following:
Here's the data and code I used for the latter one:
dt1 <- fread('
patient Time Result
patientA 0 Positive
patientA 0 Negative
patientA 2 Negative
patientA 4 Positive
patientB 1 Positive
patientB 7 Positive
patientC -1 Positive
patientC 2 Negative
patientC 2 Negative
patientC 3 Negative
patientC 6 Negative
patientC 6 Positive
')
ggplot(dt1, aes(x=Time, y=patient))+
theme(panel.grid=element_blank())+
geom_segment(aes(x=xmin,xend=xmax,yend=patient), dt1[,.(xmin=min(Time),xmax=max(Time)),by=patient] )+
geom_point(aes(shape=Result),size=3,fill='white',position = position_dodge(width = 0.25)) +
scale_shape_manual(values=c(21,19))
Upvotes: 1
Views: 1754
Reputation: 8626
just a variation of @AllanCameron's solution - adds one more column, but maybe looks a bit more transparent, and more universal (can handle more than 2 points!). I introduce helper scaling function scaleInt()
that transforms numbers to 0-centered integer vector (for example 1,2
is transformed to -1,1
; 1,2,3
- to -1,0,1
, etc). Then just pass scaled position as a group
aesthetics:
scaleInt <- function(x){
if (length(x)==1) return(0)
scaled <- scale(x)[,1]
ceiling(abs(scaled))*sign(scaled) # <- can this be done simpler?
}
dt1[, tpG:=scaleInt(seq_len(.N)), by=.(patient,Time)]
ggplot(dt1, aes(x=Time, y=patient)) +
geom_segment(aes(x=xmin, xend=xmax, yend=patient), dt1[,.(xmin=min(Time), xmax=max(Time)), by=patient]) +
geom_point(aes(shape=Result, group=tpG), size=3, fill='white', position=position_dodge(width=0.2)) +
scale_shape_manual(values = c(21, 19)) +
theme(panel.grid = element_blank())
Upvotes: 1
Reputation: 173803
Here's a rather contorted way of getting it done by labelling points according to whether they are duplicated, then using that as a grouping variable for the dodge.
ggplot(dt1, aes(x = Time, y = patient)) +
geom_segment(aes(x = xmin, xend = xmax, yend = patient),
dt1[,.(xmin = min(Time), xmax = max(Time)), by = patient] ) +
geom_point(aes(shape = Result, group =
factor(paste(rev(duplicated(rev(interaction(patient, Time)))),
duplicated(interaction(patient, Time))),
levels = c("FALSE TRUE", "FALSE FALSE", "TRUE FALSE"))),
size = 3, fill = 'white',
position = position_dodge(width = 0.1)) +
scale_shape_manual(values = c(21, 19)) +
theme(panel.grid = element_blank())
I think this is the same as your desired output plot.
Upvotes: 2