Skip to content

Commit

Permalink
Added new function sl.remap.array() to remap a numeric array (can als…
Browse files Browse the repository at this point in the history
…o be a vector) along one of its dimensions from an old to a new dimension variable vector. Based partly on code from sl.trajectory.remaptime().
  • Loading branch information
helgegoessling committed Mar 4, 2024
1 parent 0e34e22 commit 3ebc5be
Show file tree
Hide file tree
Showing 2 changed files with 246 additions and 0 deletions.
156 changes: 156 additions & 0 deletions R/sl.remap.array.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
sl.remap.array <- function (input.dimvals,input.data,new.dimvals,remap.dim=1,method="linear",extrapolate=FALSE,return.remapinfo=FALSE,verbose=TRUE) {

if (method != "nearestneighbour" && method != "linear") {
stop("'method' must be one of 'nearestneighbour' and 'linear'.")
}

input.N = length(input.dimvals)
new.N = length(new.dimvals)
input.dim = dim(input.data)
if (is.null(input.dim)) {input.data = array(input.data); input.dim = dim(input.data)} # convert vector into 1D array
ND = length(input.dim)
if (ND > 4) {stop("'input.data' must not have more than 4 dimensions")}
if (remap.dim > ND) {stop("'remap.dim' is larger than the number of dimensions of 'input.data'")}
if (input.N != input.dim[remap.dim]) {stop("length of 'input.dimvals' is inconsistent with 'input.data' and 'remap.dim'")}
new.dim = input.dim
new.dim[remap.dim] = new.N

if (new.N > 1) {
if (any(new.dimvals[2:new.N] <= new.dimvals[1:(new.N-1)])) {stop("'new.dimvals' must increase strict monotonously")}
}
if (input.N <= 1) {stop("'input.dimvals' must have at least two elements")}
if (any(input.dimvals[2:input.N] <= input.dimvals[1:(input.N-1)])) {stop("'input.dimvals' must increase strict monotonously")}

if (new.dimvals[new.N] > input.dimvals[input.N]) {
if (new.dimvals[1] > input.dimvals[input.N]) {
if (verbose) {warning("New dimension values completely out of original dimension values.")}
if (!extrapolate) {
if (verbose) {warning("All values will be 'NA'.")}
} else {
if (verbose) {warning("All values will be extrapolated.")}
}
} else {
if (verbose) {warning("Maximum of new dimension values above maximum of original dimension values.")}
if (extrapolate) {
if (verbose) {warning("Values outside original dimension range will be extrapolated.")}
} else {
if (verbose) {warning("Values outside original dimension range will be 'NA'.")}
}
}
}
if (new.dimvals[1] < input.dimvals[1]) {
if (new.dimvals[new.N] < input.dimvals[1]) {
if (verbose) {warning("New dimension values completely out of original dimension values.")}
if (!extrapolate) {
if (verbose) {warning("All values will be 'NA'.")}
} else {
if (verbose) {warning("All values will be extrapolated.")}
}
} else {
if (verbose) {warning("Minimum of new dimension values below minimum of original dimension values.")}
if (extrapolate) {
if (verbose) {warning("Values outside original dimension range will be extrapolated.")}
} else {
if (verbose) {warning("Values outside original dimension range will be 'NA'.")}
}
}
}

weights.left = rep(NA,new.N)
weights.left.ind = rep(NA,new.N)
i.input = 1
for (i.new in 1:new.N) {
while (i.input < (input.N-1) && new.dimvals[i.new] > input.dimvals[i.input+1]) {
i.input = i.input + 1
}
weights.left.ind[i.new] = i.input
weights.left[i.new] = (input.dimvals[i.input+1] - new.dimvals[i.new]) / (input.dimvals[i.input+1] - input.dimvals[i.input])
}
if (!extrapolate) {
weights.left[weights.left < 0 | weights.left > 1] = NA
}
if (method == "nearestneighbour") {
weights.left[weights.left > .5] = 1
weights.left[weights.left <= .5] = 0
}

new.data = array(dim=new.dim)
if (ND == 1) {
for (i.new in 1:new.N) {
wli = weights.left.ind[i.new]
wl = weights.left[i.new]
new.data[i.new] = (input.data[wli]*wl + input.data[wli+1]*(1-wl))
}
}
if (ND == 2 && remap.dim == 1) {
for (i.new in 1:new.N) {
wli = weights.left.ind[i.new]
wl = weights.left[i.new]
new.data[i.new,] = (input.data[wli,]*wl + input.data[wli+1,]*(1-wl))
}
}
if (ND == 2 && remap.dim == 2) {
for (i.new in 1:new.N) {
wli = weights.left.ind[i.new]
wl = weights.left[i.new]
new.data[,i.new] = (input.data[,wli]*wl + input.data[,wli+1]*(1-wl))
}
}
if (ND == 3 && remap.dim == 1) {
for (i.new in 1:new.N) {
wli = weights.left.ind[i.new]
wl = weights.left[i.new]
new.data[i.new,,] = (input.data[wli,,]*wl + input.data[wli+1,,]*(1-wl))
}
}
if (ND == 3 && remap.dim == 2) {
for (i.new in 1:new.N) {
wli = weights.left.ind[i.new]
wl = weights.left[i.new]
new.data[,i.new,] = (input.data[,wli,]*wl + input.data[,wli+1,]*(1-wl))
}
}
if (ND == 3 && remap.dim == 3) {
for (i.new in 1:new.N) {
wli = weights.left.ind[i.new]
wl = weights.left[i.new]
new.data[,,i.new] = (input.data[,,wli]*wl + input.data[,,wli+1]*(1-wl))
}
}
if (ND == 4 && remap.dim == 1) {
for (i.new in 1:new.N) {
wli = weights.left.ind[i.new]
wl = weights.left[i.new]
new.data[i.new,,,] = (input.data[wli,,,]*wl + input.data[wli+1,,,]*(1-wl))
}
}
if (ND == 4 && remap.dim == 2) {
for (i.new in 1:new.N) {
wli = weights.left.ind[i.new]
wl = weights.left[i.new]
new.data[,i.new,,] = (input.data[,wli,,]*wl + input.data[,wli+1,,]*(1-wl))
}
}
if (ND == 4 && remap.dim == 3) {
for (i.new in 1:new.N) {
wli = weights.left.ind[i.new]
wl = weights.left[i.new]
new.data[,,i.new,] = (input.data[,,wli,]*wl + input.data[,,wli+1,]*(1-wl))
}
}
if (ND == 4 && remap.dim == 4) {
for (i.new in 1:new.N) {
wli = weights.left.ind[i.new]
wl = weights.left[i.new]
new.data[,,,i.new] = (input.data[,,,wli]*wl + input.data[,,,wli+1]*(1-wl))
}
}

if (return.remapinfo) {
return(list(data=new.data,remapinfo=list(weights.left.ind=weights.left.ind,weights.left=weights.left)))
} else {
return(list(data=new.data))
}


}
90 changes: 90 additions & 0 deletions man/sl.remap.array.Rd
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
\name{sl.remap.array}
\alias{sl.remap.array}
%- Also NEED an '\alias' for EACH other topic documented here.
\title{
Remap Array
}
\description{
Remap a numeric array along one of its dimensions from an old to a new dimension variable vector.
}
\usage{
sl.remap(input.data, input.dimvals, new.dimvals, remap.dim = 1, method = "linear", extrapolate = FALSE, return.remapinfo = FALSE, verbose = TRUE)
}
%- maybe also 'usage' for other objects documented here.
\arguments{
\item{input.data}{
a numeric array (can also be a vector or matrix) with \code{D <= 4} dimensions specifying the original data values. The dimension specified by \code{remap.dim} must have the same length as \code{input.dimvals}; the remapping is performed along this dimension while the other dimensions are kept.
}
\item{input.dimvals}{
a numeric vector of length \code{N > 1} specifying the original dimension variable values; must increase strict monotonously.
}
\item{new.dimvals}{
a numeric vector of length \code{M >= 1} specifying the new dimension variable values; must increase strict monotonously.
}
\item{remap.dim}{
an integer for which \code{1 <= remap.dim <= D} specifying the dimension along which the data is remapped.
}
\item{method}{
a character specifying the interpolation method. Must be one of \code{"linear"} (default) and \code{"nearestneighbour"}.
}
\item{extrapolate}{
a logical value specifying whether or not to extrapolate to dimension variable values outside the original dimension range. Default is \code{FALSE} which implies that corresponding values will be \code{NA}. If \code{TRUE}, the method specified in the argument \code{method} will be used also for the extrapolation.
}
\item{return.remapinfo}{
a logical value specifying whether or not to return information on the remapping (nearest-neighbour-before indices and weights). Default is \code{FALSE}.
}
\item{verbose}{
a logical value specifying whether to produce warnings, in particular when the new dimension variable axis reaches outside the original dimension variable axis. Default is \code{TRUE}.
}
}
%\details{
%}
\value{
\item{data }{an array with \code{D} dimensions (like the input array \code{input.data}) where all expect the remapped dimension have the same length as in \code{input.data}, but the remapped dimension has the same length as \code{new.dimvals} instead of \code{input.dimvals}.}

If \code{return.remapinfo=TRUE}, an additional list element \code{remapinfo} is returned, with the following sub-elements:
\item{weights.left.ind }{an integer vector of length \code{M} with the indices of the original points directly before the corresponding new points.}
\item{weights.left }{an numeric vector of length \code{M} with the weights given to the points provided in \code{weights.left.ind}; the respective subsequent points were given the weights \code{(1-weights.left)}.}
}
\references{
%% ~put references to the literature/web site here ~
}
\author{
Helge Goessling
}
\note{
This function is similar to \code{sl.trajectory.remaptime} but works on arrays (including vectors) instead of 1-dimensional spatial line objects on spheres (such as trajectories).
}

%% ~Make other sections like Warning with \section{Warning }{....} ~

\seealso{
\code{\link{sl.trajectory.remaptime}}
}

\examples{
# Vector remapping
x = seq(-2, 3, by=0.5)
y = x^2
x.remap = seq(-1, 5, by=0.3)
y.remap.lin = as.vector(sl.remap.array(input.dat=y, input.dimvals=x, new.dimvals=x.remap,
extrapolate=TRUE)$data)
y.remap.nn = as.vector(sl.remap.array(input.dat=y, input.dimvals=x,new.dimvals=x.remap,
extrapolate=TRUE, method="nearestneighbour")$data)

plot(x,y,ylim=c(0,20),type="p",xlim=c(-2,5))
points(x.remap,y.remap.lin,col="red")
points(x.remap,y.remap.nn,col="blue")

# Matrix remapping
dat1 = matrix(seq(0.5,3,by=0.5),ncol=3)
dat2 = sl.remap.array(input.dat=dat1, input.dimvals=1:3, new.dimvals = seq(1.5,3.5,by=0.5),
remap.dim=2, extrapolate=TRUE)

print(dat1)
print(dat2$data)
}
% Add one or more standard keywords, see file 'KEYWORDS' in the
% R documentation directory.
%\keyword{ ~kwd1 }
%\keyword{ ~kwd2 }% __ONLY ONE__ keyword per line

0 comments on commit 3ebc5be

Please sign in to comment.