Integration inconsistency with adaptIntegrate - r

I am estimating the probability of a species dispersing across a gridded landscape, given a dispersal kernel (a function of distance) with a maximum dispersal distance. I'm attempting to calculate area-to-area dispersal probabilities as described in eqn. 8 of this (open access) paper. This involves quadruple integration, evaluating the value of the dispersal function for every possible combination of source and target point in the source and target cells, respectively.
I've implemented this with adaptIntegrate from the cubature package, as follows, for source cell A, target cell B, and a simplified dispersal kernel where dispersal is 1 when the inter-point distance > 1.25 and 0 otherwise. This is shown graphically below, where the red region of cell B is unreachable since no point in cell A is within a distance of 1.25.
library(cubature)
f <- function(xmin, xmax, ymin, ymax) {
adaptIntegrate(function(x) {
r <- sqrt((x[3] - x[1])^2 + (x[4] - x[2])^2)
ifelse(r > 1.25, 0, 1)
},
lowerLimit=c(-0.5, -0.5, xmin, ymin),
upperLimit=c(0.5, 0.5, xmax, ymax),
maxEval=1e5)
}
f(xmin=1.5, xmax=2.5, ymin=-0.5, ymax=0.5)
# $integral
# [1] 0.01949567
#
# $error
# [1] 0.001225998
#
# $functionEvaluations
# [1] 100035
#
# $returnCode
# [1] 0
I get a different integral when considering a target cell, C, that is placed the same distance away, but above rather than to the right of cell A.
f(xmin=-0.5, xmax=0.5, ymin=1.5, ymax=2.5)
# $integral
# [1] 0.01016105
#
# $error
# [1] 0.0241325
#
# $functionEvaluations
# [1] 100035
#
# $returnCode
# [1] 0
Why are these integrals so different (0.01949567 vs 0.01016105)? Have I coded it incorrectly? Changing the tolerance and maximum number of evaluations appears to make no great difference. Alternatively, is there a better approach to coding a solution to this type of problem?
I realise that questions about the general approach are probably better suited to stats.stackexchange.com, but I've posted here since I suspect there may be something that I'm overlooking with the coding itself.
EDIT:
For the A -> B case, nested integrate returns a solution similar to the first adaptIntegrate solution. For the A -> C case, it returns Error in integrate(function(ky) { : the integral is probably divergent.
g <- function(Bx, By, Ax, Ay) {
r <- sqrt((Ax - Bx)^2 + (Ay - By)^2)
ifelse(r > 1.25, 0, 1)
}
integrate(function(Ay) {
sapply(Ay, function(Ay) {
integrate(function(Ax) {
sapply(Ax, function(Ax) {
integrate(function(By) {
sapply(By, function(By) {
integrate(function(Bx) g(Bx, By, Ax, Ay), 1.5, 2.5)$value # Bx
})
}, -0.5, 0.5)$value # By
})
}, -0.5, 0.5)$value # Ax
})
}, -0.5, 0.5)$value # Ay
# [1] 0.019593

The reason for this seems to be the way adaptIntegrate works since, clearly, the only thing that you change is the order of integration. Nonidentical results are likely because of approximate integration alone (see the first response here), but this seems to be more like a bug.
Here are the values of r when computing f(xmin=1.5, xmax=2.5, ymin=-0.5, ymax=0.5)
and f(xmin=-0.5, xmax=0.5, ymin=1.5, ymax=2.5)
so there must be something going on inside the function since the range of values differs dramatically.
One alternative for this is Monte Carlo integration which is good in this case since your points are distributed uniformly.
MCI <- function(Ax, Ay, Bx, By, N, r) {
d <- sapply(list(Ax, Ay, Bx, By), function(l) runif(N, l[1], l[2]))
sum(sqrt((d[, 1] - d[, 3])^2 + (d[, 2] - d[, 4])^2) <= r) / N
}
set.seed(123)
MCI(c(-0.5, 0.5), c(-0.5, 0.5), c(1.5, 2.5), c(-0.5, 0.5), 100000, 1.25)
# [1] 0.0194
MCI(c(-0.5, 0.5), c(-0.5, 0.5), c(-0.5, 0.5), c(1.5, 2.5), 100000, 1.25)
# [1] 0.01929

Generally distance measures are (x1-x2)^2+(y1-y2)^2. Can you explain why you are subtracting the x's from y's when constructing r? Consider the alternate version:
f <- function(xmin, xmax, ymin, ymax) {
adaptIntegrate(function(x) {
r <- sqrt((x[4] - x[3])^2 + (x[2] - x[1])^2)
ifelse(r > 1.25, 0, 1)
},
lowerLimit=c(-0.5, -0.5, xmin, ymin),
upperLimit=c(0.5, 0.5, xmax, ymax),
maxEval=1e5)
}
f(xmin=1.5, xmax=2.5, ymin=-0.5, ymax=0.5)
#-------------
$integral
[1] 0.01016105
$error
[1] 0.0241325
$functionEvaluations
[1] 100035
$returnCode
[1] 0
#---------
f(xmin=-0.5, xmax=0.5, ymin=1.5, ymax=2.5)
#---------
$integral
[1] 0.01016105
$error
[1] 0.0241325
$functionEvaluations
[1] 100035
$returnCode
[1] 0

The maintainer of the R cubature package (Naras) has informed me that the Cubature C library gives the same results as I report in the question above, and that this is unlikely to be a bug; rather, the h-adaptive cubature routine (to which the R package is an interface) is in some cases less accurate than Cubature's p-adaptive routine, which doubles the number of sampling points in appropriate regions.
Naras also provided the following julia code that demonstrates consistent pcubature solutions for the two cases presented in my question (elements of the returned value are the estimated integral followed by the estimated absolute error).
using Cubature
# integrand
f = x -> ifelse(sqrt((x[3] - x[1])^2 + (x[4] - x[2])^2) > 1.25, 0, 1)
# A to B case
pcubature(f, [-0.5, -0.5, 1.5, -0.5], [0.5, 0.5, 2.5, 0.5], abstol=1e-5)
# (0.019593408732917292,3.5592555263398717e-6)
# A to C case
pcubature(f, [-0.5, -0.5, -0.5, 1.5], [0.5, 0.5, 0.5, 2.5], abstol=1e-5)
# (0.019593408732918302,3.559255527241928e-6)

Related

Simple linear transformation of variable in R: changing the scope of a variable. How to make it right?

I am trying to change the value range of a variable (array, set of values) while keeping its properties. I don't know the exact name in math, but I mean such a kind of transformation that the variable array has exactly the same properties, the spacing between the values is the same, but the range is different. Maybe the code below will explain what I mean.
I just want to "linearly transpose" (or smth?) values to some other range and the distribution should remain same. In other words - I'll just change the scope of the variable using the regression equation y = a * x + b. I assume that the transformation will be completely linear, the correlation between the variables is exactly 1, and I calculate new variable (array) from a regression equation, actually a system of equations where I simply substitute the maximum ranges of both variables:
minimum.y1 = minimum.x1 * a + b
maximum.y2 = maximum.x2 * a + b
from which I can work out the following code to obtain a and b coefficients:
# this is my input variable
x <- c(-1, -0.5, 0, 0.5, 1)
# this is the range i want to obtain
y.pred <- c(1,2,3,4,5)
max_y = 5
min_y = 1
min_x = min(x)
max_x = max(x)
c1 = max_x-min_x
c2 = max_y-min_y
a.coeff = c2/c1
b.coeff = a.coeff-min_x
y = x * a.coeff + b.coeff
y
# hey, it works! :)
[1] 1 2 3 4 5
the correlation between the variable before and after the transformation is exactly 1. So we have a basis for further action. Let's get it as a function:
linscale.to.int <- function(max.lengt, vector) {
max_y = max.lengt
min_y = 1
min_x = min(vector)
max_x = max(vector)
c1 = max_x-min_x
c2 = max_y-min_y
a.coeff = c2/c1
b.coeff = a.coeff-min_x
return(vector * a.coeff + b.coeff)
}
x <- c(-1, -0.5, 0, 0.5, 1)
linscale.to.int(5,x)
[1] 1 2 3 4 5
and it works again. But here's the thing: when i aplly this function to random distribution, like this:
x.rand <- rnorm(50)
y.rand <- linscale.to.int(5,x.rand)
plot(x.rand, y.rand)
or better seable this:
x.rand <- rnorm(500)
y.rand <- linscale.to.int(20,x.rand)
plot(x.rand, y.rand)
I get the values of the second variable completely out of range; it should be between 1 and 20 but i get scope of valuest about -1 to 15:
And now the question arises - what am I doing wrong here? Where do I go wrong with such a transformation?
What you are trying to do is very straightforward using rescale from the scales package (which you will already have installed if you have ggplot2 / tidyverse installed). Simply give it the new minimum / maximum values:
x <- c(-1, -0.5, 0, 0.5, 1)
scales::rescale(x, c(1, 5))
#> [1] 1 2 3 4 5
If you want to have your own function written in base R, the following one-liner should do what you want:
linscale_to_int <- function(y, x) (x - min(x)) * (y - 1) / diff(range(x)) + 1
(Note that it is good practice in R to avoid periods in function names because this can cause confusion with S3 method dispatch)
Testing, we have:
x <- c(-1, -0.5, 0, 0.5, 1)
linscale_to_int(5, x)
#> [1] 1 2 3 4 5
x.rand <- rnorm(50)
y.rand <- linscale_to_int(5, x.rand)
plot(x.rand, y.rand)
y.rand <- linscale_to_int(20, x.rand)
plot(x.rand, y.rand)
Created on 2022-08-31 with reprex v2.0.2

How do I optimize a function in R for all values of y?

So I'm trying to plot a function into a graph and ultimately I landed on the easiest option being to give alpha-values and optimize velocity for the x_position values. The problem is, I'm doing something wrong with the optimization.
Here's what I've got thus far:
y <- seq(0, 55, 0.1)
x_position <- function(alpha,velo)
{velo*cos(alpha)*((velo*sin(alpha)+sqrt((velo^2*sin(alpha))^2+2*16.5*9.81)/9.81))}
x <- optimize(x_position,c(1,1000),alpha=y,maximum=TRUE)$objective
Basically, I'm trying to make "y" a vector for the angle and "x" a vector of maximum function value for each angle value so that I could then plot the x,y vector for the function. The problem is, I can't get the x-vector right. For whatever reason it just keeps telling me "invalid function value in 'optimize'". Changing the optimization interval doesn't seem to accomplish anything and I'm out of ideas. The function seems to work just fine when I tested it with e.g. alpha 55 and velocity 10.
y <- seq(0, 55, 0.1)
x_position <- function(velo,alpha){
velo*cos(alpha)*((velo*sin(alpha)+sqrt((velo^2*sin(alpha))^2+2*16.5*9.81)/9.81))
}
optimize(f = x_position, interval = c(1,1000), maximum=TRUE, alpha = y[1])
#> $maximum
#> [1] 999.9999
#>
#> $objective
#> [1] 1834.098
a <- sapply(y, function(y) optimize(f = x_position, interval = c(1,1000),
maximum=TRUE, alpha = y)$objective)
head(a)
#> [1] 1834.098 10225190.493 20042734.667 29061238.316 36921162.118
#> [6] 43309155.705
Created on 2021-09-29 by the reprex package (v2.0.1)

r How do I rescale a range of numbers with these constraints?

I need to rescale a series of numbers with certain constraints.
Let's say I have a vector like this:
x <- c(0.5, 0.3, 0.6, 0.4, 0.9, 0.1, 0.2, 0.3, 0.6)
The sum of x must be 6. Right now the sum of x = 3.9.
The numbers cannot be lower than 0
The numbers cannot be higher than 1
I know how to do 1 and 2+3 separately, but not together.
How do I rescale this?
EDIT: As was tried by r2evans, preferably the relative relationships of the numbers is preserved
I don't know that this can be done with a simple expression, but we can optimize our way through it:
opt <- optimize(function(z) abs(6 - sum( z + (1-z) * (x - min(x)) / diff(range(x)) )),
lower=0, upper=1)
opt
# $minimum
# [1] 0.2380955
# $objective
# [1] 1.257898e-06
out <- ( opt$minimum + (1-opt$minimum) * (x - min(x)) / diff(range(x)) )
out
# [1] 0.6190477 0.4285716 0.7142858 0.5238097 1.0000000 0.2380955 0.3333335 0.4285716 0.7142858 1.0000000
sum(out)
# [1] 6.000001
Because that is note perfectly 6, we can do one more step to safeguard it:
out <- out * 6/sum(out)
out
# [1] 0.6190476 0.4285715 0.7142857 0.5238096 0.9999998 0.2380954 0.3333335 0.4285715 0.7142857 0.9999998
sum(out)
# [1] 6
This process preserves the relative relationships of the numbers. If there are more "low" numbers than "high" numbers, scaling so that the sum is 6 will bring the higher numbers above 1. To compensate for that, we shift the lower-end (z in my code), so that all numbers are nudged up a little (but the lower numbers will be nudged up proportionately more).
The results should always be that the numbers are in [opt$minimum,1], and the sum will be 6.
Should be possible with a while loop to increase the values of x (to an upper limit of 1)
x <- c(0.5, 0.3, 0.6, 0.4, 0.9, 0.1, 0.2, 0.3, 0.6)
current_sum = sum(x)
target_sum = 6
while (!current_sum == target_sum) {
print(current_sum)
perc_diff <- (target_sum - current_sum) / target_sum
x <- x * (1 + perc_diff)
x[which(x > 1)] <- 1
current_sum = sum(x)
}
x <- c(0.833333333333333, 0.5, 1, 0.666666666666667, 1, 0.166666666666667,
0.333333333333333, 0.5, 1)
There is likely a more mathematical way

Minimising area under the ROC curve to optimise the parameters of a polynomial predictor with optim

My predictor (x) has U-shaped distribution in relation to the binary outcome (y), with positive outcomes at both low and high values of x, leading to a biconcave roc curve with a poor area under the curve (auc).
To maximise its ability to discriminate the outcome, I am trying to optimise the parameters of a second grade polynomial of x, by using optim and 1 - auc as the cost function to minimise.
x = c(13,7,7,7,1,100,3,4,4,2,2,7,14,8,3,14,5,12,8,
13,9,4,9,4,8,3,13,9,4,4,5,9,10,10,7,6,12,7,2,
6,6,4,3,2,3,10,5,2,5,8,3,5,4,2,7,5,7,6,79,9)
y = c(0,0,1,0,0,1,0,0,1,1,0,1,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,1,0)
theta = c(0, 0, 0)
min_auc <- function(theta, x, y) {
(1 - roc(y, (theta[1] + theta[2]*x + theta[3]*x^2))$auc)
}
optim(theta, min_auc, x = x, y = y)
The results are as follow:
$par
[1] 0.0 0.1 0.0
$value
[1] 0.4380054
$counts
function gradient
8 NA
$convergence
[1] 0
$message
NULL
However, from a manual definition of the parameters, I know that min_auc can be further minimised.
theta = c(0, -40, 1)
(1 - roc(y, (theta[1] + theta[2]*x + theta[3]*(x^2)))$auc)
[1] 0.2762803
Could anyone explain to me what I am doing wrong, please? Is it possibly due to a non-convex cost function?
One possibility is it's a collinearity problem. Scaling the inputs helps:
min_auc <- function(theta, x, y) {
(1 - roc(y, (theta[1] + theta[2]*scale(x) + theta[3]*scale(x)^2))$auc)
}
optim(theta, min_auc, x = x, y = y)
# $par
# [1] -0.02469136 -0.03117284 0.11049383
#
# $value
# [1] 0.2762803
#
# $counts
# function gradient
# 30 NA
#
# $convergence
# [1] 0
#
# $message
# NULL
#
Another potential problem is that the surface over which you're optimizing has some flat spots. Let's say, for example, that we fix the intercept in this equation to -2. This is about what you get if you do qlogis(mean(y)). Then, you're only optimizing over 2 parameters so the surface is easier to see. Here's what it looks like with the two remaining theta terms on the two horizontal axes and the 1-AUC value on the y-axis.
min_auc <- function(theta, x, y) {
(1 - roc(y, (-2 + theta[1]*scale(x) + theta[2]*scale(x)^2))$auc)
}
s <- seq(-.25, .25, length=50)
o <- outer(s, s, Vectorize(function(z,w)min_auc(c(z,w),x,y)))
library(plotly)
plot_ly(x = ~s, y = ~s, z = ~o) %>% add_surface()
As you may have noticed above, there is no unique solution to the problem. There are lots of solutions that seem to get to the minimum value.

Inconsistent behavior of gOverlaps and gTouches when polygons intersect at a point

I have two examples of SpatialPolygons (from package sp) that intersect at a single point. According to the help pages for gOverlaps and gTouches (from package rgeos), in this situation gOverlaps should return FALSE and gTouches should return TRUE. In other words, gOverlaps should only be true when the two polygons share some non-zero area, and gTouches should only be true when the two polygons only touch at one or more points along their boundary. This behavior holds for Example A below, but the opposite occurs for Example B below.
First, here's a helper function to create SpatialPolygons objects:
library(sp)
library(rgeos)
spatial.polygon <- function(coord.vec, ID)
{
SpatialPolygons(
list(Polygons(list(Polygon(matrix(coord.vec, byrow = TRUE, ncol = 2))), ID = ID))
)
}
Now for the examples.
Example A:
Here are a square and a diamond that overlap at exactly one point:
square <-
spatial.polygon(
c(0, 0,
0, 2,
2, 2,
2, 0,
0, 0),
ID = 'square'
)
diamond <-
spatial.polygon(
c(3, 0,
2, 1,
3, 2,
4, 1,
3, 0),
ID = 'diamond'
)
Here's a plot of the square and diamond:
Here are the results of various rgeos functions tested on the square and diamond:
> gIntersects(square, diamond) # Should be TRUE. Is TRUE.
# [1] TRUE
> gIntersection(square, diamond) # Should return a point. Returns a point.
# SpatialPoints:
# x y
# 1 2 1
# Coordinate Reference System (CRS) arguments: NA
> gOverlaps(square, diamond) # Should be FALSE. Is FALSE.
# [1] FALSE
> gTouches(square, diamond) # Should be TRUE. Is TRUE.
# [1] TRUE
So, for Example A, gOverlaps and gTouches behave as expected.
Example B:
Here are a rectangle and a trapezoid that intersect at exactly one point:
rectangle <-
spatial.polygon(
c(1.00, 1.46,
2.10, 1.46,
2.10, 0.70,
1.00, 0.70,
1.00, 1.46),
ID = 'rectangle'
)
trapezoid <-
spatial.polygon(
c(1.5, 5.0,
5.0, 5.0,
5.0, 0.7,
2.5, 0.7,
1.5, 2.6,
1.5, 5.0),
ID = 'trapezoid'
)
Here's a plot of the rectangle and trapezoid:
You can verify mathematically that the rectangle and the trapezoid do in fact intersect at exactly one point. Here are the results of various rgeos functions tested on the rectangle and trapezoid:
> gIntersects(rectangle, trapezoid) # Should be TRUE. Is TRUE.
# [1] TRUE
> gIntersection(rectangle, trapezoid) # Should return a point. Returns a point.
# SpatialPoints:
# x y
# 1 2.1 1.46
# Coordinate Reference System (CRS) arguments: NA
> gOverlaps(rectangle, trapezoid) # Should be FALSE. Is TRUE.
# [1] TRUE
> gTouches(rectangle, trapezoid) # Should be TRUE. Is FALSE.
# [1] FALSE
So, for Example B, the results of gOverlaps and gTouches are the opposite of the expected results.
The Question:
For Example B, why are the results of gOverlaps and gTouches the opposite of the expected results? Could this be a problem with machine precision?

Resources