r3dbilinear-interpolation

Obtain function from akima::interp() matrix


Using the interp function (Akima package), it is possible to draw the surface corresponding to the bivariate interpolation of a data set, see example below (from interp documentation):

library(rgl)
data(akima)
# data visualisation
rgl.spheres(akima$x,akima$z , akima$y,0.5,color="red")
rgl.bbox()
# bivariate linear interpolation
# interp:
akima.li <- interp(akima$x, akima$y, akima$z, 
                   xo=seq(min(akima$x), max(akima$x), length = 100),
                   yo=seq(min(akima$y), max(akima$y), length = 100))
# interp surface:
rgl.surface(akima.li$x,akima.li$y,akima.li$z,color="green",alpha=c(0.5))

However, the output is only a list describing a set of points, not a general function.

Question: is there any method to obtain a function z = f(x,y) that matches the previously obtained surface ? I know that it works using interp(akima$x, akima$y, akima$z, xo=A, yo=B), but it is very slow.

In two dimensions, the approxfun() function would do the job, but I could not find the equivalent for multiple parameters interpolation.


Solution

  • If you want a linear interpolation so that the surface cross all points, you will not be able to interpolate with a function z = f(x,y), except if the dataset has been simulated through this kind of function.
    If you are looking for a function z=f(x,y) that matches your point set, you will have to build a model with GLM or GAM for instance. However, this induces that the surface will not cross all points data and there will be some residuals.

    As I use to work with spatial datasets, which means x and y coordinates with a z observation, I will give you some clues in this way.

    First, I prepare a dataset for interpolation:

    library(rgl)
    library(akima)
    library(dplyr)
    library(tidyr)
    
    data(akima)
    data.akima <- as.data.frame(akima)
    # data visualisation
    rgl.spheres(akima$x, akima$z , akima$y,0.5,color="red")
    rgl.bbox()
    
    # Dataset for interpolation
    seq_x <- seq(min(akima$x) - 1, max(akima$x) + 1, length.out = 20)
    seq_y <- seq(min(akima$y) - 1, max(akima$y) + 1, length.out = 20)
    data.pred <- dplyr::full_join(data.frame(x = seq_x, by = 1),
                                  data.frame(y = seq_y, by = 1)) %>%
      dplyr::select(-by)
    

    Then, I use your akima interpolation function:

    # bivariate linear interpolation
    # interp:
    akima.li <- interp(akima$x, akima$y, akima$z, 
                       xo=seq_x,
                       yo=seq_y)
    
    # interp surface:
    rgl.surface(akima.li$x,akima.li$y,akima.li$z,color="green",alpha=c(0.5))
    rgl.spheres(akima$x, akima$z , akima$y,0.5,color="red")
    rgl.bbox()
    

    Using rasters

    From now, if you want to get interpolated information on some specific points, you can re-use interp function or decide to work with a rasterized image. Using rasters, you are then able to increase resolution, and get any spatial position information data.

    # Using rasters 
    library(raster)
    r.pred <- raster(akima.li$z, xmn = min(seq_x), xmx = max(seq_x),
           ymn = min(seq_y), ymx = max(seq_y))
    plot(r.pred)
    
    ## Further bilinear interpolations
    ## Double raster resolution
    r.pred.2 <- disaggregate(r.pred, fact = 2, method = "bilinear")
    plot(r.pred.2)
    

    Spatial interpolation (inverse distance interpolation or kriging)

    When thinking in spatial for interpolation, I first think about kriging. This will smooth your surface, thus it will not cross every data points.

    # Spatial inverse distance interpolation
    library(sp)
    library(gstat)
    # Transform data as spatial objects
    data.akima.sp <- data.akima
    coordinates(data.akima.sp) <- ~x+y
    data.pred.sp <- data.pred
    coordinates(data.pred.sp) <- ~x+y
    # Inverse distance interpolation
    # idp is set to 2 as weight for interpolation is :
    # w = 1/dist^idp
    # nmax is set to 3, so that only the 3 closest points are used for interpolation
    pred.idw <- idw(
      formula = as.formula("z~1"),
      locations = data.akima.sp, 
      newdata = data.pred.sp,
      idp = 2,
      nmax = 3)
    
    data.spread.idw <- data.pred %>%
      select(-pred) %>%
      mutate(idw = pred.idw$var1.pred) %>%
      tidyr::spread(key = y, value = idw) %>% 
      dplyr::select(-x)
    
    surface3d(seq_x, seq_y, as.matrix(data.spread.idw), col = "green")
    rgl.spheres(akima$x, akima$y , akima$z, 0.5, color = "red")
    rgl.bbox()
    

    Interpolate using gam or glm

    However, if you want to find a formula like z = f(x,y), you should use GLM or GAM with high degrees of freedom depending on the smooth you hope to see. Another advantage is that you can add other covariates, not only x and y. The model needs to be fitted with a x/y interaction.
    Here an example with a simple GAM smooth:

    # Approximation with a gam model
    library(mgcv)
    gam1 <- gam(z ~ te(x, y), data = data.akima)
    summary(gam1)
    
    plot(gam1)
    data.pred$pred <- predict(gam1, data.pred)
    data.spread <- tidyr::spread(data.pred, key = y, value = pred) %>% 
      dplyr::select(-x)
    
    surface3d(seq_x, seq_y, as.matrix(data.spread), col = "blue")
    rgl.spheres(akima$x, akima$y , akima$z, 0.5, color = "red")
    rgl.bbox()
    

    Does this answer goes in the right direction for you ?