Offset Geom_Segment in Ggplot

how to jitter/dodge geom_segments so they remain parallel?

As far as I know, geom_segment does not allow jittering nor dodging. You can add jittering to the relevant variable in the data frame, then plot the jittered variable. In your example, the factor is converted to numeric, then the labels for the levels of the factor are added to the axis using scale_y_continuous.

library(ggplot2)
iris$JitterSpecies <- ave(as.numeric(iris$Species), iris$Species,
FUN = function(x) x + rnorm(length(x), sd = .1))

ggplot(iris, aes(x = Petal.Length, xend = Petal.Width,
y = JitterSpecies, yend = JitterSpecies)) +
geom_segment()+
geom_point(aes(size=Sepal.Length, shape=Species)) +
scale_y_continuous("Species", breaks = c(1,2,3), labels = levels(iris$Species))

Sample Image

But it seems geom_linerange allows dodging.

ggplot(iris, aes(y = Petal.Length, ymin = Petal.Width,
x = Species, ymax = Petal.Length, group = row.names(iris))) +
geom_point(position = position_dodge(.5)) +
geom_linerange(position = position_dodge(.5)) +
coord_flip()

Sample Image

How to draw directional spider network in geom_segment/ggplot2 in R?

You could use trig functions to calculate an offset value, then plug this into the ggplot() call. Below is an example using your dataset above. I'm not exactly sure what you mean by clockwise, so I put in a simple dummy variable.

# make a dummy "clockwise" variable for now
df$clockwise = df$O > df$D
# angle from coordinates of stations
df$angle = atan((df$lat.y - df$lat.x)/(df$lon.y - df$lon.x))
# offsets from cos/sin of orthogonal angle
# scale the distance of the offsets by the trip size so wider bars offset more
# offset them one way if the trip is clockwise, the other way if not clockwise
df$xoffset = cos(df$angle - pi/2) * df$trip/5 * (2 * df$clockwise - 1)
df$yoffset = sin(df$angle - pi/2) * df$trip/5 * (2 * df$clockwise - 1)

ggplot() +
geom_segment(data = df, aes(x = lon.x + xoffset, y = lat.x + yoffset, xend = lon.y + xoffset, yend = lat.y + yoffset, size = trip, color = clockwise),
alpha = 0.5, show.legend = TRUE) +
scale_size_continuous(range = c(0, 5), breaks = c(300, 600, 900, 1200),
limits = c(100, 1200), name = "Person trips/day (over 100 trips)") +
theme(legend.key = element_rect(colour = "transparent", fill = alpha("black", 0))) +
guides(size = guide_legend(override.aes = list(alpha = 1.0))) +
geom_point(data = df, aes(x = lon.x, y = lat.x), pch = 16, size = 2.4) +
coord_fixed()

Sample plot with above code

Set variable position for geom_segment based on the input data

filter the data for your geom_segment layer (you only need one):

tibble("X"=rep(c("Reference.A", "1.A", "2.A", "Reference.B", "1.B", "2.B"),3), 
"Decision"=c(rep("Type 1",6),rep("Type 2",6),rep("Type 3",6)),
"Outcome"=c(rnorm(n=6,mean=50,sd=5),rnorm(n=6,mean=30,sd=5),rnorm(n=6,mean=20,sd=5))) %>%
ggplot(. , aes(X, Outcome, color=Decision, shape=Decision, size=2)) +
geom_point(stroke=2, alpha = 0.8) +
scale_x_discrete(limits=c("Reference.A", "1.A", "2.A", "Reference.B", "1.B", "2.B")) +
geom_segment(
aes(x = c(1, 4), y = Outcome, xend = c(3, 6), yend = Outcome),
data = . %>% filter(Decision == 'Type 1', X %in% c('Reference.A', 'Reference.B')),
color="black", linetype="dashed", size=1
)

This uses the . %>% trick to define a functional sequence, since you need to pass a function as the data argument because you pipe in your data.

Alternatively:

d <- tibble("X"=rep(c("Reference.A", "1.A", "2.A", "Reference.B", "1.B", "2.B"),3), 
"Decision"=c(rep("Type 1",6),rep("Type 2",6),rep("Type 3",6)),
"Outcome"=c(rnorm(n=6,mean=50,sd=5),rnorm(n=6,mean=30,sd=5),rnorm(n=6,mean=20,sd=5)))

ggplot(d , aes(X, Outcome, color=Decision, shape=Decision, size=2)) +
geom_point(stroke=2, alpha = 0.8) +
scale_x_discrete(limits=c("Reference.A", "1.A", "2.A", "Reference.B", "1.B", "2.B")) +
geom_segment(
aes(x = c(1, 4), y = Outcome, xend = c(3, 6), yend = Outcome),
data = filter(d, Decision == 'Type 1', X %in% c('Reference.A', 'Reference.B')),
color="black", linetype="dashed", size=1
)

Plotting geom_segment with position_dodge

If you include colour = workplace in the aes() mapping for geom_segment you get colours and a legend and some dodging, but it doesn't work quite right (it looks like position_dodge only applies to x and not xend ... ? this seems like a bug, or at least an "infelicity", in position_dodge ...

However, replacing geom_segment with an appropriate use of geom_linerange does seem to work:

ggplot(mydata_ym) +
geom_linerange(aes(x = id, ymin = ymd_start, ymax = ymd_end, colour = workplace),
position = position_dodge(width = 0.1), size = 2) +
scale_x_discrete(limits = rev) +
coord_flip()

(some tangential components omitted).

A similar approach is previously documented here — a near-duplicate of your question once the colour= mapping is taken care of ...

Slightly change geom_segment's position of x only, but keep position of xend constant

Here's an approach:

segments <- data.frame(seg = rep(c(1:2), each = 4),
x = c(0.8, 0.8, 3, 3, 1.2, 1.2, 2, 2),
y = c(as.numeric(demoData[1,2]), 450,
450, as.numeric(demoData[3,2]),
as.numeric(demoData[1,2]), 425,
425, as.numeric(demoData[2,2])))

ggplot() +
geom_path(data = segments, aes(x, y, group = seg), arrow = arrow()) +
geom_col(data = demoData,
aes(x = as.numeric(factor(demoData$priming,
levels = demoData$priming)), rt)) +
scale_x_continuous(breaks = 1:3, labels = demoData$priming)

Sample Image

how to prevent an overlapped segments in geom_segment

Your geom_segment call isn't using any aesthetic mapping, which is how you normally get ggplot elements to change position based on a particular variable (or set of variables).

The stacking of the geom_segment based on the number of overlapping regions is best calculated ahead of the call to ggplot. This allows you to pass the x and y values into an aesthetic mapping:

# First ensure that the data feame is ordered by the start time
lines <- lines[order(lines$from),]

# Now iterate through each row, calculating how many previous rows have
# earlier starts but haven't yet finished when the current row starts.
# Multiply this number by a small negative offset and add the 1.48 baseline value
lines$offset <- 1.48 - 0.03 * sapply(seq(nrow(lines)), function(i) {
with(lines[seq(i),], length(which(from < from[i] & to > from[i])))
})

Now do the same plot but using aesthetic mapping inside geom_segment:

 ggplot() +
scale_x_continuous(breaks = seq(1,16), name = "") +
scale_y_continuous(limits = c(1, 2.2), name = "") +
geom_hline(yintercept = 1.6,
size = 20,
alpha = 0.1) +
geom_rect(
data = regions,
mapping = aes(
xmin = start,
xmax = end,
ymin = 1.5,
ymax = 1.8,
)) +
geom_segment(
data = lines,
mapping = aes(
x = from,
xend = to,
y = offset,
yend = offset),
colour = "red",
size = 3
) +
theme_minimal()

Sample Image

Grouped data by factor with geom_segment

I'm a little uncertain that @alistaire and I are communicating this clearly to you, so here's what we mean:

mydata = data.frame(variable = factor(c("A","A","A","B","C")),
color = factor(c(1,2,3,4,5)),
start = c(1,2,1,4,6),
end = c(3,4,6,5,8))

ggplot(mydata, aes(ymin = start, ymax = end, x = variable)) +
geom_linerange(aes(color = color),position = position_dodge(width = 0.2), size = 3) +
coord_flip()

Which results in:

Sample Image



Related Topics



Leave a reply



Submit