1

I have a basic plot using stat.funciton:

x<-c(-5,5)
fun.1 <- function(x) x^2 + x
p<-ggplot(data.frame(x=x),aes(x)) +stat_function(fun = fun.1,geom="line") 
print(p)

plot

Is there any way to change the linetype of the plot conditional on the y-value? In other words, how can I change this plot so that values on the curve that lie above, say, y=10, are plotted as dotted instead of solid line? Note I get that I could make separate curves for the x-values, and define the function piecewise, but I'm looking for a simpler solution.

EDIT: The followibng code (piecewise-defined x) doesnt seem to work either:

x1<-c(-5,4)
fun.1 <- function(x1) x1^2 + x1
x2<-c(4,5)
fun.2 <- function(x2) x2^2 + x2
p1<-ggplot(data.frame(x=x1),aes(x1)) +stat_function(fun = fun.1,geom="line") 
p1<-p1+ggplot(data=data.frame(x2),aes(x2))+stat_function(fun = fun.2,geom="line",lty=2) 
print(p1)

>Error: Don't know how to add o to a plot

However the second plot works when done separately:

 x2<-c(4,5)
 fun.2 <- function(x2) x2^2 + x2
 p2<-ggplot(data=data.frame(x2),aes(x2))+stat_function(fun = fun.2,geom="line",lty=2) 
 print(p2)

Please help?

1 Answer 1

3

You need to create a separate group for each segment of the curve that is above/below 10.

ggplot(data.frame(x=x),aes(x)) +
  stat_function(fun = fun.1,geom="line", n=400,
            aes(group=factor(c(0,cumsum(diff(..y.. >= 10) != 0))), 
                linetype=factor(c(0,cumsum(diff(..y.. >= 10) != 0))))) +
  scale_linetype_manual(values=c(2,1,2)) +
  guides(linetype=FALSE)

enter image description here

Below is more detail on how the groups are created. First we start with a simple y vector for illustration:

> y = c(7:13,12:7)

> y
 [1]  7  8  9 10 11 12 13 12 11 10  9  8  7

Now let's find where the curve is above or below 10:

> y >= 10
 [1] FALSE FALSE FALSE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE FALSE FALSE FALSE

Mathematical functions treat logical TRUE and FALSE as having numeric values of, respectively, 1 and 0, so we can now find the specific values at which the curve crosses y==10:

> diff(y >=10)
 [1]  0  0  1  0  0  0  0  0  0 -1  0  0

We want to increment to a new group each time the curve crosses y==10:

> diff(y >=10) != 0
 [1] FALSE FALSE  TRUE FALSE FALSE FALSE FALSE FALSE FALSE  TRUE FALSE FALSE

> cumsum(diff(y >=10) != 0)
 [1] 0 0 1 1 1 1 1 1 1 2 2 2

So now we have three different groups. But diff returns a vector that is one shorter than the original vector. So we add 0 onto the beginning of the grouping vector so that it will be the same length as the data vectors.

> c(0, cumsum(diff(y >=10) != 0))
 [1] 0 0 0 1 1 1 1 1 1 1 2 2 2

Finally, we convert it to a factor, otherwise linetype will give us an error for mapping a continuous variable to linetype:

> factor(c(0, cumsum(diff(y >=10) != 0)))
 [1] 0 0 0 1 1 1 1 1 1 1 2 2 2
Levels: 0 1 2

Also, ..y.. is the vector of y values internally calculated by ggplot to plot your function, so that's why we use ..y.. inside ggplot.

Sign up to request clarification or add additional context in comments.

5 Comments

Thanks. Why is it not possible to access ..y.. directly? ie suppose i only wanted one side to be dotted, why doesn't aes(group=factor(c(..y.. < 10,..y.. >= 10))) work?
It is possible to access ..y.. directly, but c(..y.. < 10,..y.. >= 10) is a vector of length equal to twice the length of the data, causing an error. For example, let's say you have a vector y=1:5, and then do c(y > 10, y <=10). What do you get? You could do linetype=..y..<10 and group=..y..<10, but try it and see what you get.
To get only one dashed segment with the code in my answer, you could do scale_linetype_manual(values=c(2,1,1))
x<-c(-5,5) ggplot(data.frame(x=x),aes(x)) + stat_function(fun = fun.1,geom="line", n=400, aes(group=factor(..y.. <= 10), linetype=factor(..y.. <= 10))) + scale_linetype_manual(values=c(2,1)) + guides(linetype=FALSE) gives an extra horizontal dashed line??
Yes, because ..y.. <= 10 puts both segments that are greater than 10 in the same group, so ggplot connects all points in that group. BTW, I've simplified the plot code for group and linetype.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.