1
votes

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

1 Answers

3
votes

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.