Vasily A
Vasily A

Reputation: 8626

position_dodge() for geom_point()

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))

basic swimmers plot

Now imagine I have two values at the same timepoint, and I want to draw them alongside, to have something like this: plot I want
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: wrong
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

Answers (2)

Vasily A
Vasily A

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

Allan Cameron
Allan Cameron

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())

enter image description here

I think this is the same as your desired output plot.

Upvotes: 2

Related Questions