I have two functions, with a range-specific argument. I can’t get them on one chart in any way.
f1 = function(x1){
return (sqrt(64-x1^2))
}
f2 = function(x2){
return (cos(x2) - x2)
}
plot(f1,-5, 1)
plot(f2,-pi/2, pi/2)
I just started to learn this language, and I do not quite understand how this process can be performed.
If I execute the code, I get the following:
I need these curves to be on the same graph
You can use ggplot2 and stat_function to draw multiple functions and to restrict the range of each of them:
library(ggplot2)
ggplot() +
stat_function(fun = function(x) cos(x) - x, color = "red", xlim = c(-pi/2,pi/2)) +
stat_function(fun = function(x) sqrt(64-x^2), xlim = c(-5,1)) +
ylim(-10,10)
You wan still add ylim (as I did) and xlim to restrict the main panel range, but the inside-functions xlim will restrict the computation of the functions to theses ranges
You can try the lines() argument to add to an existing plot:
f1 = function(x1){
return (sqrt(64-x1^2))
}
f2 = function(x2){
return (cos(x2) - x2)
}
x <- c(-5:5) # choose your x value range here
y1 <- mapply(FUN = f1,x1 = x) # lets get the y values before plotting
y2 <- mapply(FUN = f2,x2 = x) # lets get the y values before plotting
plot(x,y1, type = "l", col = "red", ylim = c(-5,10))
lines(x, y2, col = "blue", type = "l")
Giving you this:
Using curve might be quite efficient.
curve(sqrt(64 - x^2), col=2, xlim=c(-5, 1), ylim=c(-5, 10))
curve(cos(x) - x, col=4, add=TRUE)
f1 = function(x1){
return (sqrt(64-x1^2))
}
f2 = function(x2){
return (cos(x2) - x2)
}
x = seq(-5, 1, len=20)
y = cbind(y1=f1(x), y2=f2(x))
matplot(x, y, col=1:2, type="l", lty=1)
legend('topright', legend=c(expression(f[1]), expression(f[2])), col = 1:2, lty=1, bty = "n")
Related
The type of plot I am trying to achieve in R seems to have been known as either as moving distribution, as joy plot or as ridgeline plot:
There is already a question in Stackoverflow whose recorded answer explains how to do it using ggplot: How to reproduce this moving distribution plot with R?
However, for learning purposes, I am trying to achieve the same using only base R plots (no lattice, no ggplot, no any plotting package).
In order to get started, I generated the following fake data to play with:
set.seed(2020)
shapes <- c(0.1, 0.5, 1, 2, 4, 5, 6)
dat <- lapply(shapes, function(x) rbeta(1000, x, x))
names(dat) <- letters[1:length(shapes)]
Then using mfrow I can achieve this:
par(mfrow=c(length(shapes), 1))
par(mar=c(1, 5, 1, 1))
for(i in 1:length(shapes))
{
values <- density(dat[[names(dat)[i]]])
plot(NA,
xlim=c(min(values$x), max(values$x)),
ylim=c(min(values$y), max(values$y)),
axes=FALSE,
main="",
xlab="",
ylab=letters[i])
polygon(values, col="light blue")
}
The result I get is:
Clearly, using mfrow (or even layout) here is not flexible enough and also does allow for the overlaps between the distributions.
Then, the question: how can I reproduce that type of plot using only base R plotting functions?
Here's a base R solution. First, we calculate all the density values and then manually offset off the y axis
vals <- Map(function(x, g, i) {
with(density(x), data.frame(x,y=y+(i-1), g))
}, dat, names(dat), seq_along(dat))
Then, to plot, we calculate the overall range, draw an empty plot, and the draw the densities (in reverse so they stack)
xrange <- range(unlist(lapply(vals, function(d) range(d$x))))
yrange <- range(unlist(lapply(vals, function(d) range(d$y))))
plot(0,0, type="n", xlim=xrange, ylim=yrange, yaxt="n", ylab="", xlab="Value")
for(d in rev(vals)) {
with(d, polygon(x, y, col="light blue"))
}
axis(2, at=seq_along(dat)-1, names(dat))
d = lapply(dat, function(x){
tmp = density(x)
data.frame(x = tmp$x, y = tmp$y)
})
d = lapply(seq_along(d), function(i){
tmp = d[[i]]
tmp$grp = names(d)[i]
tmp
})
d = do.call(rbind, d)
grp = unique(d$grp)
n = length(grp)
spcx = 5
spcy = 3
rx = range(d$x)
ry = range(d$y)
rx[2] = rx[2] + n/spcx
ry[2] = ry[2] + n/spcy
graphics.off()
plot(1, type = "n", xlim = rx, ylim = ry, axes = FALSE, ann = FALSE)
lapply(seq_along(grp), function(i){
x = grp[i]
abline(h = (n - i)/spcy, col = "grey")
axis(2, at = (n - i)/spcy, labels = grp[i])
polygon(d$x[d$grp == x] + (n - i)/spcx,
d$y[d$grp == x] + (n - i)/spcy,
col = rgb(0.5, 0.5, 0.5, 0.5))
})
I have three functions and a plot code:
f1 <- function(c){0.187*c-0.000236*c^2+0.194*10-0.00330*100-0.000406*10}
f2 <- function(c){0.187*c-0.000236*c^2+0.194*16.53-0.00330*(16.53^2)-0.000406*16.53}
f3 <- function(c){0.187*c-0.000236*c^2+0.194*20-0.00330*400-0.000406*20}
I wish to plot all three of these on the same graph. I currently have:
png("figure.png")
plot(f1(1:1000), type="l", xlab="x", ylab="y", main="the plot :)")
plot(f2(1:1000), type="l", xlab="x", ylab="y", add = T)
dev.off()
So far this produces just f1 on a plot as opposed to f1 and f2. I believe I am taking the wrong approach because I am producing another plot and trying to add it to a pre-existing plot. I am unsure whether to use geom_line or something similar and just overlay it.
Is there a straight forward way to plot multiple functions and overlay them in the same plot?
geom_line is for ggplot2, which is an entirely different plotting system.
If you start with plot(), you can use lines() to draw lines on your current plot. Your lines are pretty close together, so it doesn't matter much here, but with base plot you usually want to calculate the maximum range in advance so your can set your plot window up right from the start:
x = 1:1000
y1 = f1(x)
y2 = f2(x)
y3 = f3(x)
y_range = range(c(y1, y2, y3))
plot(x, y1, ylim = y_range, type="l", xlab="x", ylab="y", main="the plot :)", col = "red")
lines(x, y2, col = "blue")
lines(x, y3, col = "chartreuse")
ggplot2 is made to work with data in data frames - particularly long-format data frames. Here's how we might approach the problem with ggplot. (Note that, unlike above, ggplot calculates the plot limits and gives a nice legend automatically.)
library(ggplot2)
dd = data.frame(x, y1, y2, y3)
d_long = reshape2::melt(data = dd, id.vars = "x", variable.name = "fun", value.name = "y")
ggplot(d_long, aes(x = x, y = y, color = fun)) +
geom_line()
OR sticking with base R plotting like your code, you can just add the extra functions using lines
plot(f1(1:1000), type="l", xlab="x", ylab="y", main="the plot :)")
lines(1:1000, f2(1:1000))
lines(1:1000, f3(1:1000))
If you want two plots one right next to the other, you have to set the parameter of your palette. Use par(mfrow=c(1,2)) after the png() command.
png("figure.png")
par(mfrow=c(1,2))
plot(f1(1:1000), type="l", xlab="x", ylab="y", main="the plot :)")
plot(f2(1:1000), type="l", xlab="x", ylab="y", add = T)
dev.off()
For functions you can also use curve:
f1 <- function(c){0.187*c-0.000236*c^2+0.194*10-0.00330*100-0.000406*10}
f2 <- function(c){0.187*c-0.000236*c^2+0.194*16.53-0.00330*(16.53^2)-0.000406*16.53}
f3 <- function(c){0.187*c-0.000236*c^2+0.194*20-0.00330*400-0.000406*20}
c0 <- 1
c <- 1000
curve(f1, c0, c, main = 'the plot :)', xlab = 'x', ylab = 'y')
curve(f2, c0, c, add = T)
curve(f3, c0, c, add = T)
As #Gregor noted, geom_line() is a ggplot() call. To go all into the tidyverse, you can do:
#or with ggplot / geom_line
library(tidyverse)
map_df(list(f1 =f1,f2 = f2,f3 = f3), exec, 1:1000)%>%
mutate(x = 1:1000)%>%
gather(key = fx,value = value, -x)%>%
ggplot(aes(x = x, y = value, col = fx)) + geom_line()
Finally, you may be interested in facet_grid as well:
map_df(list(f1 =f1,f2 = f2,f3 = f3), exec, 1:1000)%>%
mutate(x = 1:1000)%>%
gather(key = fx,value = value, -x)%>%
ggplot(aes(x = x, y = value)) + geom_line() +
facet_grid(rows = vars(fx))
I am trying to visualize a curve for pollination distribution. I am very new to R so please don't be upset by my stupidity.
llim <- 0
ulim <- 6.29
f <- function(x,y) {(.156812/((2*pi)*(.000005^2)*(gamma(2/.156812)))*exp(-((sqrt(x^2+y^2))/.000005)^.156812))}
integrate(function(y) {
sapply(y, function(y) {
integrate(function(x) f(x,y), llim, ulim)$value
})
}, llim, ulim)
fv <- Vectorize(f)
curve(fv, from=0, to=1000)
And I get:
Error in y^2 : 'y' is missing
I'm not quite sure what you're asking to plot. But I know you want to visualise your scalar function of two arguments.
Here are some approaches. First we define your function.
llim <- 0
ulim <- 6.29
f <- function(x,y) {
(.156812/((2*pi)*(.000005^2)*(gamma(2/.156812)))*exp(-((sqrt(x^2+y^2))/.000005)^.156812))
}
From your title I thought of the following. The function defined below intf integrates your function over the square [0,ul] x [0,ul] and return the value. We then vectorise and plot the integral over the square as a function the length of the side of the square.
intf <- function(ul) {
integrate(function(y) {
sapply(y, function(y) {
integrate(function(x) f(x,y), 0, ul)$value
})
}, 0, ul)$value
}
fv <- Vectorize(intf)
curve(fv, from=0, to=1000)
If f is a distribution, I guess you can make your (somewhat) nice probability interpretation of this curve. (I.e. ~20 % probability of pollination(?) in the 200 by 200 meter square.)
However, you can also do a contour plot (of the log-transformed values) which illustrate the function we are integrating above:
logf <- function(x, y) log(f(x, y))
x <- y <- seq(llim, ulim, length.out = 100)
contour(x, y, outer(x, y, logf), lwd = 2, drawlabels = FALSE)
You can also plot some profiles of the surface:
plot(1, xlim = c(llim, ulim), ylim = c(0, 0.005), xlab = "x", ylab = "f")
y <- seq(llim, ulim, length.out = 6)
for (i in seq_along(y)) {
tmp <- function(x) f(x, y = y[i])
curve(tmp, llim, ulim, add = TRUE, col = i)
}
legend("topright", lty = 1, col = seq_along(y),
legend = as.expression(paste("y = ",y)))
They need to be modified a bit to make them publication worthy, but you get the idea. Lastly, you can do some 3d plots as others have suggested.
EDIT
As per your comments, you can also do something like this:
# Define the function times radius (this time with general a and b)
# The default of a and b is as before
g <- function(z, a = 5e-6, b = .156812) {
z * (b/(2*pi*a^2*gamma(2/b)))*exp(-(z/a)^b)
}
# A function that integrates g from 0 to Z and rotates
# As g is not dependent on the angle we just multiply by 2pi
intg <- function(Z, ...) {
2*pi*integrate(g, 0, Z, ...)$value
}
# Vectorize the Z argument of intg
gv <- Vectorize(intg, "Z")
# Plot
Z <- seq(0, 1000, length.out = 100)
plot(Z, gv(Z), type = "l", lwd = 2)
lines(Z, gv(Z, a = 5e-5), col = "blue", lwd = 2)
lines(Z, gv(Z, b = .150), col = "red", lwd = 2)
lines(Z, gv(Z, a = 1e-4, b = .2), col = "orange", lwd = 2)
You can then plot the curves for the a and b you want. If either is not specified, the default is used.
Disclaimer: my calculus is rusty and I just did off this top of my head. You should verify that I've done the rotation of the function around the axis properly.
The lattice package has several functions that can help you draw 3 dimensional plots, including wireframe() and persp(). If you prefer not to use a 3d-plot, you can create a contour plot using contour().
Note: I don't know if this is intentional, but your data produces a very large spike in one corner of the plot. This produces a plot that is for all intents flat, with a barely noticable spike in one corner. This is particularly problematic with the contour plot below.
library(lattice)
x <- seq(0, 1000, length.out = 50)
y <- seq(0, 1000, length.out = 50)
First the wire frame plot:
df <- expand.grid(x=x, y=y)
df$z <- with(df, f(x, y))
wireframe(z ~ x * y, data = df)
Next the perspective plot:
dm <- outer(x, y, FUN=f)
persp(x, y, dm)
The contour plot:
contour(x, y, dm)
I'm trying to achieve a similar plot to this one, using R's native plot command.
I was able to get something similar with the code below, however, I'd like the density polygons to overlap. Can anyone suggest a way to do this?
data = lapply(1:5, function(x) density(rnorm(100, mean = x)))
par(mfrow=c(5,1))
for(i in 1:length(data)){
plot(data[[i]], xaxt='n', yaxt='n', main='', xlim=c(-2, 8), xlab='', ylab='', bty='n', lwd=1)
polygon(data[[i]], col=rgb(0,0,0,.4), border=NA)
abline(h=0, lwd=0.5)
}
Outputs:
I would do it something like the following. I plot the densities in the same plot but add an integer to the y values. To make them overlapping i multiply by a constant factor fac.
# Create your toy data
data <- lapply(1:5, function(x) density(rnorm(100, mean = x)))
fac <- 5 # A factor to make the densities overlap
# We make a empty plot
plot(1, type = "n", xlim = c(-3, 10), ylim = c(1, length(data) + 2),
axes = FALSE, xlab = "", ylab = "")
# Add each density, shifted by i and scaled by fac
for(i in 1:length(data)){
lines( data[[i]]$x, fac*data[[i]]$y + i)
polygon(data[[i]]$x, fac*data[[i]]$y + i, col = rgb(0, 0, 0, 0.4), border = NA)
abline(h = i, lwd = 0.5)
}
(Note: This content was previously edited into the Question and was written by #by0.)
Thanks to #AEBilgrau, I quickly put together this function which works really nicely. Note: you need to play around with the factor fac depending on your data.
stacked.density <- function(data, fac = 3, xlim, col = 'black',
alpha = 0.4, show.xaxis = T,
xlab = '', ylab = ''){
xvals = unlist(lapply(data, function(d) d$x))
if(missing(xlim)) xlim=c(min(xvals), max(xvals))
col = sapply(col, col2alpha, alpha)
if(length(col) == 1) col = rep(col, length(data))
plot(1, type = "n", xlim = xlim, ylim = c(1,length(data) + 2),
yaxt='n', bty='n', xaxt=ifelse(show.xaxis, 'l', 'n'), xlab = xlab, ylab = ylab)
z = length(data):1
for(i in 1:length(data)){
d = data[[ z[i] ]]
lines(d$x, fac*d$y + i, lwd=1)
polygon(d$x, fac*d$y+ i, col=col[i], border=NA)
abline(h = i, lwd=0.5)
}
}
data <- lapply(1:5, function(x) density(rnorm(100, mean = x)))
stacked.density(data, col=c('red', 'purple', 'blue', 'green', 'yellow'), alpha=0.3, show.xaxis=T)
outputs:
I was wondering if it is possible to specify the orientation of the axes labels in scatterplot3d? I'd like the y axis label (wt) to be parallel to the y axis and not parallel to the z axis as it is now:
library(scatterplot3d)
with(mtcars, {
scatterplot3d(disp, wt, mpg, main="")
})
At the moment it is hard-coded. You can create a new scatterplot3d function and replace the mtext2 function definition with a slightly modified version that will accept a 'las' argument and then give the call to that function for 'ylab' a different value (2).
......
mytext2 <- function(lab, side, line, at, las=0) mtext(lab, side = side,
line = line, at = at, col = col.lab, cex = cex.lab,
font = font.axis, las = las) # shift hard coding to a default value
lines(c(x.min, x.max), c(z.min, z.min), col = col.axis,
lty = lty.axis)
mytext2(xlab, 1, line = 1.5, at = mean(x.range))
lines(xx[1] + c(0, y.max * yx.f), c(z.min, y.max * yz.f +
z.min), col = col.axis, lty = lty.axis)
mytext2(ylab, if (angle.1)
2
else 4, las=2, line = 0.5, at = z.min + y.max * yz.f) # 2nd change
.....
This answer is stolen and slightly tweaked from #JorisMeys answer to a similar question about changing the label position.
with(mtcars, {
scatterplot3d(disp, wt, mpg, main="", ylab="")
})
dims <- par("usr")
x <- dims[1]+ 0.97*diff(dims[1:2])
y <- dims[3]+ 0.4*diff(dims[3:4])
text(x, y, "wt", srt=0)
There is certainly more playing you can do, especially with the margins. For example:
with(mtcars, {
scatterplot3d(disp, wt, mpg, main="", ylab="",
y.margin.add=0.5)
})
dims <- par("usr")
x <- dims[1]+ 0.97*diff(dims[1:2])
y <- dims[3]+ 0.4*diff(dims[3:4])
text(x, y, "wt")