Related
This code gets the data needed to make two different lines. I was wondering if there was a way to see if two lines intersected easily.
# generate data
red <- matrix(runif(n = 4, min = 0, max = 1), nrow = 2)# gets the 4 points for the first line
blue <- matrix(runif(n = 4, min = 0, max = 1), nrow = 2)# gets the 4 points for second line
# make a plot
plot(red, col = "red", pch = 16, cex = 2,
asp = 1, xlim = c(0,1), ylim = c(0,1),
xlab = "", ylab = "")##plots both points red
abline(v = c(0,1), col = "grey", lty = 2)
abline(h = c(0,1), col = "grey", lty = 2)
segments(red[1,1], red[1,2], red[2,1], red[2,2], lwd = 2, col = "red")#Makes the line segment
points(blue, col = "blue", pch = 16, cex = 2,
asp = 1, xlim = c(0,1), ylim = c(0,1))# does same thing for blue line
segments(blue[1,1], blue[1,2], blue[2,1], blue[2,2], lwd = 2, col = "blue")
##makes all of the plots and can see if the plot intersects.`
Here's a practical answer using simple algebra wrapped up in a function.
The process is to find the slope and y intercept of both lines, and solve simultaneous equations to find the intersection. If both line segments have the same gradient the answer is undefined so return NA.
Return the x, y co-ordinates of the intersection if it is within the x range of one of the line segments, otherwise return NA
check_intersect <- function(mat1, mat2)
{
dy1 <- mat1[,2][which.max(mat1[,1])] - mat1[,2][which.min(mat1[,1])]
dy2 <- mat2[,2][which.max(mat2[,1])] - mat2[,2][which.min(mat2[,1])]
dx1 <- max(mat1[,1]) - min(mat1[,1])
dx2 <- max(mat2[,1]) - min(mat2[,1])
m1 <- dy1/dx1
m2 <- dy2/dx2
if(m1 == m2) return(NA)
c1 <- mat1[1, 2] - m1 * mat1[1, 1]
c2 <- mat2[1, 2] - m2 * mat2[1, 1]
x <- (c2 - c1)/(m1 - m2)
y <- m1 * x + c1
if(x > min(mat1[,1]) & x < max(mat1[,1]))
return(c(x, y))
else
return(NA)
}
Now test this with a reprex:
set.seed(123)
red <- matrix(runif(n = 4, min = 0, max = 1), nrow = 2)# gets the 4 points for the first line
blue <- matrix(runif(n = 4, min = 0, max = 1), nrow = 2)# gets the 4 points for second line
# make a plot
plot(red, col = "red", pch = 16, cex = 2,
asp = 1, xlim = c(0,1), ylim = c(0,1),
xlab = "", ylab = "")##plots both points red
abline(v = c(0,1), col = "grey", lty = 2)
abline(h = c(0,1), col = "grey", lty = 2)
segments(red[1,1], red[1,2], red[2,1], red[2,2], lwd = 2, col = "red")#Makes the line segment
points(blue, col = "blue", pch = 16, cex = 2,
asp = 1, xlim = c(0,1), ylim = c(0,1))# does same thing for blue line
segments(blue[1,1], blue[1,2], blue[2,1], blue[2,2], lwd = 2, col = "blue")
p <- check_intersect(red, blue)
points(p[1], p[2], cex = 2)
p
#> [1] 0.5719010 0.6781469
Created on 2020-03-24 by the reprex package (v0.3.0)
I have found multiple ways to create a secondary y-axis in plot but I couldn't find a way to create a secondary y-axis in histogram.
Here is a sample code:
a <- sample(90:110, 50, replace=TRUE)
b <- runif(50, min=0, max=1)
hist(a)
lines(b)
b is too small to show in hist(a) so is there any way that I can see both in the histogram?
Technically a solution may be quite an identical to the approach proposed for the plots in this answer. The idea is to use overlapping of two plots as proposed by #r2evans.
It makes sense to use color coding:
# set color rules
col_a <- "red"
col_b <- "darkblue"
col_common <- "black"
Then let's draw the histogram and the plot:
# draw a histogram first
par(mar = c(5, 5, 5, 5) + 0.3)
hist(a, col = col_a, axes = FALSE, xlab = "", ylab = "", main = "")
# add both axes with the labels
axis(side = 1, xlim = seq(along.with = b), col = col_a, col.axis = col_a)
mtext(side = 1, text = "a_value", col = col_a, line = 2.5)
axis(side = 2, col = col_a, col.axis = col_a, ylab = "")
mtext(side = 2, text = "a_Frequency", col = col_a, line = 2.5)
# ... and add an overlaying plot
par(new=TRUE)
plot(b, ylim = c(0, 1), axes = FALSE, col = col_b, type = "l", xlab = "", ylab = "")
points(b, col = col_b, pch = 20, xlab = "", ylab = "")
axis(side = 3, xlim = seq(along.with = b), col = col_b, col.axis = col_b)
mtext(side = 3, text = "b_index", col = col_b, line = 2.5)
axis(side = 4, ylim = c(0, 1), col = col_b, col.axis = col_b)
mtext(side = 4, text = "b_value", col = col_b, line = 2.5)
box(col = col_common)
Here is a simplified plot to work with:
env <- data.frame(site = c('BLK','DUC','WHP','BLK','DUC','WHP','BLK','DUC','WHP'),
sal = c(5,6,3,2,4,5,6,8,4),
date = c(2013,2013,2013,2015,2015,2015,2017,2017,2017))
sitelist <- c('BLK','DUC','WHP')
par(mar=c(3,5,3,6), xpd = T)
plot(sal~date, data = env, type = 'n', ylim = c(0,10), ylab = 'Salinity',
bty = 'n', xlab = '')
abline(v=2016, col = 'khaki', lwd = 20)
abline(mean(env$sal), 0, lty = 3)
for (ii in seq_along(sitelist)) {
i <- sitelist[ii]; lines(sal[site==i] ~ date[site==i], data = env,
col = c(4,2,5)[ii], lwd = 2,
lty = c(1,2,3)[ii]);
points(sal[site==i] ~ date[site==i], data = env,
pch = c(0,1,2)[ii], col = c(4,2,5)[ii])}
legend('topright', title = 'sites', inset=c(-0.2,0), lty = c(1,2,3),
col = c(4,2,5), lwd = 2, sitelist,
pch = c(0,1,2))
As written, this code yields a plot where the abline functions create lines that go outside of the boundaries of the plot, thanks to xpd=T. However, I don't want to set xpd=F, because I won't be able to plot my legend outside of the boundaries. The solution must either be a way to plot a legend outside of the boundaries with xpd=F or a way to plot lines that stop at the boundaries. Ideally, the solution would use the base program and be fairly standard, so I could drop it into each of my ~20 plots without too much customization.
I tried using segments but was not happy with the rounded edges of the segment, as my vertical line is supposed to be a sort of shaded area to indicate a certain time period.
You can either set xpd to FALSE in the par call and insert xpd = TRUE in the legend call like this:
env <- data.frame(site = c('BLK','DUC','WHP','BLK','DUC','WHP','BLK','DUC','WHP'),
sal = c(5,6,3,2,4,5,6,8,4),
date = c(2013,2013,2013,2015,2015,2015,2017,2017,2017))
sitelist <- c('BLK','DUC','WHP')
par(mar=c(3,5,3,6), xpd = F)
plot(sal~date, data = env, type = 'n', ylim = c(0,10), ylab = 'Salinity',
bty = 'n', xlab = '')
abline(v=2016, col = 'khaki', lwd = 20)
abline(mean(env$sal), 0, lty = 3)
for (ii in seq_along(sitelist)) {
i <- sitelist[ii]; lines(sal[site==i] ~ date[site==i], data = env,
col = c(4,2,5)[ii], lwd = 2,
lty = c(1,2,3)[ii]);
points(sal[site==i] ~ date[site==i], data = env,
pch = c(0,1,2)[ii], col = c(4,2,5)[ii])}
legend('topright', title = 'sites', inset=c(-0.2,0), lty = c(1,2,3),
col = c(4,2,5), lwd = 2, sitelist,
pch = c(0,1,2),
xpd = T)
Or keep xpd = TRUE in the par call and set xpd to FALSE in the abline calls like this:
env <- data.frame(site = c('BLK','DUC','WHP','BLK','DUC','WHP','BLK','DUC','WHP'),
sal = c(5,6,3,2,4,5,6,8,4),
date = c(2013,2013,2013,2015,2015,2015,2017,2017,2017))
sitelist <- c('BLK','DUC','WHP')
par(mar=c(3,5,3,6), xpd = T)
plot(sal~date, data = env, type = 'n', ylim = c(0,10), ylab = 'Salinity',
bty = 'n', xlab = '')
abline(v=2016, col = 'khaki', lwd = 20,xpd=F)
abline(mean(env$sal), 0, lty = 3,xpd=F)
for (ii in seq_along(sitelist)) {
i <- sitelist[ii]; lines(sal[site==i] ~ date[site==i], data = env,
col = c(4,2,5)[ii], lwd = 2,
lty = c(1,2,3)[ii]);
points(sal[site==i] ~ date[site==i], data = env,
pch = c(0,1,2)[ii], col = c(4,2,5)[ii])}
legend('topright', title = 'sites', inset=c(-0.2,0), lty = c(1,2,3),
col = c(4,2,5), lwd = 2, sitelist,
pch = c(0,1,2))
This should solve your issue.
Replace
abline(v=2016, col = 'khaki', lwd = 20)
abline(mean(env$sal), 0, lty = 3)
with
lines(c(2013, 2017), rep(mean(env$sal), 2), col="black", lwd = 2, lty = 2)
lines(rep(2016, 2), c(0, 10), col="khaki", lwd = 20)
Source: https://stackoverflow.com/a/24741885/5874001
par(mar=c(3,5,3,6), xpd = T)
plot(sal~date, data = env, type = 'n', ylim = c(0,10), ylab = 'Salinity', bty = 'n', xlab = '')
lines(c(2013, 2017), rep(mean(env$sal), 2), col="black", lwd = 2, lty = 2)
lines(rep(2016, 2), c(0, 10), col="khaki", lwd = 20)
for (ii in seq_along(sitelist)) {
i <- sitelist[ii]; lines(sal[site==i] ~ date[site==i],
data = env,
col = c(4,2,5)[ii],
lwd = 2,
lty = c(1,2,3)[ii]);
points(sal[site==i] ~ date[site==i], data = env,
pch = c(0,1,2)[ii], col = c(4,2,5)[ii])}
legend('topright', title = 'sites', inset=c(-0.2,0),
lty = c(1,2,3), col = c(4,2,5), lwd = 2,
sitelist, pch = c(0,1,2))
If you have 20+ plots, I'd look to see if you can write a loop to perform that task.
I've constructed an Ecdf plot from the Hmisc package with the following call:
require(Hmisc)
Ecdf(latency_targ1, group = CONDITION, lty = c(1, 2, 3, 4),
lwd = 4, label.curves = list(method = 'arrow',
keys = "lines", lwd = 2), xlim = c(0,500),
subtitles = FALSE, xlab = "Latency",
ylab = "Proportion latency <= x")
I have been unable to find how to change the size of the axis labels of the plot and the default size is rather small.
Try this:
Ecdf(latency_targ1, group = CONDITION, lty = c(1, 2, 3, 4),
lwd = 4, label.curves = list(method = 'arrow',
keys = "lines", lwd = 2), xlim = c(0,500),
subtitles = FALSE, xlab = "Latency",
ylab = "Proportion latency <= x",
cex.lab=1.5, xaxt="n", yaxt="n")
axis(2, cex.axis=1.5)
axis(1, cex.axis=1.5)
When I draw grid lines on a plot using abline() the grid lines are drawn over the data.
Is there a way to draw the abline() lines behind the data? I feel this would look better.
Example:
x <- seq(0, 10)
y <- x
plot(x, y, col = 'red', type = 'o', lwd = 3, pch = 15)
abline(h = seq(0, 10, .5), col = 'lightgray', lty = 3)
abline(v = seq(0, 10, .5), col = 'lightgray', lty = 3)
The plot produced has the gray grid lines going over the data (red line). I would like the red line to be on top of the gray lines.
The panel.first argument of plot() can take a list or vector of functions so you can put your abline() calls in there.
plot(1:4, panel.first =
c(abline(h = 1:4, lty = 2, col = 'grey')
,abline(v = 1:4, lty = 2, col = 'grey')))
Use plot() to set up the plotting window, but use type = "n" to not plot any data. Then do your abline() calls, or use grid(), and then plot the data using whatever low-level function is appropriate (here points() is fine).
x <- seq(0, 10)
y <- x
plot(x, y, type = "n")
abline(h = seq(0, 10, .5), col = 'lightgray', lty = 3)
abline(v = seq(0, 10, .5), col = 'lightgray', lty = 3)
points(x, y, col = 'red', type = 'o', lwd = 3, pch = 15)
or
## using `grid()`
plot(x, y, type = "n")
grid()
points(x, y, col = 'red', type = 'o', lwd = 3, pch = 15)
See ?grid for details of how to specify the grid as per your abline() version.
Plot first with type="n" to establish coordinates. Then put in the grid lines, then plot again with your regular plot type:
plot(x, y, col = 'red', type = 'n', lwd = 3, pch = 15)
abline(h = seq(0, 10, .5), col = 'lightgray', lty = 3)
abline(v = seq(0, 10, .5), col = 'lightgray', lty = 3)
par(new=TRUE)
plot(x, y, col = 'red', type = 'o', lwd = 3, pch = 15)
I admit that I have always thought the name for that par parameter was "backwards."
Another way of creating grid lines is to set tck=1 when plotting or in the axis function (you may still want to plot the points using points after creating the grid lines.