Suppose I have a curve y, and two other curves u and l in the form of vectors. How to plot:
plot(y, lab="estimate")
plot!(y-l, lab="lower bound")
plot!(y+u, lab="upper bound")
That is, an asymmetric confidence interval? I know how to plot the symmetric case with the option ribbon as explained here.
The current answers are NOT correct. Here are two ways that are correct (as of v1.10.1 of Plots.jl):
Method 1: Using fillrange
plot(x, l, fillrange = u, fillalpha = 0.35, c = 1, label = "Confidence band")
Method 2: Using ribbon
plot(x, (l .+ u) ./ 2, ribbon = (l .- u) ./ 2, fillalpha = 0.35, c = 1, label = "Confidence band")
(Here, l and u denote the the "lower" and "upper" y values, respectively, and x denotes their common x values.) The key difference between these two methods is that fillrange shades the region between l and u, while the ribbon argument is a radius, i.e. half the width of the ribbon (or in other words, the vertical deviation from the midpoints).
Example using fillrange:
x = collect(range(0, 2, length= 100))
y1 = exp.(x)
y2 = exp.(1.3 .* x)
plot(x, y1, fillrange = y2, fillalpha = 0.35, c = 1, label = "Confidence band", legend = :topleft)
Let's scatter y1 and y2 on top of the plot, just to make sure we're filling in the right region.
plot!(x,y1, line = :scatter, msw = 0, ms = 2.5, label = "Lower bound")
plot!(x,y2, line = :scatter, msw = 0, ms = 2.5, label = "Upper bound")
Result:
Example using ribbon:
mid = (y1 .+ y2) ./ 2 #the midpoints (usually representing mean values)
w = (y2 .- y1) ./ 2 #the vertical deviation around the means
plot(x, mid, ribbon = w , fillalpha = 0.35, c = 1, lw = 2, legend = :topleft, label = "Mean")
plot!(x,y1, line = :scatter, msw = 0, ms = 2.5, label = "Lower bound")
plot!(x,y2, line = :scatter, msw = 0, ms = 2.5, label = "Upper bound")
(Here, x, y1, and y2 are the same as before.)
Result:
Notice that the labels for ribbon and fillrange are different in the legends: the former labels the midpoints/means, while the latter labels the shaded region itself.
Some additional comments:
The OP's answer of plot(y, ribbon=(l,u), lab="estimate") is not correct (at least for Plots v1.10.1.). I realize this thread is over 3 years old, so perhaps it worked in the earlier version of Plots.jl that the OP was using at the time)
Similar to one of the answers given,
plot(x, [mid mid], fillrange=[mid .- w, mid .+ w], fillalpha=0.35, c = [1 4], label = ["Band 1" "Band 2"], legend = :topleft, dpi = 80)
will work, but this actually creates TWO ribbons (and hence, two icons in the legend) which may or may not be what the OP was looking for. To illustrate the point:
It turns out that the option ribbon accepts both lower and upper bounds:
plot(y, ribbon=(l,u), lab="estimate")
Notice that by passing l and u in the ribbon option, the filled area will correspond to the region between y-l and y+u. In other words, l and u should be the "deviations" from the mean curve y.
Something like this? (seen here).
plot([y y], fillrange=[y.-l y.+u], fillalpha=0.3, c=:orange)
plot!(y)
The fillrange solution in #leonidas 's answer might bring an additional boundary line (at least in Plots v1.35). To remove such a line, a workaround is to specify linealpha = 0, that is,
plot(x, l, fillrange = u, fillalpha = 0.35, c = 1, label = "Confidence band", linealpha = 0)
Related
I have a plot where I draw arrows from points to points. I would like to put this arrow heads not to the end of the line, but to middle. Is there a simple way to do it other than placing extra arrows with half length of the according line?
My code is this:
plot(x, y, xlim=range(x), ylim=range(y), xlab="x", ylab="y", pch=16,
main="Filled Plane")
for(i in 1:20){
arrows(x[i], y[i], x[i+1], y[i+1], length = 0.25, angle = 30, col = i)
}
Make a custom function myArrow() and add one new argument cut to control the proportion of the arrows
myArrow <- function(x0, y0, x1, y1, cut = 1, ...){
x.new <- (1 - cut) * x0 + cut * x1
y.new <- (1 - cut) * y0 + cut * y1
# segments(x0, y0, x1, y1, ...)
arrows(x0, y0, x.new, y.new, ...)
}
Note1 : The computation of x.new and y.new in this custom function uses a simple mathematical concept, i.e. the Section Formula. The value of cut must be between 0 to 1.
Note2 : The use of this function is equivalent to that of the original functionarrows() other than that it has one more new argument cut.
Note3 : If you want complete lines behind the arrows, just remove the hash(#) in the function.
Plot and try different cut value. For example, I use cut = 0.7. (If you want the arrowheads to the middle, use cut = 0.5.)
# Toy Data
x <- seq(1, 5.5, by = 0.5)
y <- rep(c(1, 5), 5)
plot(x, y, pch = 16)
for(i in 1:9){
myArrow(x[i], y[i], x[i+1], y[i+1], cut = 0.7, col = i, lwd = 2)
}
Since you do not provide your x and y, I made up some data. There is no need for the loop. arrows will handle a vector of coordinates. One way is to draw a full-length arrow with no arrowhead and another that just goes halfway but has the arrowhead.
## Some bogus data
set.seed(123)
x = runif(4)
y = runif(4)
## Compute the midpoints
midx = diff(x)/2 + x[-length(x)]
midy = diff(y)/2 + y[-length(y)]
## Draw it
plot(x,y,xlim=range(x), ylim=range(y), xlab="x", ylab="y",
main="Filled Plane",pch=16)
arrows(x[-length(x)], y[-length(y)],x[-1],y[-1],
angle = 0, col = 1:3)
arrows(x[-length(x)], y[-length(y)],midx,midy,
length = 0.25, angle = 30, col = 1:3)
I have been trying to generate random values so that I can construct a circle.
The values of x and y are expected to satisfy the following equation
x^2 + y^2 = 1
Here is the code that I used.
par(type = "s")
x <- runif(1000, min = -1, max = 1)
y <- sqrt(1 - x^2)
z <- NULL
z$x <- x
z$y <- y
z <- as.data.frame(z)
plot.new()
plot(z$x, z$y, type = "p")
plot.window(xlim = c(-10,10), ylim = c(-10,10), asp = 1)
But the graph I get is not quite what I expected it to be.
The graph resembles an upper half of an ellipse rather than a semicircle
Why are there no values for y where y < 0
Please find the plot here.
I am also interested in finding out, how to generate random values for x, y, z, a; where x^2 + y^2 + z^2 + a^2 = 10
Maybe you missed #thelatemail's comment:
png()
plot(z$x, z$y, type = "p", asp=1)
dev.off()
The reason passing asp=1 to plot.window would fail(if it were called first, and this is what you might have tried) is that plot itself calls plot.window again, and in the process reacquires the default values. You can see that in the code of plot.default:
> plot.default
function (x, y = NULL, type = "p", xlim = NULL, ylim = NULL,
log = "", main = NULL, sub = NULL, xlab = NULL, ylab = NULL,
ann = par("ann"), axes = TRUE, frame.plot = axes, panel.first = NULL,
panel.last = NULL, asp = NA, ...)
{
localAxis <- function(..., col, bg, pch, cex, lty, lwd) Axis(...)
localBox <- function(..., col, bg, pch, cex, lty, lwd) box(...)
localWindow <- function(..., col, bg, pch, cex, lty, lwd) plot.window(...)
#.... omitted the rest of the R code.
(Calling plot.window after that plot call should not be expected to have any favorable effect.)
The problem is within this part of your code:
x <- runif(1000, min = -1, max = 1)
y <- sqrt(1 - x^2)enter code here
This problem arises from interpreting two distinct mathematical entities as the same (functions and equations are two different things). A function f takes an input x, and returns a single output f(x). Equations don't have this limitation, so if you are encoding this equation as a function, you will lose half the points in the circle, you will generate all the points in the upper semicircle.
Since the circle equation has two y outputs for any x value you can just generate two pairs of coordinates for each point generated by your uniform distribution like this:
x1 = runif(1000, min = -1, max = 1)
x2 = x1
y1 = sqrt(1 - x1^2)
y2 = (-1)*y1
x = c(x1,x2)
y = c(y1,y2)
plot(x,y, asp=1)
As John Coleman recommended in his comment, i'd prefer using parametric/polar coordinates instead. Generate angles in radians between 0 and 2pi and then calculate the appropriate x and y positions using the generated angle and the radius you want.
radius = 1
theta = runif(1000, min = 0, max = 2*pi)
x = radius * cos(theta)
y = radius * sin(theta)
plot(x,y, asp=1)
For the last part of your question, for each value of a variable, you'd have to work out all the possible tuples that solve the equation, and if z and a are also variables, it may not be possible to represent it solely on a 2-dimensional graph.
I'm quite new to R and ggplot2 so apologies if this is an obvious question, but I've searched around and can't find anything about this exact issue
I have a ggplot density plot for 6 variables on the same plot, overlapping. What I am trying to do is to change the maximum height of each variable to be a certain value without changing the distribution. e.g. :
variable_1 - 1, //on Y axis
variable_2 - 0.5 etc.
This way I can get an idea of the distribution (across the x axis) whilst also showing a second independent parameter through the y axis
Is this possible at all?
Yes this is possible although I wouldn't recommend it. What you can do is just divide the distribution by it's maximum and then multiply with the target height.
# some example data:
x = seq(-5, 5, .1)
y1 = dnorm(x)
y2 = dnorm(x, .5, .2)
Y = cbind(y1, y2)
matplot(x, Y, type = 'l', bty = 'n', lty = 1, las = 1)
# now I want the red line to be max 1
# and the black line to be mack .5
y1 = .5*y1 / max(y1)
y2 = 1*y2 / max(y2)
Y = cbind(y1, y2)
matplot(x, Y, type = 'l', bty = 'n', lty = 1, las = 1)
The important part here is that I used two different transformations for y1 and y2. The consequence is that in the second figure the distributions cannot be compared anymore. You can avoid this by only applying the same transformation to all distributions.
I wrote following R script:
#energy diagram
x <- c(0.1, 0.3, 0.5, 0.7, 0.9 ) #chosen randomly, reaction axis
y <- c(-5.057920, -5.057859, -5.057887,-5.057674, -5.057919 ) #energy of the educt, intermediate, transtition states and product
plot(x,y, type="p",
xlim=c(0,1),
ylim=c(-5.058,-5.0575),
xlab="reaction axis",
ylab=expression(paste(E[el] ," / ",10^6," ",kJ/mol)),
xaxt="n" #hide x-axis
)
#h- and v-lines, so i can draw curves by hand
abline(v=seq(0,1,0.1),h=seq(-5.0600,-5.0500,0.00005),col="black",lty=1,lwd=1)
abline(h=c(-5.057920, -5.057859, -5.057887,-5.057674), col="blue", lty=1,lwd=0.7)
Is it possible to draw a curve through the points that would look like a energy diagram. An example of an energy diagram is here:
A lot could be done to streamline / vectorize this code, but for a smallish diagram this works pretty well:
# get that data
x <- c(0.1, 0.3, 0.5, 0.7, 0.9 ) # reaction axis
y <- c(-5.057920, -5.057859, -5.057887,-5.057674, -5.057919 ) # energies
I'm going to make a little Bezier curve to connect each point to the next---this way we can make sure the smooth line passes through the data, not just close to it. I'll give each point a single 'control point' to define the slope. By using the same y-values for a point and it's control point, the slope at the point will be 0. I'll call the offset between the point and the control point delta. We'll start with one point-pair:
library(Hmisc)
delta = 0.15
bezx = c(0.1, 0.1 + delta, 0.3 - delta, 0.3)
bezy = rep(y[1:2], each = 2)
plot(bezx, bezy, type = 'b', col = "gray80")
lines(bezier(bezx, bezy), lwd = 2, col = "firebrick4")
Here I plotted the points and control points in gray, and the smooth line in red so we can see what's going on.
It looks promising, let's turn it into a function that we can apply to each pair of points:
bezf = function(x1, x2, y1, y2, delta = 0.15) {
bezier(x = c(x1, x1 + delta, x2 - delta, x2), y = c(y1, y1, y2, y2))
}
You can play with the delta parameter, I think 0.1 looks pretty good.
plot(x, y, xlab = "Reaction coordinate", ylab = "E", axes = F)
box(bty = "L")
axis(side = 2)
for(i in 1:(length(x) - 1)) {
lines(bezf(x1 = x[i], x2 = x[i + 1], y1 = y[i], y2 = y[i + 1], delta = 0.1))
}
You can of course tweak the plot, add labels, and ablines as in your original. (Use my for loop with the lines command to draw only the smoothed lines.) I left the points on to show that we are passing through them, not just getting close.
I prefer plotting in ggplot2, if you do too you'll need to extract the data into a data.frame:
bezlist = list()
for (i in 1:(length(x) - 1)) {
bezlist[[i]] = bezf(x1 = x[i], x2 = x[i + 1], y1 = y[i], y2 = y[i + 1], delta = 0.1)
}
xx = unlist(lapply(bezlist, FUN = '[', 'y'))
yy = unlist(lapply(bezlist, FUN = '[', 'y'))
bezdat = data.frame(react = xx, E = yy)
library(ggplot2)
ggplot(bezdat, aes(x = react, y = E)) +
geom_line() +
labs(x = "Reaction coordinate")
You could use a spline fit. Define some points along the energy diagram, and then fit to them using a spline function. The more points that you provide, the better that your fit will be. You can check out the smooth.splines function in the stats package for one implementation of the spline fit.
I use the following code to draw a plot.
x = seq(-20,20,by=0.2);
c = .2;
y1 = exp(c*x);
c = .5;
y2 = exp(c*x);
c = 1;
y3 = exp(c*x);
par(mgp = c(2,.5,0)); # to adjust dist of x/y label to plot, x/y axes to plot
plot(x,
y1,
type="l",
xlab=expression(x-theta),
ylab=expression(L(x,theta)),
main="function");
lines(x,y2,col="blue");
lines(x,y3,col="green");
legend(x=-20,
y=40,
title=expression(L(x, theta)==e^{c(x-theta)}),
legend=expression("c=.2", "c=.5", "c=1"),
lty=c(1, 1, 1),
lwd=c(2.5, 2.5,2.5),
col=c("black","blue","green"));
I find that the brackets of $(x-\theta)$ in the legend exceed the box. Is there any way to move that expression down? I tried replacing the original legend function by
legend(x=-20,y=40,title.adj=c(0,.5), title=expression(L(x,theta)==e^{c(x-theta)}), legend=expression("c=.2","c=.5","c=1"), lty=c(1,1,1), lwd=c(2.5,2.5,2.5), col=c("black","blue","green"));
However, the expression appeared twice in the legend box.
Thanks!
I think it looks better without a box ( legend( ... ,bty='n') ) but if you really want a box, here's how you do it:
lgnd = legend(x=-20,
y=40,
title=expression(L(x, theta)==e^{c(x-theta)}),
legend=expression("c=.2", "c=.5", "c=1"),
lty=c(1, 1, 1),
lwd=c(2.5, 2.5,2.5),
col=c("black","blue","green"),
# no box
bty='n');
# plot your own box using the lgnd$rect as your starting point
params = lgnd$rect
rect(xleft = params[['left']],
ybottom = params[['top']] - params[['h']],
xright = params[['left']] + params[['w']],
ytop = params[['top']] + 1)