Different Colours of Geom_Line Above and Below a Specific Value

Different colours of geom_line above and below a specific value

You have at least a couple of options here. The first is quite simple, general (in that it's not limited to straight-line segments) and precise, but uses base plot rather than ggplot. The second uses ggplot, but is slightly more complicated, and colour transition will not be 100% precise (but near enough, as long as you specify an appropriate resolution... read on).

base:

If you're willing to use base plotting functions rather than ggplot, you could clip the plotting region to above the threshold (2.2), then plot the segments in your preferred colour, and subsequently clip to the region below the threshold, and plot again in red. While the first clip is strictly unnecessary, it prevents overplotting different colours, which can look a bit dud.

threshold <- 2.2
set.seed(123)
stackOne=data.frame(id=rep(c(1,2,3),each=3),
y=rnorm(9,2,1),
x=rep(c(1,2,3),3))
# create a second df to hold segment data
d <- stackOne
d$y2 <- c(d$y[-1], NA)
d$x2 <- c(d$x[-1], NA)
d <- d[-findInterval(unique(d$id), d$id), ] # remove last row for each group

plot(stackOne[, 3:2], pch=20)
# clip to region above the threshold
clip(min(stackOne$x), max(stackOne$x), threshold, max(stackOne$y))
segments(d$x, d$y, d$x2, d$y2, lwd=2)
# clip to region below the threshold
clip(min(stackOne$x), max(stackOne$x), min(stackOne$y), threshold)
segments(d$x, d$y, d$x2, d$y2, lwd=2, col='red')
points(stackOne[, 3:2], pch=20) # plot points again so they lie over lines

base package, changing line colours

ggplot:

If you want or need to use ggplot, you can consider the following...

One solution is to use geom_line(aes(group=id, color = y < 2.2)), however this will assign colours based on the y-value of the point at the beginning of each segment. I believe you want to have the colour change not just at the nodes, but wherever a line crosses your given threshold of 2.2. I'm not all that familiar with ggplot, but one way to achieve this is to make a higher-resolution version of your data by creating new points along the lines that connect your existing points, and then use the color = y < 2.2 argument to achieve the desired effect.

For example:

threshold <- 2.2 # set colour-transition threshold
yres <- 0.01 # y-resolution (accuracy of colour change location)

d <- stackOne # for code simplification
# new cols for point coordinates of line end
d$y2 <- c(d$y[-1], NA)
d$x2 <- c(d$x[-1], NA)
d <- d[-findInterval(unique(d$id), d$id), ] # remove last row for each group
# new high-resolution y coordinates between each pair within each group
y.new <- apply(d, 1, function(x) {
seq(x['y'], x['y2'], yres*sign(x['y2'] - x['y']))
})
d$len <- sapply(y.new, length) # length of each series of points
# new high-resolution x coordinates corresponding with new y-coords
x.new <- apply(d, 1, function(x) {
seq(x['x'], x['x2'], length.out=x['len'])
})
id <- rep(seq_along(y.new), d$len) # new group id vector
y.new <- unlist(y.new)
x.new <- unlist(x.new)
d.new <- data.frame(id=id, x=x.new, y=y.new)

p <- ggplot(d.new, aes(x=x,y=y)) +
geom_line(aes(group=d.new$id, color=d.new$y < threshold))+
geom_point(data=stackOne)+
scale_color_discrete(sprintf('Below %s', threshold))
p

conditional line colour - ggplot

There may well be a way to do this through ggplot functions, but in the meantime I hope this helps. I couldn't work out how to draw a ggplotGrob into a clipped viewport (rather it seems to just scale the plot). If you want colour to be conditional on some x-value threshold instead, this would obviously need some tweaking.

Use geom_line() to colour by a positive or negative number

By manually assigning colours, you're triggering the automatic grouping that ggplot2 does, which will just draw seperate lines for positive and negative. However, even if you override the grouping, you're still faced with the problem that a line segment can only have a single colour and lines that cross the 0-line will likely give problems.

The usual solution to such problems is to interpolate the data to specify exact cross-over points. However, this is annoying to do. Instead, we can use ggforce::geom_link2() that already interpolates and use after_stat() to apply colouring after interpolation. If you need your cross-over points to be exact, you might want to interpolate yourself.

library(ggplot2)
library(ggforce)

df <- data.frame(
x = seq(as.Date("1890-01-01"), as.Date("2020-01-01"), by = "1 year"),
y = rnorm(131)
)

ggplot(df, aes(x, y)) +
geom_link2(aes(colour = after_stat(ifelse(y > 0, "positve", "negative"))))

Sample Image

Created on 2021-06-28 by the reprex package (v1.0.0)

There are similar questions here and here where I've suggested the same solution.

How to color geom_line() & geom_point() properly based on condition if less than 0 or greater than 0 in r?

As for giving lines different colours based on whether they are above/below some point, you'd need to interpolate the lines at the crossover points to assign different colours, as line segments themselves cannot have multiple colours. Here is a self-plagiarised solution for interpolating such lines.

First, we'll write two functions. One for finding crossovers and shaping data, and the other one for interpolating at crossover sites.

library(ggplot2)

divide_line <- function(x, y, at = 0) {
df <- data.frame(x, ymin = at, ymax = y)
df$sign <- sign(df$ymax - df$ymin)
df <- df[order(df$x), ]
df$id <- with(rle(df$sign), rep.int(seq_along(values), lengths))

crossover <- which(c(FALSE, diff(df$id) == 1))
crossover <- sort(c(crossover, crossover - 1))
splitter <- rep(seq_len(length(crossover) / 2), each = 2)
crossover <- lapply(split(df[crossover, ], splitter), find_isect)

df <- do.call(rbind, c(list(df), crossover))
df[order(df$x),]
}

find_isect <- function(df) {
list2env(df, envir = rlang::current_env())
dx <- x[1] - x[2]
dy <- ymin[1] - ymin[2]
t <- (-1 * (ymin[1] - ymax[1]) * dx) / (dx * (ymax[1] - ymax[2]) - dy * dx)
df$x <- x[1] + t * -dx
df$ymin <- df$ymax <- ymin[1] + t * -dy
return(df)
}

We can then do the following:

df <- data.frame(
x = 1:100,
y = rnorm(100)
)

df <- divide_line(df$x, df$y, at = 0)

ggplot(df, aes(x, ymax, group = id, colour = as.factor(sign))) +
geom_line()

Sample Image

geom_line - different colour in the same line

Is this what you want?

time <- seq (1,7,1)
var1 <- c(3,5,7,2,3,2,8)
var2 <- c(2,4,18,16,12,3,2)
DF <- data.frame(time, var1, var2)

ggplot(DF, aes(time, var1, colour=(var2>10))) +
geom_line(aes(group=1))

Sample Image

geom line: set manual color for variables that are grouped

Try this:

library(ggplot2)

ggplot(data_sample, aes(x=BP, y=value,
group =variable,color=variable)) +
scale_color_manual(values= c("overall_diff_cases"="#9633FF" ,
"overall_diff_controls"="#E2FF33")) + geom_line(size=1.2)

Output:

Sample Image

With scale_color_manual() you can set the colors you want.



Related Topics



Leave a reply



Submit