Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    primarykey
    data
    text
    <p>OK, here is an option that implements various spatial correlation structures in <code>gls</code>/<code>nlme</code> with haversine distance. </p> <p>The various <code>corSpatial</code>-type classes already have machinery in place to construct a correlation matrix from spatial covariates, given a distance metric. Unfortunately, <code>dist</code> does not implement haversine distance, and <code>dist</code> is the function called by <code>corSpatial</code> to compute a distance matrix from the spatial covariates.</p> <p>The distance matrix computations are performed in <code>getCovariate.corSpatial</code>. A modified form of this method will pass the appropriate distance to other methods, and the majority of methods will not need to be modified.</p> <p>Here, I create a new <code>corStruct</code> class, <code>corHaversine</code>, and modify only <code>getCovariate</code> and one other method (<code>Dim</code>) that determines which correlation function is used. Those methods which do not need modification, are copied from equivalent <code>corSpatial</code> methods. The (new) <code>mimic</code> argument in <code>corHaversine</code> takes the name of the spatial class with the correlation function of interest: by default, it is set to "<code>corSpher</code>".</p> <p>Caveat: beyond ensuring that this code runs for spherical and Gaussian correlation functions, I haven't really done a lot of checking.</p> <pre><code>#### corHaversine - spatial correlation with haversine distance # Calculates the geodesic distance between two points specified by radian latitude/longitude using Haversine formula. # output in km haversine &lt;- function(x0, x1, y0, y1) { a &lt;- sin( (y1 - y0)/2 )^2 + cos(y0) * cos(y1) * sin( (x1 - x0)/2 )^2 v &lt;- 2 * asin( min(1, sqrt(a) ) ) 6371 * v } # function to compute geodesic haversine distance given two-column matrix of longitude/latitude # input is assumed in form decimal degrees if radians = F # note fields::rdist.earth is more efficient haversineDist &lt;- function(xy, radians = F) { if (ncol(xy) &gt; 2) stop("Input must have two columns (longitude and latitude)") if (radians == F) xy &lt;- xy * pi/180 hMat &lt;- matrix(NA, ncol = nrow(xy), nrow = nrow(xy)) for (i in 1:nrow(xy) ) { for (j in i:nrow(xy) ) { hMat[j,i] &lt;- haversine(xy[i,1], xy[j,1], xy[i,2], xy[j,2]) } } as.dist(hMat) } ## for most methods, machinery from corSpatial will work without modification Initialize.corHaversine &lt;- nlme:::Initialize.corSpatial recalc.corHaversine &lt;- nlme:::recalc.corSpatial Variogram.corHaversine &lt;- nlme:::Variogram.corSpatial corFactor.corHaversine &lt;- nlme:::corFactor.corSpatial corMatrix.corHaversine &lt;- nlme:::corMatrix.corSpatial coef.corHaversine &lt;- nlme:::coef.corSpatial "coef&lt;-.corHaversine" &lt;- nlme:::"coef&lt;-.corSpatial" ## Constructor for the corHaversine class corHaversine &lt;- function(value = numeric(0), form = ~ 1, mimic = "corSpher", nugget = FALSE, fixed = FALSE) { spClass &lt;- "corHaversine" attr(value, "formula") &lt;- form attr(value, "nugget") &lt;- nugget attr(value, "fixed") &lt;- fixed attr(value, "function") &lt;- mimic class(value) &lt;- c(spClass, "corStruct") value } # end corHaversine class environment(corHaversine) &lt;- asNamespace("nlme") Dim.corHaversine &lt;- function(object, groups, ...) { if (missing(groups)) return(attr(object, "Dim")) val &lt;- Dim.corStruct(object, groups) val[["start"]] &lt;- c(0, cumsum(val[["len"]] * (val[["len"]] - 1)/2)[-val[["M"]]]) ## will use third component of Dim list for spClass names(val)[3] &lt;- "spClass" val[[3]] &lt;- match(attr(object, "function"), c("corSpher", "corExp", "corGaus", "corLin", "corRatio"), 0) val } environment(Dim.corHaversine) &lt;- asNamespace("nlme") ## getCovariate method for corHaversine class getCovariate.corHaversine &lt;- function(object, form = formula(object), data) { if (is.null(covar &lt;- attr(object, "covariate"))) { # if object lacks covariate attribute if (missing(data)) { # if object lacks data stop("need data to calculate covariate") } covForm &lt;- getCovariateFormula(form) if (length(all.vars(covForm)) &gt; 0) { # if covariate present if (attr(terms(covForm), "intercept") == 1) { # if formula includes intercept covForm &lt;- eval(parse(text = paste("~", deparse(covForm[[2]]),"-1",sep=""))) # remove intercept } # can only take covariates with correct names if (length(all.vars(covForm)) &gt; 2) stop("corHaversine can only take two covariates, 'lon' and 'lat'") if ( !all(all.vars(covForm) %in% c("lon", "lat")) ) stop("covariates must be named 'lon' and 'lat'") covar &lt;- as.data.frame(unclass(model.matrix(covForm, model.frame(covForm, data, drop.unused.levels = TRUE) ) ) ) covar &lt;- covar[,order(colnames(covar), decreasing = T)] # order as lon ... lat } else { covar &lt;- NULL } if (!is.null(getGroupsFormula(form))) { # if groups in formula extract covar by groups grps &lt;- getGroups(object, data = data) if (is.null(covar)) { covar &lt;- lapply(split(grps, grps), function(x) as.vector(dist(1:length(x) ) ) ) # filler? } else { giveDist &lt;- function(el) { el &lt;- as.matrix(el) if (nrow(el) &gt; 1) as.vector(haversineDist(el)) else numeric(0) } covar &lt;- lapply(split(covar, grps), giveDist ) } covar &lt;- covar[sapply(covar, length) &gt; 0] # no 1-obs groups } else { # if no groups in formula extract distance if (is.null(covar)) { covar &lt;- as.vector(dist(1:nrow(data) ) ) } else { covar &lt;- as.vector(haversineDist(as.matrix(covar) ) ) } } if (any(unlist(covar) == 0)) { # check that no distances are zero stop("cannot have zero distances in \"corHaversine\"") } } covar } # end method getCovariate environment(getCovariate.corHaversine) &lt;- asNamespace("nlme") </code></pre> <p>To test that this runs, given range parameter of 1000:</p> <pre><code>## test that corHaversine runs with spherical correlation (not testing that it WORKS ...) library(MASS) set.seed(1001) sample_data &lt;- data.frame(lon = -121:-22, lat = -50:49) ran &lt;- 1000 # 'range' parameter for spherical correlation dist_matrix &lt;- as.matrix(haversineDist(sample_data)) # haversine distance matrix # set up correlation matrix of response corr_matrix &lt;- 1-1.5*(dist_matrix/ran)+0.5*(dist_matrix/ran)^3 corr_matrix[dist_matrix &gt; ran] = 0 diag(corr_matrix) &lt;- 1 # set up covariance matrix of response sigma &lt;- 2 # residual standard deviation cov_matrix &lt;- (diag(100)*sigma) %*% corr_matrix %*% (diag(100)*sigma) # correlated response # generate response sample_data$y &lt;- mvrnorm(1, mu = rep(0, 100), Sigma = cov_matrix) # fit model gls_haversine &lt;- gls(y ~ 1, correlation = corHaversine(form=~lon+lat, mimic="corSpher"), data = sample_data) summary(gls_haversine) # Correlation Structure: corHaversine # Formula: ~lon + lat # Parameter estimate(s): # range # 1426.818 # # Coefficients: # Value Std.Error t-value p-value # (Intercept) 0.9397666 0.7471089 1.257871 0.2114 # # Standardized residuals: # Min Q1 Med Q3 Max # -2.1467696 -0.4140958 0.1376988 0.5484481 1.9240042 # # Residual standard error: 2.735971 # Degrees of freedom: 100 total; 99 residual </code></pre> <p>Testing that it runs with Gaussian correlation, with range parameter = 100:</p> <pre><code>## test that corHaversine runs with Gaussian correlation ran = 100 # parameter for Gaussian correlation corr_matrix_gauss &lt;- exp(-(dist_matrix/ran)^2) diag(corr_matrix_gauss) &lt;- 1 # set up covariance matrix of response cov_matrix_gauss &lt;- (diag(100)*sigma) %*% corr_matrix_gauss %*% (diag(100)*sigma) # correlated response # generate response sample_data$y_gauss &lt;- mvrnorm(1, mu = rep(0, 100), Sigma = cov_matrix_gauss) # fit model gls_haversine_gauss &lt;- gls(y_gauss ~ 1, correlation = corHaversine(form=~lon+lat, mimic = "corGaus"), data = sample_data) summary(gls_haversine_gauss) </code></pre> <p>With <code>lme</code>:</p> <pre><code>## runs with lme # set up data with group effects group_y &lt;- as.vector(sapply(1:5, function(.) mvrnorm(1, mu = rep(0, 100), Sigma = cov_matrix_gauss))) group_effect &lt;- rep(-2:2, each = 100) group_y = group_y + group_effect group_name &lt;- factor(group_effect) lme_dat &lt;- data.frame(y = group_y, group = group_name, lon = sample_data$lon, lat = sample_data$lat) # fit model lme_haversine &lt;- lme(y ~ 1, random = ~ 1|group, correlation = corHaversine(form=~lon+lat, mimic = "corGaus"), data = lme_dat, control=lmeControl(opt = "optim") ) summary(lme_haversine) # Correlation Structure: corHaversine # Formula: ~lon + lat | group # Parameter estimate(s): # range # 106.3482 # Fixed effects: y ~ 1 # Value Std.Error DF t-value p-value # (Intercept) -0.0161861 0.6861328 495 -0.02359033 0.9812 # # Standardized Within-Group Residuals: # Min Q1 Med Q3 Max # -3.0393708 -0.6469423 0.0348155 0.7132133 2.5921573 # # Number of Observations: 500 # Number of Groups: 5 </code></pre>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. VO
      singulars
      1. This table or related slice is empty.
    2. VO
      singulars
      1. This table or related slice is empty.
    3. VO
      singulars
      1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload