OSCR

Impaired spatial coding and neuronal hyperactivity in the medial entorhinal cortex of aged APP knock-in mice.

Code ↔ Paper

2 matches between paragraphs of the paper and lines of its authors' code, computed by the harvester (lexical-v1). Click a colored paragraph or line to see its counterpart.

The 2 matches
  1. [1] § STAR★METHODS › QUANTIFICATION AND STATISTICAL ANALYSIS ↔ DHARMa/R/tests.R, lines 402–520 · score 0.53 · zero inflation, glmmTMB, outliers, Pearson, nonparametric, ratios
  2. [2] § STAR★METHODS › QUANTIFICATION AND STATISTICAL ANALYSIS ↔ glmmTMB/R/diagnose.R, lines 1–35 · score 0.51 · zero inflation, Diagnostic, glmmTMB, logit, coefficients, ratios

Paper

Loaded from Europe PMC by your browser, not stored by OSCR: doi.org · Europe PMC

The paper is loaded when this pane is shown.

The authors' code

R · 903 lines · 56 KB · gnu · 1 match

  1. #' DHARMa general residual test
  2. #'
  3. #' Calls uniformity, dispersion and outliers tests.
  4. #'
  5. #' This function is a wrapper for the various test functions implemented in DHARMa. Currently, this function calls the functions [testUniformity], [testDispersion], and [testOutliers]. All other tests (see list below) have to be called by hand.
  6. #'
  7. #' @param simulationOutput an object of class DHARMa, either created via [simulateResiduals] for supported models or by [createDHARMa] for simulations created outside DHARMa, or a supported model. Providing a supported model directly is discouraged, because simulation settings cannot be changed in this case.
  8. #' @param plot if TRUE, plot functions of the tests are called.
  9. #' @author Florian Hartig
  10. #' @seealso [testResiduals], [testUniformity], [testOutliers], [testDispersion], [testZeroInflation], [testGeneric], [testTemporalAutocorrelation], [testSpatialAutocorrelation], [testQuantiles], [testCategorical]
  11. #' @example inst/examples/testsHelp.R
  12. #' @export
  13. testResiduals <- function(simulationOutput, plot = TRUE){
  14. opar = par(mfrow = c(1,3))
  15. on.exit(par(opar))
  16. out = list()
  17. out$uniformity = testUniformity(simulationOutput, plot = plot)
  18. out$dispersion = testDispersion(simulationOutput, plot = plot)
  19. out$outliers = testOutliers(simulationOutput, plot = plot)
  20. #print(out) # do we need it?
  21. return(out)
  22. }
  23. #' Residual tests
  24. #'
  25. #' @details Deprecated, switch your code to using the [testResiduals] function
  26. #'
  27. #' @param simulationOutput an object of class DHARMa, either created via [simulateResiduals] for supported models or by [createDHARMa] for simulations created outside DHARMa, or a supported model. Providing a supported model directly is discouraged, because simulation settings cannot be changed in this case.
  28. #' @author Florian Hartig
  29. #' @export
  30. testSimulatedResiduals <- function(simulationOutput){
  31. message("testSimulatedResiduals is deprecated, switch your code to using the testResiduals function")
  32. testResiduals(simulationOutput)
  33. }
  34. #' Test for overall uniformity
  35. #'
  36. #' This function tests the overall uniformity of the simulated residuals in a DHARMa object.
  37. #'
  38. #' @param simulationOutput an object of class DHARMa, either created via [simulateResiduals] for supported models or by [createDHARMa] for simulations created outside DHARMa, or a supported model. Providing a supported model directly is discouraged, because simulation settings cannot be changed in this case.
  39. #' @param alternative a character string specifying whether the test should test if observations are "greater", "less" or "two.sided" compared to the simulated null hypothesis. See [stats::ks.test] for details.
  40. #' @param plot if TRUE, plots calls [plotQQunif] as well.
  41. #' @details The function applies a [stats::ks.test] for uniformity on the simulated residuals.
  42. #' @author Florian Hartig
  43. #' @seealso [testResiduals], [testUniformity], [testOutliers], [testDispersion], [testZeroInflation], [testGeneric], [testTemporalAutocorrelation], [testSpatialAutocorrelation], [testQuantiles], [testCategorical]
  44. #' @example inst/examples/testsHelp.R
  45. #' @export
  46. testUniformity <- function(simulationOutput, alternative = c("two.sided", "less", "greater"), plot = TRUE){
  47. simulationOutput = ensureDHARMa(simulationOutput, convert = T)
  48. out <- suppressWarnings(ks.test(simulationOutput$scaledResiduals, 'punif', alternative = alternative))
  49. if(plot == T) plotQQunif(simulationOutput = simulationOutput)
  50. return(out)
  51. }
  52. # Experimental
  53. testBivariateUniformity <- function(simulationOutput, alternative = c("two.sided", "less", "greater"), plot = TRUE){
  54. simulationOutput = ensureDHARMa(simulationOutput, convert = T)
  55. #out <- suppressWarnings(ks.test(simulationOutput$scaledResiduals, 'punif', alternative = alternative))
  56. #if(plot == T) plotQQunif(simulationOutput = simulationOutput)
  57. out = NULL
  58. return(out)
  59. }
  60. #' Test for quantiles
  61. #'
  62. #' This function fits quantile regressions on the residuals, and compares their location to the expected location.
  63. #'
  64. #' @param simulationOutput an object of class DHARMa, either created via [simulateResiduals] for supported models or by [createDHARMa] for simulations created outside DHARMa, or a supported model. Providing a supported model directly is discouraged, because simulation settings cannot be changed in this case.
  65. #' @param predictor an optional predictor variable to be used, instead of the predicted response (default). See details.
  66. #' @param rank if TRUE, the values provided in predictor will be rank transformed. This will usually make patterns easier to spot visually, especially if the distribution of the predictor is skewed. If form is a factor, this has no effect.
  67. #' @param quantiles the quantiles to be tested.
  68. #' @param plot if TRUE, the function will create an additional plot.
  69. #' @details The function fits quantile regressions (via package qgam) on the residuals, and compares their location to the expected location (because of the uniform distribution, the expected location is 0.5 for the 0.5 quantile).
  70. #'
  71. #' A significant p-value for the splines means the fitted spline deviates from a flat line at the expected location.
  72. #'
  73. #' The p-values of the intercept and splines are combined into a total p-value via Benjamini & Hochberg adjustment to control the FDR.
  74. #'
  75. #' Predictor can be a formula (e.g. predictor = ~predictor), in which case NAs are handled automatically (recommended). Predictor can also be a variable in your environment (e.g. predictor = data$predictor), but then you need to remove rows that were excluded by the model due to NAs by hand. For more details and a more flexible syntax for predictors, see the help of the argument `form` in [plotResiduals].
  76. #'
  77. #' When plotting (plot = TRUE), the shaded gray areas indicate 95% confidence intervals of the quantile estimates (1.96 * standard error).
  78. #'
  79. #' @author Florian Hartig
  80. #' @example inst/examples/testQuantilesHelp.R
  81. #' @seealso [testResiduals], [testUniformity], [testOutliers], [testDispersion], [testZeroInflation], [testGeneric], [testTemporalAutocorrelation], [testSpatialAutocorrelation], [testQuantiles], [testCategorical]
  82. #' @export
  83. testQuantiles <- function(simulationOutput, predictor = NULL, rank = TRUE,
  84. quantiles = c(0.25,0.5,0.75), plot = TRUE){
  85. if(plot == F){
  86. out = list()
  87. out$data.name = deparse(substitute(simulationOutput))
  88. simulationOutput = ensureDHARMa(simulationOutput, convert = T)
  89. res = simulationOutput$scaledResiduals
  90. if(inherits(predictor, "formula")) predictor = getFormulaPredictors(simulationOutput, predictor)[[1]]
  91. pred = ensurePredictor(simulationOutput, predictor)
  92. if (rank == TRUE) pred = rankTransform(pred)
  93. dat = data.frame(res = simulationOutput$scaledResiduals, pred = pred)
  94. quantileFits <- list()
  95. pval = rep(NA, length(quantiles))
  96. predictions = data.frame(pred = sort(dat$pred))
  97. predictions = cbind(predictions, matrix(ncol = 2 * length(quantiles),
  98. nrow = nrow(dat)))
  99. for(i in 1:length(quantiles)){
  100. datTemp = dat
  101. datTemp$res = datTemp$res - quantiles[i]
  102. # settings for k = the dimension of the basis used to represent the smooth term.
  103. # see https://github.com/mfasiolo/qgam/issues/37
  104. dimSmooth = min(length(unique(datTemp$pred)), 10)
  105. quantResult = try(capture.output(quantileFits[[i]] <-
  106. qgam::qgam(res ~ s(pred, k = dimSmooth),
  107. data = datTemp,
  108. qu = quantiles[i])), silent = T)
  109. if(inherits(quantResult, "try-error")){
  110. message("\n DHARMa: qgam was unable to calculate quantile regression for quantile ",
  111. quantiles[i], ". Possibly too few (unique) data points / predictions. The quantile will be ommited in plots and significance calculations. \n")
  112. } else {
  113. x = summary(quantileFits[[i]])
  114. pval[i] = min(p.adjust(c(x$p.table[1,4], x$s.table[1,4]), method = "BH")) # correction for test on slope and intercept
  115. quantPre = predict(quantileFits[[i]], newdata = predictions, se = T)
  116. predictions[, 2*i] = quantPre$fit + quantiles[i]
  117. predictions[, 2*i + 1] = 1.96*quantPre$se.fit
  118. }
  119. }
  120. out$method = "Test for location of quantiles via qgam"
  121. out$alternative = "both"
  122. out$pvals = pval
  123. out$p.value = min(p.adjust(pval, method = "BH")) # correction for multiple quantile tests
  124. out$predictions = predictions
  125. out$qgamFits = quantileFits
  126. class(out) = "htest"
  127. } else if(plot == T) {
  128. if(is.null(predictor)) {
  129. out <- plotResiduals(simulationOutput = simulationOutput, form = NULL,
  130. rank = rank, quantiles = quantiles, quantreg = TRUE)
  131. } else {
  132. out <- plotResiduals(simulationOutput = simulationOutput, form = predictor,
  133. rank = rank, quantiles = quantiles, quantreg = TRUE)
  134. }
  135. }
  136. return(out)
  137. }
  138. #unif.2017YMi(X, type = c("Q1", "Q2", "Q3"), lower = rep(0, ncol(X)),upper = rep(1, ncol(X)))
  139. #' Test for outliers
  140. #'
  141. #' This function tests if the number of observations outside the simulation envelope are larger or smaller than expected
  142. #'
  143. #' @param simulationOutput an object of class DHARMa, either created via [simulateResiduals] for supported models or by [createDHARMa] for simulations created outside DHARMa, or a supported model. Providing a supported model directly is discouraged, because simulation settings cannot be changed in this case.
  144. #' @param alternative a character string specifying whether the test should test if observations are "greater", "less" or "two.sided" (default) compared to the simulated null hypothesis.
  145. #' @param margin whether to test for outliers only at the lower, only at the upper, or both sides (default) of the simulated data distribution.
  146. #' @param type either default, bootstrap or binomial. See details.
  147. #' @param nBoot number of bootstrap replicates. Only used at type = "bootstrap".
  148. #' @param plot if TRUE, the function will create an additional plot.
  149. #' @param plotBoostrap if plot should be produced of outlier frequencies calculated under the bootstrap.
  150. #' @details DHARMa residuals are created by simulating from the fitted model, and comparing the simulated values to the observed data. It can occur that all simulated values are higher or smaller than the observed data, in which case they get the residual value of 0 and 1, respectively. I refer to these values as simulation outliers, or simply outliers.
  151. #'
  152. #' Because no data was simulated in the range of the observed values, we don't know "how strongly" these values deviate from the model expectation, so the term "outlier" should be used with a grain of salt. It is not a judgment about the magnitude of the residual deviation, but simply a dichotomous sign that we are outside the simulated range. Moreover, the number of outliers will decrease as we increase the number of simulations.
  153. #'
  154. #' To test if the outliers are a concern, testOutliers implements 2 options (bootstrap, binomial), which can be chosen via the parameter "type". The third option (default) chooses bootstrap for integer-valued distributions with nObs < 500, and else binomial.
  155. #'
  156. #' The binomial test considers that under the null hypothesis that the model is correct, and for continuous distributions (i.e. data and the model distribution are identical and continuous), the probability that a given observation is higher than all simulations is 1/(nSim +1), and binomial distributed. The testOutlier function can test this null hypothesis via type = "binomial". In principle, it would be nice if we could extend this idea to integer-valued distributions, which are randomized via the PIT procedure (see [simulateResiduals]); the rate of "true" outliers is more difficult to calculate, and in general not 1/(nSim +1). The testOutlier function implements a small tweak that calculates the rate of residuals that are closer than 1/(nSim+1) to the 0/1 border, which roughly occur at a rate of nData /(nSim +1). This approximate value, however, is generally not exact, and may be particularly off non-bounded integer-valued distributions (such as Poisson or Negative Binomial).
  157. #'
  158. #' For this reason, the testOutlier function implements an alternative procedure that uses the bootstrap to generate a simulation-based expectation for the outliers. It is recommended to use the bootstrap for integer-valued distributions (and integer-valued only, because it has no advantage for continuous distributions, ideally with reasonably high values of nSim and nBoot (I recommend at least 1000 for both)). Because of the high runtime, however, this option is switched off for type = default when nObs > 500.
  159. #'
  160. #' Both binomial and bootstrap generate a null expectation, and then test for an excess or lack of outliers. Per default, testOutliers() looks for both, so if you get a significant p-value, you have to check if you have too many or too few outliers. An excess of outliers is to be interpreted as too many values outside the simulation envelope. This could be caused by overdispersion, or by what we classically call outliers. A lack of outliers would be caused, for example, by underdispersion.
  161. #'
  162. #'
  163. #' @author Florian Hartig
  164. #' @seealso [testResiduals], [testUniformity], [testOutliers], [testDispersion], [testZeroInflation], [testGeneric], [testTemporalAutocorrelation], [testSpatialAutocorrelation], [testQuantiles], [testCategorical]
  165. #' @export
  166. testOutliers <- function(simulationOutput, alternative = c("two.sided", "greater", "less"), margin = c("both", "upper", "lower"), type = c("default","bootstrap", "binomial"), nBoot = 100, plot = TRUE, plotBoostrap = FALSE){
  167. # check inputs
  168. alternative = match.arg(alternative)
  169. margin = match.arg(margin)
  170. type = match.arg(type)
  171. data.name = deparse(substitute(simulationOutput)) # remember: needs to be called before ensureDHARMa
  172. simulationOutput = ensureDHARMa(simulationOutput, convert = "Model")
  173. if(type == "default"){
  174. if(simulationOutput$integerResponse == FALSE) type = "binomial"
  175. else{
  176. if(simulationOutput$nObs > 500) type = "binomial"
  177. else type = "bootstrap"
  178. }
  179. }
  180. # using the binomial test, not exact
  181. if(type == "binomial"){
  182. # calculation of outliers
  183. if(margin == "both") outliers = sum(simulationOutput$scaledResiduals < (1/(simulationOutput$nSim+1))) + sum(simulationOutput$scaledResiduals > (1-1/(simulationOutput$nSim+1)))
  184. if(margin == "upper") outliers = sum(simulationOutput$scaledResiduals > (1-1/(simulationOutput$nSim+1)))
  185. if(margin == "lower") outliers = sum(simulationOutput$scaledResiduals < (1/(simulationOutput$nSim+1)))
  186. # calculations of trials and H0
  187. outFreqH0 = 1/(simulationOutput$nSim +1) * ifelse(margin == "both", 2, 1)
  188. trials = simulationOutput$nObs
  189. out = binom.test(outliers, trials, p = outFreqH0, alternative = alternative)
  190. # overwrite information in binom.test
  191. out$method = "DHARMa outlier test based on exact binomial test with approximate expectations"
  192. out$data.name = data.name
  193. out$margin = margin
  194. names(out$statistic) = paste("outliers at", margin, "margin(s)")
  195. names(out$parameter) = "observations"
  196. names(out$estimate) = paste("frequency of outliers (expected:", out$null.value,")")
  197. if (simulationOutput$integerResponse == T & out$p.value < 0.05) message("DHARMa:testOutliers with type = binomial may have inflated Type I error rates for integer-valued distributions. To get a more exact result, it is recommended to re-run testOutliers with type = 'bootstrap'. See ?testOutliers for details")
  198. if(plot == T) {
  199. hist(simulationOutput, main = "")
  200. main = ifelse(out$p.value <= 0.05,
  201. "Outlier test significant",
  202. "Outlier test n.s.")
  203. title(main = main, cex.main = 1,
  204. col.main = ifelse(out$p.value <= 0.05, "red", "black"))
  205. }
  206. } else {
  207. if(margin == "both") outliers = mean(simulationOutput$scaledResiduals == 0) +
  208. mean(simulationOutput$scaledResiduals == 1)
  209. if(margin == "upper") outliers = mean(simulationOutput$scaledResiduals == 1)
  210. if(margin == "lower") outliers = mean(simulationOutput$scaledResiduals == 0)
  211. # Bootstrapping to compare to expected
  212. simIndices = 1:simulationOutput$nSim
  213. nSim = simulationOutput$nSim
  214. if(simulationOutput$refit == T){
  215. simResp = simulationOutput$refittedResiduals
  216. } else {
  217. simResp = simulationOutput$simulatedResponse
  218. }
  219. resMethod = simulationOutput$method
  220. resInteger = simulationOutput$integerResponse
  221. if (nBoot > nSim){
  222. message("DHARMa::testOutliers: nBoot > nSim does not make much sense, thus changed to nBoot = nSim. If you want to increase nBoot, increase nSim in DHARMa::simulateResiduals as well.")
  223. nBoot = nSim
  224. }
  225. frequBoot <- rep(NA, nBoot)
  226. for (i in 1:nBoot){
  227. #sel = -i
  228. sel = sample(simIndices[-i], size = nSim, replace = T)
  229. residuals <- getQuantile(simulations = simResp[,sel],
  230. observed = simResp[,i],
  231. integerResponse = resInteger,
  232. method = resMethod)
  233. if(margin == "both") frequBoot[i] = mean(residuals == 1) + mean(residuals == 0)
  234. else if(margin == "upper") frequBoot[i] = mean(residuals == 1)
  235. else if(margin == "lower") frequBoot[i] = mean(residuals == 0)
  236. }
  237. out = list()
  238. class(out) = "htest"
  239. out$alternative = alternative
  240. out$p.value = getP(frequBoot, outliers, alternative = alternative)
  241. out$conf.int = quantile(frequBoot, c(0.025, 0.975))
  242. out$data.name = data.name
  243. out$margin = margin
  244. out$method = "DHARMa bootstrapped outlier test"
  245. out$statistic = outliers * simulationOutput$nObs
  246. names(out$statistic) = paste("outliers at", margin, "margin(s)")
  247. out$parameter = simulationOutput$nObs
  248. names(out$parameter) = "observations"
  249. out$estimate = outliers
  250. names(out$estimate) = paste("outlier frequency (expected:", mean(frequBoot),")")
  251. if(plotBoostrap == T){
  252. hist(frequBoot, xlim = range(frequBoot, outliers), col = "lightgrey", main = "Bootstrapped outlier frequency")
  253. abline(v = mean(frequBoot), col = 1, lwd = 2)
  254. abline(v = outliers, col = "red", lwd = 2)
  255. # legend("center", c(paste("p=", round(out$p.value, digits = 5)), paste("Deviation ", ifelse(out$p.value < 0.05, "significant", "n.s."))), text.col = ifelse(out$p.value < 0.05, "red", "black" ))
  256. }
  257. if(plot == T) {
  258. hist(simulationOutput, main = "")
  259. main = ifelse(out$p.value <= 0.05,
  260. "Outlier test significant",
  261. "Outlier test n.s.")
  262. title(main = main, cex.main = 1,
  263. col.main = ifelse(out$p.value <= 0.05, "red", "black"))
  264. }
  265. }
  266. return(out)
  267. }
  268. #' Test for categorical dependencies
  269. #'
  270. #' This function tests if there are problems in a res ~ group structure. It performs two tests: test for within-group uniformity, and test for between-group homogeneity of variances.
  271. #'
  272. #' @param simulationOutput an object of class DHARMa, either created via [simulateResiduals] for supported models or by [createDHARMa] for simulations created outside DHARMa, or a supported model. Providing a supported model directly is discouraged, because simulation settings cannot be changed in this case.
  273. #' @param catPred a categorical predictor with the same dimensions as the residuals in simulationOutput. If specified as formula, e.g. catPred = ~group, NAs are handled automatically (recommended).
  274. #' @param quantiles whether to draw the quantile lines.
  275. #' @param plot if TRUE, the function will create an additional plot.
  276. #' @param ... additional arguments to boxplot.
  277. #' @details The function tests for two common problems: are residuals within each group distributed according to model assumptions, and is the variance between groups heterogeneous.
  278. #'
  279. #' The test for within-group uniformity is performed via multiple KS-tests, with adjustment of p-values for multiple testing. If the plot is drawn, problematic groups are highlighted in red, and a corresponding message is displayed in the plot.
  280. #'
  281. #' The test for homogeneity of variances is done with a Levene test. A significant p-value means that group variances are not constant. In this case, you should consider modelling variances, e.g. via ~dispformula in glmmTMB.
  282. #'
  283. #' @author Florian Hartig
  284. #' @seealso [testResiduals], [testUniformity], [testOutliers], [testDispersion], [testZeroInflation], [testGeneric], [testTemporalAutocorrelation], [testSpatialAutocorrelation], [testQuantiles], [testCategorical]
  285. #' @example inst/examples/testsHelp.R
  286. #' @export
  287. testCategorical <- function(simulationOutput, catPred,
  288. quantiles = c(0.25, 0.5, 0.75), plot = TRUE, ...){
  289. simulationOutput = ensureDHARMa(simulationOutput, convert = T)
  290. a = list(...)
  291. a$xlab = paste(checkDots("xlab", deparse(substitute(catPred)), ...), "(catPred)")
  292. if(!inherits(catPred, "formula")) {
  293. catPred = catPred # old default
  294. } else {
  295. catPred = getFormulaPredictors(simulationOutput, formula = catPred)[[1]] # formula syntax
  296. }
  297. catPred = as.factor(catPred)
  298. out = list()
  299. out$uniformity$details = suppressWarnings(by(simulationOutput$scaledResiduals,
  300. catPred, ks.test, 'punif',
  301. simplify = TRUE))
  302. out$uniformity$p.value = rep(NA, nlevels(catPred))
  303. for(i in 1:nlevels(catPred)) out$uniformity$p.value[i] = out$uniformity$details[[i]]$p.value
  304. out$uniformity$p.value.cor = p.adjust(out$uniformity$p.value)
  305. if(nlevels(catPred) > 1) out$homogeneity = leveneTest_formula(simulationOutput$scaledResiduals ~ catPred)
  306. if(plot == T){
  307. boxplot(simulationOutput$scaledResiduals ~ catPred, ylim = c(0,1), xlab = a$xlab, axes = FALSE, col = ifelse(out$uniformity$p.value.cor < 0.05, "red", "lightgrey"))
  308. axis(1, at = 1:nlevels(catPred), levels(catPred))
  309. axis(2, at=c(0, quantiles, 1))
  310. abline(h = quantiles, lty = 2)
  311. }
  312. title(ifelse(any(out$uniformity$p.value.cor < 0.05), "Within-group deviations from uniformity significant (red)", "Within-group deviation from uniformity n.s."),
  313. col.main = ifelse(any(out$uniformity$p.value.cor < 0.05), "red", "black"),
  314. line = 1, cex.main = 0.8)
  315. if(length(out) > 1) {
  316. title(ifelse(out$homogeneity$`Pr(>F)`[1] < 0.05, "Levene Test for homogeneity of variance significant", "Levene Test for homogeneity of variance n.s."),
  317. col.main = ifelse(out$homogeneity$`Pr(>F)`[1] < 0.05, "red", "black"), cex.main = 0.8)
  318. }
  319. return(out)
  320. }
  321. #' DHARMa dispersion tests
  322. #'
  323. #' This function performs simulation-based tests for over / underdispersion. If type = "DHARMa" (default and recommended), simulation-based dispersion tests are performed. Their behavior differs depending on whether simulations are done with refit = F, or refit = T, and whether data is simulated conditional (see below). If type = "PearsonChisq", a chi2 test on Pearson residuals is performed.
  324. #'
  325. #' @param simulationOutput an object of class DHARMa, either created via [simulateResiduals] for supported models or by [createDHARMa] for simulations created outside DHARMa, or a supported model. Providing a supported model directly is discouraged, because simulation settings cannot be changed in this case.
  326. #' @param alternative a character string specifying whether the test should test if observations are "greater", "less" or "two.sided" compared to the simulated null hypothesis. Greater corresponds to testing only for overdispersion. It is recommended to keep the default setting (testing for both over and underdispersion).
  327. #' @param plot whether to provide a plot for the results.
  328. #' @param type which test to run. Default is DHARMa, other options are PearsonChisq (see details).
  329. #' @param ... arguments to pass on to [testGeneric].
  330. #'
  331. #' @details Over / underdispersion means that the observed data is more / less dispersed than expected under the fitted model. There is no unique way to test for dispersion problems, and there are a number of different dispersion tests implemented in various R packages. For detailed recommendations see also Leite M.S., Rettelbach, D. and Hartig, F. (2025), preprint available at https://doi.org/10.32942/X23M14.
  332. #'
  333. #' The testDispersion function implements several dispersion tests:
  334. #'
  335. #' **Simulation-based dispersion tests (type == "DHARMa")**
  336. #'
  337. #' If type = "DHARMa" (default and recommended), simulation-based dispersion tests are performed. Their behavior differs depending on whether simulations are done with refit = F, or refit = T.
  338. #'
  339. #' **Important:** for either refit = T or F, the results of type = "DHARMa" dispersion test will differ depending on whether simulations are done conditional (= conditional on fitted random effects) or unconditional (= REs are re-simulated). The default as of DHARMa 0.5.0 is conditional simulations, which substantially increase the power and sensitivity of the dispersion test (for details see [simulateResiduals] and the DHARMa vignette).
  340. #'
  341. #' If refit = F, the function uses [testGeneric] to compare the variance of the observed raw residuals (i.e. var(observed - predicted), displayed as a red line) against the variance of the simulated residuals (i.e. var(simulated - predicted), histogram). The variances are scaled to the mean simulated variance. A significant ratio > 1 indicates overdispersion, a significant ratio < 1 underdispersion.
  342. #'
  343. #' If refit = T, the function compares the approximate deviance (via squared Pearson residuals) with the same quantity from the models refitted with simulated data. Applying this is much slower than the previous alternative. Given the computational cost, I would suggest that most users will be satisfied with the standard dispersion test.
  344. #'
  345. #' **Analytical dispersion tests (type == "PearsonChisq")**
  346. #'
  347. #' This is the test described in https://bbolker.github.io/mixedmodels-misc/glmmFAQ.html#overdispersion, identical to performance::check_overdispersion. Works only if the fitted model provides df.residual and Pearson residuals.
  348. #'
  349. #' The test statistic is biased to lower values under quite general conditions, and will therefore tend to test significant for underdispersion. It is recommended to use this test only for overdispersion, i.e. use alternative == "greater". Also, obviously, it requires that Pearson residuals are available for the chosen model, which will not be the case for all models / packages.
  350. #'
  351. #' @note For particular model classes / situations, there may be more powerful and thus preferable options over the DHARMa test. The advantage of the DHARMa test is that it directly targets the spread of the data (unlike other tests such as dispersion/df, which essentially measure fit and may thus be triggered by problems other than dispersion as well), and it makes practically no assumptions about the fitted model, other than the availability of simulations.
  352. #'
  353. #' @author Florian Hartig
  354. #' @references Leite, MS, Rettelbach, D. & Hartig, F. 2025. Dispersion tests in generalised linear mixed-effects models - a methods comparison and practical guide. EcoEvoRxiv Preprint: https://doi.org/10.32942/X23M14.
  355. #' @seealso [testResiduals], [testUniformity], [testOutliers], [testDispersion], [testZeroInflation], [testGeneric], [testTemporalAutocorrelation], [testSpatialAutocorrelation], [testQuantiles], [testCategorical]
  356. #' @example inst/examples/testDispersionHelp.R
  357. #' @export
  358. testDispersion <- function(simulationOutput, alternative = c("two.sided", "greater", "less"), plot = T, type = c("DHARMa", "PearsonChisq"), ...){
  359. alternative <- match.arg(alternative)
  360. type <- match.arg(type)
  361. out = list()
  362. out$data.name = deparse(substitute(simulationOutput))
  363. if(type == "DHARMa"){
  364. simulationOutput = ensureDHARMa(simulationOutput, convert = "Model")
  365. if(simulationOutput$refit == F){
  366. expectedVar = sd(simulationOutput$simulatedResponse)^2
  367. spread <- function(x) var(x - simulationOutput$fittedPredictedResponse) / expectedVar
  368. out = testGeneric(simulationOutput, summary = spread, alternative = alternative, methodName = "DHARMa nonparametric dispersion test via sd of residuals fitted vs. simulated", plot = plot, ...)
  369. names(out$statistic) = "dispersion"
  370. } else {
  371. rp <- getPearsonResiduals(simulationOutput$fittedModel)
  372. observed = sum(rp^2)
  373. expected = apply(simulationOutput$refittedPearsonResiduals^2 , 2, sum)
  374. out$statistic = c(dispersion = observed / mean(expected))
  375. names(out$statistic) = "dispersion"
  376. out$method = "DHARMa nonparametric dispersion test via mean deviance residual fitted vs. simulated-refitted"
  377. p = getP(simulated = expected, observed = observed, alternative = alternative)
  378. out$alternative = alternative
  379. out$p.value = p
  380. class(out) = "htest"
  381. if(plot == T) {
  382. #plotTitle = gsub('(.{1,50})(\\s|$)', '\\1\n', out$method)
  383. xLabel = paste("Simulated values, red line = fitted model. p-value (",out$alternative, ") = ", out$p.value, sep ="")
  384. hist(expected, xlim = range(expected, observed, na.rm=T ), col = "lightgrey", main = "", xlab = xLabel, breaks = 20, cex.main = 1)
  385. abline(v = observed, lwd= 2, col = "red")
  386. main = ifelse(out$p.value <= 0.05,
  387. "Dispersion test significant",
  388. "Dispersion test n.s.")
  389. title(main = main, cex.main = 1,
  390. col.main = ifelse(out$p.value <= 0.05, "red", "black"))
  391. }
  392. }
  393. } else if(type == "PearsonChisq"){
  394. if("DHARMa" %in% class(simulationOutput)){
  395. model = simulationOutput$fittedModel
  396. }
  397. else model = simulationOutput
  398. if(!alternative == "greater" & class(model)[1] %in% c("lmerMod", "lmerModLmerTest", "glmerMod", "bam", "glmmTMB", "HLfit", "MixMod")) {
  399. message("Note that the Chi2 test on Pearson residuals is biased for MIXED models towards underdispersion. Tests with alternative = two.sided or less are therefore not reliable. If you have random effects in your model, we recommend to test only with alternative = 'greater', i.e. test for overdispersion, or else use the DHARMa default tests which are unbiased. See help for details.")}
  400. rp <- getPearsonResiduals(model)
  401. rdf <- df.residual(model)
  402. Pearson.chisq <- sum(rp^2)
  403. prat <- Pearson.chisq/rdf
  404. if(alternative == "greater") pval <- pchisq(Pearson.chisq, df=rdf, lower.tail=FALSE)
  405. else if (alternative == "less") pval <- pchisq(Pearson.chisq, df=rdf, lower.tail=TRUE)
  406. else if (alternative == "two.sided") pval <- min(min(pchisq(Pearson.chisq, df=rdf, lower.tail=TRUE), pchisq(Pearson.chisq, df=rdf, lower.tail=FALSE)) * 2,1)
  407. out$statistic = prat
  408. names(out$statistic) = "dispersion"
  409. out$parameter = rdf
  410. names(out$parameter) = "df"
  411. out$method = "Parametric dispersion test via mean Pearson-chisq statistic"
  412. out$alternative = alternative
  413. out$p.value = pval
  414. class(out) = "htest"
  415. # c(chisq=Pearson.chisq,ratio=prat,rdf=rdf,p=pval)
  416. return(out)
  417. }
  418. return(out)
  419. }
  420. #' Simulated overdisperstion tests (deprecated)
  421. #'
  422. #' @details Deprecated, switch your code to using the [testDispersion] function
  423. #'
  424. #' @param simulationOutput an object of class DHARMa with simulated quantile residuals, either created via [simulateResiduals] or by [createDHARMa] for simulations created outside DHARMa
  425. #' @param ... additional arguments to [testDispersion]
  426. #' @export
  427. testOverdispersion <- function(simulationOutput, ...){
  428. message("testOverdispersion is deprecated, switch your code to using the testDispersion function")
  429. testDispersion(simulationOutput, ...)
  430. }
  431. #' Parametric overdisperstion tests (deprecated)
  432. #'
  433. #' @details Deprecated, switch your code to using the [testDispersion] function.
  434. #'
  435. #' @param ... arguments will be ignored, the parametric tests is no longer recommend
  436. #' @export
  437. testOverdispersionParametric <- function(...){
  438. message("testOverdispersionParametric is deprecated - switch your code to using the testDispersion function")
  439. return(0)
  440. }
  441. #' Tests for zero-inflation
  442. #'
  443. #' This function compares the observed number of zeros with the zeros expected from simulations.
  444. #'
  445. #' @param simulationOutput an object of class DHARMa, either created via [simulateResiduals] for supported models or by [createDHARMa] for simulations created outside DHARMa, or a supported model. Providing a supported model directly is discouraged, because simulation settings cannot be changed in this case.
  446. #' @param ... further arguments to [testGeneric].
  447. #' @details Zero-inflation means that the observed data contains more zeros than would be expected under the fitted model. Zero-inflation must always be accessed with respect to a particular model, so the mere fact that there are many zeros in the observed data is not an indication of zero-inflation, see Warton, D. I. (2005). Many zeros does not mean zero inflation: comparing the goodness-of-fit of parametric models to multivariate abundance data. Environmetrics 16(3), 275-289.
  448. #'
  449. #' The testZeroInflation function simulates new datasets from the fitted model and compares this null distribution (gray histogram in the plot) with the observed values (red line in the plot). Technically, it is a wrapper for [testGeneric], with the summary argument set to function(x) sum(x == 0). The test statistic is the ratio of observed to simulated zeros. A value < 1 means that the observed data have fewer zeros than expected, a value > 1 means that they have more zeros than expected (aka zero inflation). By default, the function tests both sides, so it would also test for fewer zeros than expected.
  450. #'
  451. #' @note Zero-inflation can occur for a number of reasons other than an underlying data generating process corresponding to a ZIP model. Vice versa, it is very well possible that no zero-inflation will be observed when fitting models to data derived from a ZIP process. The latter is due to the fact that excess zeros can often be explained by other model parameters, such as the theta parameter in the negative binomial.
  452. #'
  453. #' For this reason, results of the zero-inflation test should be interpreted as a residual pattern that can have many reasons, not as a decision criterion for whether or not to fit a ZIP model. To decide whether to add a ZIP term, I would advise relying on appropriate model selection techniques such as AIC, BIC, WAIC, Bayes factor, or LRT. Note that these tests are often not reliable in GLMMs because it is difficult to determine the df spent by the different models. The [simulateLRT] function in DHARMa provides a nonparametric alternative to obtain p-values for LRTs in nested models with unknown df.
  454. #'
  455. #' @author Florian Hartig
  456. #' @example inst/examples/testsHelp.R
  457. #' @seealso [testResiduals], [testUniformity], [testOutliers], [testDispersion], [testZeroInflation], [testGeneric], [testTemporalAutocorrelation], [testSpatialAutocorrelation], [testQuantiles], [testCategorical]
  458. #' @export
  459. testZeroInflation <- function(simulationOutput, ...){
  460. countZeros <- function(x) sum( x == 0)
  461. testGeneric(simulationOutput = simulationOutput, summary = countZeros, methodName = "DHARMa zero-inflation test via comparison to expected zeros with simulation under H0 = fitted model", ... )
  462. }
  463. #' Test for a generic summary statistic based on simulated data
  464. #'
  465. #' This function tests if a user-defined summary differs when applied to simulated / observed data.
  466. #'
  467. #' @param simulationOutput an object of class DHARMa, either created via [simulateResiduals] for supported models or by [createDHARMa] for simulations created outside DHARMa, or a supported model. Providing a supported model directly is discouraged, because simulation settings cannot be changed in this case.
  468. #' @param summary a function that can be applied to simulated / observed data. See examples below.
  469. #' @param alternative a character string specifying whether the test should test if observations are "greater", "less" or "two.sided" compared to the simulated null hypothesis.
  470. #' @param plot whether to plot the simulated summary.
  471. #' @param methodName name of the test (will be used in plot).
  472. #'
  473. #' @details This function applies a user-defined summary to the simulated / observed data of a DHARMa object and then performs a hypothesis test using the ratio Obs / Sim as the test statistic.
  474. #'
  475. #' The summary is applied directly to the data and not to the residuals, but it can easily be remodeled to apply summaries to the residuals by simply defining something like f = function(x) summary (x - predictions), as done in [testDispersion].
  476. #'
  477. #' @note The summary function you specify will be applied to the data as it appears in your fitted model, which may not always be what you want.
  478. #'
  479. #' As an example, consider the case where we want to test for n-inflation in k/n data. If you provide your data via cbind (k, n-k), you have to test for n-inflation, but if you provide your data via k/n and weights = n, you should test for 1-inflation. When in doubt, check how the data is represented internally in model.frame(model) or via simulate(model).
  480. #'
  481. #' @export
  482. #' @author Florian Hartig
  483. #' @example inst/examples/testsHelp.R
  484. #' @seealso [testResiduals], [testUniformity], [testOutliers], [testDispersion], [testZeroInflation], [testGeneric], [testTemporalAutocorrelation], [testSpatialAutocorrelation], [testQuantiles], [testCategorical]
  485. testGeneric <- function(simulationOutput, summary, alternative = c("two.sided", "greater", "less"), plot = T, methodName = "DHARMa generic simulation test"){
  486. out = list()
  487. out$data.name = deparse(substitute(simulationOutput))
  488. simulationOutput = ensureDHARMa(simulationOutput, convert = "Model")
  489. alternative <- match.arg(alternative)
  490. observed = summary(simulationOutput$observedResponse)
  491. simulated = apply(simulationOutput$simulatedResponse, 2, summary)
  492. p = getP(simulated = simulated, observed = observed, alternative = alternative)
  493. out$statistic = c(ratioObsSim = observed / mean(simulated))
  494. out$method = methodName
  495. out$alternative = alternative
  496. out$p.value = p
  497. class(out) = "htest"
  498. if(plot == T) {
  499. plotTitle = gsub('(.{1,50})(\\s|$)', '\\1\n', methodName)
  500. xLabel = paste("Simulated values, red line = fitted model. p-value (",out$alternative, ") = ", out$p.value, sep ="")
  501. hist(simulated, xlim = range(simulated, observed, na.rm=T ), col = "lightgrey", main = plotTitle, xlab = xLabel, breaks = max(round(simulationOutput$nSim / 5), 20), cex.main = 0.8)
  502. abline(v = observed, lwd= 2, col = "red")
  503. }
  504. return(out)
  505. }
  506. #' Test for temporal autocorrelation
  507. #'
  508. #' This function performs a standard test for temporal autocorrelation on the simulated residuals.
  509. #'
  510. #' @param simulationOutput an object of class DHARMa, either created via [simulateResiduals] for supported models or by [createDHARMa] for simulations created outside DHARMa, or a supported model. Providing a supported model directly is discouraged, because simulation settings cannot be changed in this case.
  511. #' @param time time variable in the same order as the datapoints and with the same length as the residuals in simulationOutput. If specified as formula, e.g. time = ~time, NAs are handled automatically (recommended).
  512. #' @param alternative a character string specifying whether the test should test if observations are "greater", "less" or "two.sided" compared to the simulated null hypothesis.
  513. #' @param plot whether to plot output.
  514. #' @details The function performs a Durbin-Watson test on the uniformly scaled residuals, and plots the residuals against time. The DB test was originally designed for normal residuals. In simulations, I didn't see a problem with this setting though. The alternative is to transform the uniform residuals to normal residuals and perform the DB test on those.
  515. #'
  516. #' Testing for temporal autocorrelation requires unique time values - if you have several observations per time value, either use [recalculateResiduals] function to aggregate residuals per time step, or extract the residuals from the fitted object, and plot / test each of them independently for temporally repeated subgroups (typical choices would be location / subject etc.). Note that the latter must be done by hand, outside testTemporalAutocorrelation.
  517. #'
  518. #' @note Standard DHARMa simulations from models with (temporal / spatial / phylogenetic) conditional autoregressive terms will still have the respective temporal / spatial / phylogenetic correlation in the DHARMa residuals, unless the package you are using is modelling the autoregressive terms as explicit REs and is able to simulate conditional on the fitted REs. This has two consequences:
  519. #'
  520. #' 1. If you check the residuals for such a model, they will still show significant autocorrelation, even if the model fully accounts for this structure.
  521. #'
  522. #' 2. Because the DHARMa residuals for such a model are not statistically independent anymore, other tests (e.g. dispersion, uniformity) may have inflated type I error, i.e. you will have a higher likelihood of spurious residual problems.
  523. #'
  524. #' There are three (non-exclusive) routes to address these issues when working with spatial / temporal / other autoregressive models:
  525. #'
  526. #' 1. Simulate conditional on the fitted CAR structures (see conditional simulations in the help of [simulateResiduals]).
  527. #'
  528. #' 2. Rotate simulations prior to residual calculations (see parameter rotation in [simulateResiduals]).
  529. #'
  530. #' 3. Use custom tests / plots that explicitly compare the correlation structure in the simulated data to the correlation structure in the observed data.
  531. #'
  532. #' @author Florian Hartig
  533. #' @seealso [testResiduals], [testUniformity], [testOutliers], [testDispersion], [testZeroInflation], [testGeneric], [testTemporalAutocorrelation], [testSpatialAutocorrelation], [testQuantiles], [testCategorical]
  534. #' @example inst/examples/testTemporalAutocorrelationHelp.R
  535. #' @export
  536. testTemporalAutocorrelation <- function(simulationOutput, time, alternative = c("two.sided", "greater", "less"), plot = TRUE){
  537. simulationOutput = ensureDHARMa(simulationOutput, convert = T)
  538. alternative <- match.arg(alternative)
  539. if(is.null(time)){
  540. time = sample.int(simulationOutput$nObs, simulationOutput$nObs)
  541. message("DHARMa::testTemporalAutocorrelation - no time argument provided, using random times for each data point")
  542. }
  543. if(!inherits(time, "formula")){
  544. # old default
  545. # To avoid Issue #190
  546. if (length(time) != length(residuals(simulationOutput))) stop("Dimensions of time don't match the dimension of the residuals. Remove rows with NAs or use formula syntax for group to handle NAs automatically.")
  547. } else {
  548. time = getFormulaPredictors(simulationOutput, formula = time)[[1]] # formula syntax
  549. }
  550. # actually not sure if this is neccessary for dwtest, but seems better to aggregate
  551. if(any(duplicated(time))) stop("testing for temporal autocorrelation requires unique time values - if you have several observations per time value, either use the recalculateResiduals function to aggregate residuals per time step, or extract the residuals from the fitted object, and plot / test each of them independently for temporally repeated subgroups (typical choices would be location / subject etc.). Note that the latter must be done by hand, outside testTemporalAutocorrelation.")
  552. out = lmtest::dwtest(simulationOutput$scaledResiduals ~ 1, order.by = time, alternative = alternative)
  553. if(plot == T) {
  554. oldpar <- par(mfrow = c(1,2))
  555. on.exit(par(oldpar))
  556. plot(simulationOutput$scaledResiduals[order(time)] ~ time[order(time)],
  557. type = "l", ylab = "Scaled residuals", xlab = "Time", main = "Residuals vs. time", ylim = c(0,1))
  558. abline(h=c(0.5))
  559. abline(h=c(0,0.25,0.75,1), lty = 2 )
  560. acf(simulationOutput$scaledResiduals[order(time)], main = "Autocorrelation", ylim = c(-1,1))
  561. legend("topright",
  562. c(paste(out$method, " p=", round(out$p.value, digits = 5)),
  563. paste("Deviation ", ifelse(out$p.value < 0.05, "significant", "n.s."))),
  564. text.col = ifelse(out$p.value < 0.05, "red", "black" ), bty="n")
  565. }
  566. return(out)
  567. }
  568. #' Test for distance-based spatial (or similar type) autocorrelation
  569. #'
  570. #' This function performs a Moran's I test for distance-based spatial (or similar type) autocorrelation on the calculated quantile residuals.
  571. #'
  572. #' @param simulationOutput an object of class DHARMa, either created via [simulateResiduals] for supported models or via [createDHARMa] for simulations created outside DHARMa, or a supported model. Providing a supported model directly is discouraged, because simulation settings cannot be changed in this case.
  573. #' @param x the x coordinate, in the same order as the data points. Must be specified unless distMat is provided.
  574. #' @param y the y coordinate, in the same order as the data points. Must be specified unless distMat is provided.
  575. #' @param distMat optional distance matrix. Must be provided as class "matrix/array" (class "dist" is not supported). If distMat is not provided, euclidean distances based on x and y will be calculated. See details for explanation.
  576. #' @param alternative a character string specifying whether the test should test if observations are "greater", "less" or "two.sided" compared to the simulated null hypothesis.
  577. #' @param plot if T, and if x and y is provided, plot the output (see details).
  578. #'
  579. #' @details The function performs Moran.I test from the package ape on the DHARMa residuals. If a distance matrix (distMat) is provided, calculations will be based on this distance matrix, and x,y coordinates will only be used for the plotting (if provided). If distMat is not provided, the function will calculate the euclidean distances between x,y coordinates, and test Moran.I based on these distances.
  580. #'
  581. #' If plot = T, a plot will be produced showing each residual at its x,y position, colored according to the residual value. Residuals with 0.5 are colored white, everything below 0.5 is colored increasingly red, everything above 0.5 is colored increasingly blue.
  582. #'
  583. #' Testing for spatial autocorrelation requires unique x,y values - if you have several observations per location, either use the [recalculateResiduals] function to aggregate residuals per location, or extract the residuals from the fitted object, and plot / test each of them independently for spatially repeated subgroups (a typical scenario would be repeated spatial observation, in which case one could plot / test each time step separately for temporal autocorrelation). Note that the latter must be done by hand, outside [testSpatialAutocorrelation].
  584. #'
  585. #' @note Standard DHARMa simulations from models with (temporal / spatial / phylogenetic) conditional autoregressive terms will still have the respective temporal / spatial / phylogenetic correlation in the DHARMa residuals, unless the package you are using is modelling the autoregressive terms as explicit REs and is able to simulate conditional on the fitted REs. This has two consequences:
  586. #'
  587. #' 1. If you check the residuals for such a model, they will still show significant autocorrelation, even if the model fully accounts for this structure.
  588. #'
  589. #' 2. Because the DHARMa residuals for such a model are not statistically independent anymore, other tests (e.g. dispersion, uniformity) may have inflated type I error, i.e. you will have a higher likelihood of spurious residual problems.
  590. #'
  591. #' There are three (non-exclusive) routes to address these issues when working with spatial / temporal / phylogenetic / other autoregressive models:
  592. #'
  593. #' 1. Simulate conditional on the fitted CAR structures (see conditional simulations in the help of [simulateResiduals]).
  594. #'
  595. #' 2. Rotate simulations prior to residual calculations (see parameter rotation in [simulateResiduals]).
  596. #'
  597. #' 3. Use custom tests / plots that explicitly compare the correlation structure in the simulated data to the correlation structure in the observed data.
  598. #'
  599. #' @author Florian Hartig
  600. #' @seealso [testResiduals], [testUniformity], [testOutliers], [testDispersion], [testZeroInflation], [testGeneric], [testTemporalAutocorrelation], [testSpatialAutocorrelation], [testQuantiles], [testCategorical]
  601. #' @import grDevices
  602. #' @example inst/examples/testSpatialAutocorrelationHelp.R
  603. #' @export
  604. testSpatialAutocorrelation <- function(simulationOutput, x = NULL, y = NULL, distMat = NULL, alternative = c("two.sided", "greater", "less"), plot = TRUE){
  605. alternative <- match.arg(alternative)
  606. data.name = deparse(substitute(simulationOutput)) # needs to be before ensureDHARMa
  607. simulationOutput = ensureDHARMa(simulationOutput, convert = T)
  608. # Assertions
  609. if( (!is.null(x) | !is.null(y)) & !is.null(distMat) ){
  610. if(isTRUE(plot)) {message("Both coordinates and distMat provided, calculations will be done based on the distance matrix, coordinates will only be used for plotting.")}
  611. else { # set x,y to NULL so they don't cause errors if wrong dimensions
  612. x = NULL
  613. y = NULL
  614. message("Both coordinates and distMat provided, calculations will be done based on the distance matrix, coordinates will be ignored.")
  615. }
  616. }
  617. if( (is.null(x) | is.null(y)) & is.null(distMat) ) stop("You need to provide either x,y coordinates or a distMatrix.")
  618. # check if x,y are formulas
  619. if(!inherits(x, "formula") | !inherits(y, "formula")) {
  620. # old default
  621. # To avoid Issue #190
  622. if (is.null(distMat) & !is.null(x) & length(x) != length(residuals(simulationOutput)) | is.null(distMat) & !is.null(y) & length(y) != length(residuals(simulationOutput))) stop("Dimensions of x / y coordinates don't match the dimension of the residuals. Remove rows with NAs or specify x,y as formula to handle NAs automatically.")
  623. if(inherits(x, "formula") != inherits(y, "formula")) stop("Please specify x and y the same way, either both as formula (e.g.x = ~x) or both as vector (e.g. x = data$x).")
  624. } else {
  625. # formula syntax
  626. x = getFormulaPredictors(simulationOutput, formula = x)[[1]]
  627. y = getFormulaPredictors(simulationOutput, formula = y)[[1]]
  628. }
  629. # if not provided, create distance matrix based on x and y
  630. if(is.null(distMat)) distMat <- as.matrix(dist(cbind(x, y)))
  631. # check for duplicates in x,y and distMat (off-diagonal zero values) (#73)
  632. testDistMat = distMat
  633. diag(testDistMat) = NA # set diagonal to NA
  634. if(any(duplicated(cbind(x,y))) | any(testDistMat == 0, na.rm = TRUE)) stop("Testing for spatial autocorrelation requires unique x,y values - if you have several observations per location, either use the recalculateResiduals function to aggregate residuals per location, or extract the residuals from the fitted object, and plot / test each of them independently for spatially repeated subgroups (a typical scenario would be repeated spatial observation, in which case one could plot / test each time step separately for temporal autocorrelation). Note that the latter must be done by hand, outside testSpatialAutocorrelation.")
  635. invDistMat <- 1/distMat
  636. diag(invDistMat) <- 0
  637. MI = ape::Moran.I(simulationOutput$scaledResiduals, weight = invDistMat, alternative = alternative)
  638. out = list()
  639. out$statistic = c(observed = MI$observed, expected = MI$expected, sd = MI$sd)
  640. out$method = "DHARMa Moran's I test for distance-based autocorrelation"
  641. out$alternative = "Distance-based autocorrelation"
  642. out$p.value = MI$p.value
  643. out$data.name = data.name
  644. class(out) = "htest"
  645. if(plot == T & !is.null(x) & !is.null(y)) {
  646. opar = par(no.readonly = TRUE)
  647. on.exit(par(opar))
  648. col = colorRamp(c("red", "white", "blue"))(simulationOutput$scaledResiduals)
  649. layout(matrix(c(1, 2), ncol = 2), widths = c(6, 1))
  650. # scatterplot
  651. par(mar = c(5, 4, 4, 1))
  652. plot(x, y,
  653. col = rgb(col, maxColorValue = 255),
  654. main = "Standardized residuals in space",
  655. cex.main = 0.8)
  656. mtext(paste0(out$method, "\n",
  657. " p = ", round(out$p.value, digits = 5), ", Deviation ", ifelse(out$p.value < 0.05, "significant", "n.s.")),
  658. col = ifelse(out$p.value < 0.05, "red", "black"),
  659. cex = 0.8,
  660. side = 3, line = 0)
  661. # legend
  662. par(mar = c(1, 0.5, 2, 2))
  663. plot.new()
  664. plot.window(xlim = c(0, 1), ylim = c(-1, 1))
  665. legend_image = as.raster(matrix(colorRampPalette(c("blue", "white", "red"))(200), ncol = 1))
  666. rasterImage(legend_image,
  667. xleft = -0.5, xright = 1,
  668. ybottom = -0.1, ytop = 0.5)
  669. axis(4, at = c(-0.1, 0.5), labels = c("0", "1"), las = 1)
  670. mtext("Standardized \n residuals", side = 3, line = -5,cex = 0.8,)
  671. # TODO implement correlogram
  672. }
  673. return(out)
  674. }
  675. #' Test for phylogenetic autocorrelation
  676. #'
  677. #' This function performs a Moran's I test for phylogenetic autocorrelation on the calculated quantile residuals.
  678. #'
  679. #' @param simulationOutput an object of class DHARMa, either created via [simulateResiduals] for supported models or via [createDHARMa] for simulations created outside DHARMa, or a supported model. Providing a supported model directly is discouraged, because simulation settings cannot be changed in this case.
  680. #' @param tree a phylogenetic tree object.
  681. #' @param alternative a character string specifying whether the test should test if observations are "greater", "less" or "two.sided" compared to the simulated null hypothesis of no phylogenetic correlation.
  682. #'
  683. #' @details The function performs Moran.I test from the package ape on the DHARMa residuals, based on the phylogenetic distance matrix internally created from the provided tree. For custom distance matrices, you can use [testSpatialAutocorrelation].
  684. #'
  685. #' @note Standard DHARMa simulations from models with (temporal / spatial / phylogenetic) conditional autoregressive terms will still have the respective temporal / spatial / phylogenetic correlation in the DHARMa residuals, unless the package you are using is modelling the autoregressive terms as explicit REs and is able to simulate conditional on the fitted REs. This has two consequences:
  686. #'
  687. #' 1. If you check the residuals for such a model, they will still show significant autocorrelation, even if the model fully accounts for this structure.
  688. #'
  689. #' 2. Because the DHARMa residuals for such a model are not statistically independent anymore, other tests (e.g. dispersion, uniformity) may have inflated type I error, i.e. you will have a higher likelihood of spurious residual problems.
  690. #'
  691. #' There are three (non-exclusive) routes to address these issues when working with spatial / temporal / phylogenetic autoregressive models:
  692. #'
  693. #' 1. Simulate conditional on the fitted CAR structures (see conditional simulations in the help of [simulateResiduals]).
  694. #'
  695. #' 2. Rotate simulations prior to residual calculations (see parameter rotation in [simulateResiduals]).
  696. #'
  697. #' 3. Use custom tests / plots that explicitly compare the correlation structure in the simulated data to the correlation structure in the observed data.
  698. #'
  699. #' @author Florian Hartig
  700. #' @seealso [testResiduals], [testUniformity], [testOutliers], [testDispersion], [testZeroInflation], [testGeneric], [testTemporalAutocorrelation], [testSpatialAutocorrelation], [testQuantiles], [testCategorical]
  701. #' @example inst/examples/testPhylogeneticAutocorrelationHelp.R
  702. #' @export
  703. testPhylogeneticAutocorrelation <- function(simulationOutput,
  704. tree,
  705. alternative = c("two.sided", "greater", "less")){
  706. alternative <- match.arg(alternative)
  707. data.name = deparse(substitute(simulationOutput)) # needs to be before ensureDHARMa
  708. simulationOutput = ensureDHARMa(simulationOutput, convert = T)
  709. # calculate distance matrix
  710. distMat <- cophenetic(tree)
  711. invDistMat <- 1/distMat
  712. diag(invDistMat) <- 0
  713. MI = ape::Moran.I(simulationOutput$scaledResiduals, weight = invDistMat, alternative = alternative)
  714. out = list()
  715. out$statistic = c(observed = MI$observed, expected = MI$expected, sd = MI$sd)
  716. out$method = "DHARMa Moran's I test for phylogenetic autocorrelation"
  717. out$alternative = "Phylogenetic autocorrelation"
  718. out$p.value = MI$p.value
  719. out$data.name = data.name
  720. class(out) = "htest"
  721. return(out)
  722. }
  723. getP <- function(simulated, observed, alternative, plot = FALSE, ...){
  724. if(alternative == "greater") p = mean(simulated >= observed)
  725. if(alternative == "less") p = mean(simulated <= observed)
  726. if(alternative == "two.sided") p = min(min(mean(simulated <= observed), mean(simulated >= observed) ) * 2,1)
  727. if(plot == T){
  728. hist(simulated, xlim = range(simulated, observed), col = "lightgrey", main = "Distribution of test statistic \n grey = simulated, red = observed", ...)
  729. abline(v = mean(simulated), col = 1, lwd = 2)
  730. abline(v = observed, col = "red", lwd = 2)
  731. }
  732. return(p)
  733. }

tests.R at commit c9dbcee, under gnu · at the source

Overview

Authors: Gustavo A Rodriguez1, Andrew Aoun1,2,3, Eva F Rothenberg1, C Oliver Shetler1, Lorenzo Posani4,5,6, Srujan V Vajram1, Thomas Tedesco1, Anurag Sharma2, Stefano Fusi4,5,6,7, S Abid Hussaini1,2,8,9
ORCID iDs: S Abid Hussaini
  1. Taub Institute for Research on Alzheimer’s Disease and the Aging Brain, Columbia University Irving Medical Center, New York, NY 10032, USA
  2. Burke Neurological Institute, White Plains, NY 10605, USA
  3. Center for Neural Science, New York University, New York, NY 10003, USA
  4. Department of Neuroscience, Columbia University Irving Medical Center, New York, NY 10027, USA
  5. Center for Theoretical Neuroscience, Columbia University, New York, NY 10027, USA
  6. Zuckerman Mind Brain Behavior Institute, Columbia University, New York, NY 10027, USA
  7. Kavli Institute for Brain Science, Columbia University, New York, NY 10027, USA
  8. Department of Pathology and Cell Biology, Columbia University Irving Medical Center, New York, NY 10032, USA
  9. Lead contact
Journal: Cell reports, volume 45, issue 6, article 117505
Dates: published online 6 June 2026; in print 23 June 2026
Type: Research article · Language: English
License: CC BY
Identifiers: DOI 10.1016/j.celrep.2026.117505 · PMID 42250220 · PMCID PMC13560757 · OpenAlex W7163717440
Open access: gold, a free copy (OpenAlex)
Status: code verified
Categories: extracellular electrophysiology (units, LFP) (modality), mouse (organism), Alzheimer's / dementia (population)
Methods: Spectral & time-frequency, Connectivity, Statistics, Machine learning, Single-unit activity, calcium imaging, Smoothing, state filtering, decompositions
Keywords: Amyloid beta, Medial Entorhinal Cortex, Aβ Plaque, In Vivo Electrophysiology, Neuronal Hyperactivity, Spatial Remapping, App Knock-in, Cp: Neuroscience, Spatial Decoding, App Nl-g-f, Ratemap Stability
MeSH: Aging*, Amyloid beta-Protein Precursor*, Entorhinal Cortex*, Neurons*, Alzheimer Disease, Amyloid beta-Peptides, Animals, Gene Knock-In Techniques, Interneurons, Male, Mice, Mice, Inbred C57BL, Mice, Transgenic (* major topic)
Topic: Memory and Neural Mechanisms (Cognitive Neuroscience, Neuroscience), according to OpenAlex
Funding: NIA NIH HHS (RF1 AG080818, R01 AG050425, K01 AG068598, R01 AG064066); BrightFocus Foundation (A2019382F); National Institute on Aging (R01AG064066, R01AG050425, K01AG068598, RF1AG080818)
Citations: cited by 1 paper (Europe PMC); 142 references in the paper

Abstract

Advanced amyloid beta (Aβ) pathology is associated with aberrant neuronal network activity and cognitive impairment in preclinical Alzheimer’s disease (AD) models. Here, we assess Aβ pathology’s impact on spatial information processing in the medial entorhinal cortex (MEC) of 18-month AppNL-G-F/NL-G-F knock-in (APP KI) mice during exploration of open field arenas. Spatial information scores are decreased in APP KI MEC neurons versus age-matched controls. Border cell firing preferences are unstable across sessions and grid cell spatial periodicity is disrupted. Ratemap stability analysis using the Earth Mover’s Distance indicates increased instability in spatially tuned APP KI neurons. Spatial decoding analysis indicates deficits in position and speed coding in APP KI mice across all comparisons. Additionally, APP KI mice display a mild hyperactive phenotype driven by narrow-spiking putative interneurons. These findings tie Aβ-associated dysregulation in neuronal firing to disruptions in spatial information processing that may underlie cognitive deficits associated with AD.

Reproduced under the paper's license (CC BY), from the paper cited above.

Repositories

Its files are read in the Code ↔ Paper reader above, with 2 matches between paragraphs and lines of code.

Zenodo 19040105

License: CC-BY-4.0
State: the link answers, verified on 27 September 2026
Evidence: files inventoried
Size: 1 file
Software Heritage: not checked
Found in: the resources table
Not found: README, license file, CITATION.cff, environment file, tests, continuous integration, documentation
Availability: 1 check, the latest on 27 September 2026: the link answers (HTTP 200)
  • 27 September 2026: the link answers (HTTP 200)
1 file

klusta-team/klustakwik

License: none: the authors keep all their rights
State: the link answers, verified on 27 September 2026
Evidence: files inventoried
Commit: d8b750107fef53996957d774dfa3ec71f2442462, 21 April 2015
Languages: C/C++ (8), C++ (8), Python (2)
Size: 26 files, 18 scripts
Software Heritage: archived
Found in: the resources table
Holds: README
Not found: license file, CITATION.cff, environment file, tests, continuous integration, documentation
Tools: Matplotlib (1 file), NumPy (1 file), pandas (1 file)
Availability: 1 check, the latest on 27 September 2026: the link answers
  • 27 September 2026: the link answers
19 files

glmmTMB/glmmTMB

License: none: the authors keep all their rights
State: the link answers, verified on 27 September 2026
Evidence: files inventoried
Commit: db725d143d529c93c55cf7baa7402dc5135fe6ff, 23 September 2026
Languages: R (170), JavaScript (20), C/C++ (3), C++ (2), Shell (2), Quarto (2), Jupyter (1)
Size: 487 files, 200 scripts
Software Heritage: archived
Found in: the resources table
Holds: README, environment (glmmTMB/DESCRIPTION, misc/dockerfile), tests, continuous integration, documentation, 18 notebooks
Not found: license file, CITATION.cff
Tools: glmmTMB (126 files), lme4 (32 files), tidyverse (15 files), ggplot2 (10 files), car (7 files), emmeans (7 files), nlme (6 files), mgcv (5 files), broom (4 files), easystats (3 files), reshape2 (3 files), brms (2 files), lmerTest (2 files), metafor (2 files), patchwork (2 files), Stan (2 files), NumPy (1 file), SymPy (1 file)
Availability: 1 check, the latest on 27 September 2026: the link answers
  • 27 September 2026: the link answers
201 files

florianhartig/DHARMa

License: gnu
State: the link answers, verified on 27 September 2026
Evidence: files inventoried
Commit: c9dbcee2eec12904e505dd8c7c4f1f8d61ec1f79, 25 September 2026
Languages: R (142)
Size: 305 files, 142 scripts
Software Heritage: not archived
Found in: the resources table
Holds: README, environment (DHARMa/DESCRIPTION), tests, continuous integration, documentation, 18 notebooks
Not found: license file, CITATION.cff
Tools: lme4 (55 files), glmmTMB (27 files), mgcv (15 files), JAGS (5 files), tidyverse (5 files), brms (4 files), easystats (4 files), nlme (4 files), ggplot2 (1 file)
Availability: 1 check, the latest on 27 September 2026: the link answers
  • 27 September 2026: the link answers
143 files

hussainilab/rodriguez-et-al-cell-reports-2026

License: none: the authors keep all their rights
State: the link answers, verified on 27 September 2026
Evidence: files inventoried
Commit: 7772cfa1cce93cad101419229805151e27f5beea, 14 May 2026
Size: 6 files, 0 scripts
Software Heritage: not archived
Found in: the Zenodo archive record
Holds: README
Not found: license file, CITATION.cff, environment file, tests, continuous integration, documentation
Availability: 1 check, the latest on 27 September 2026: the link answers
  • 27 September 2026: the link answers
1 file

The paper's code and data availability statement is in the Data section.

Tracing map

Proposed by the machine: these links were found in the paper and verified at the source, without human review. The map will receive a Zenodo DOI once one of the paper's authors has validated it with their ORCID.

What the map holds:

  • 5 repositories of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
  • 360 scripts, each with its path and the digest of its content;
  • 2 matches between paragraphs of the paper and lines of the code (method lexical-v1);
  • neither the text of the paper nor the code itself.

Its JSON (tracing-map.json) is deposited on Zenodo with its DOI once the map is validated.

Data

No dataset and no data link were found in the paper.

Data and code availability

Source data for all figures will be made freely available on the Hussaini Lab GitHub page (https://doi.org/10.5281/zenodo.19040105).

All original code used for data analysis will be made freely available on the Hussaini Lab GitHub page.

Any additional information regarding data is available from the lead contact upon request.

Reproduced under the paper's license (CC BY), from the paper cited above.

Versions

The history of this record: each version stored by the harvester or made by a correction of its authors or of the maintainers of its code, and what changed in its facts. The texts of the paper (its abstract, its availability statements) are not part of it; versions that changed only those are not listed.

Version 2, 28 September 2026

  • Publisher: n/a → Cell Press
  • Authors: added S Abid Hussaini (0000-0002-3921-9021); removed S Abid Hussaini

Version 1, 27 September 2026: the first record

Recorded: type, language, journal, volume, issue, pages, dates, 10 authors, 11 keywords, 13 MeSH terms, 3 funders, 140 references, 10 RRIDs.

Cite

This paper

Rodriguez, G. A., Aoun, A., Rothenberg, E. F., Shetler, C. O., Posani, L., Vajram, S. V., Tedesco, T., Sharma, A., Fusi, S., & Hussaini, S. A. (2026). Impaired spatial coding and neuronal hyperactivity in the medial entorhinal cortex of aged APP knock-in mice. Cell reports, 45(6), 117505. https://doi.org/10.1016/j.celrep.2026.117505

BibTeX

@article{rodriguez2026impaired,
author = {Rodriguez, Gustavo A and Aoun, Andrew and Rothenberg, Eva F and Shetler, C Oliver and Posani, Lorenzo and Vajram, Srujan V and Tedesco, Thomas and Sharma, Anurag and Fusi, Stefano and Hussaini, S Abid},
title = {{Impaired spatial coding and neuronal hyperactivity in the medial entorhinal cortex of aged APP knock-in mice}},
journal = {Cell reports},
year = {2026},
month = jun,
volume = {45},
number = {6},
pages = {117505},
publisher = {Cell Press},
issn = {2211-1247},
doi = {10.1016/j.celrep.2026.117505},
url = {https://doi.org/10.1016/j.celrep.2026.117505},
pmid = {42250220},
pmcid = {PMC13560757}
}

RIS

TY - JOUR
AU - Rodriguez, Gustavo A
AU - Aoun, Andrew
AU - Rothenberg, Eva F
AU - Shetler, C Oliver
AU - Posani, Lorenzo
AU - Vajram, Srujan V
AU - Tedesco, Thomas
AU - Sharma, Anurag
AU - Fusi, Stefano
AU - Hussaini, S Abid
TI - Impaired spatial coding and neuronal hyperactivity in the medial entorhinal cortex of aged APP knock-in mice
T2 - Cell reports
J2 - Cell Rep
PY - 2026
DA - 2026/06/06
VL - 45
IS - 6
SP - 117505
SN - 2211-1247
PB - Cell Press
DO - 10.1016/j.celrep.2026.117505
UR - https://doi.org/10.1016/j.celrep.2026.117505
LA - en
ER -

CSL-JSON

{
"id": "10.1016/j.celrep.2026.117505",
"type": "article-journal",
"title": "Impaired spatial coding and neuronal hyperactivity in the medial entorhinal cortex of aged APP knock-in mice",
"container-title": "Cell reports",
"author": [
{
"family": "Rodriguez",
"given": "Gustavo A"
},
{
"family": "Aoun",
"given": "Andrew"
},
{
"family": "Rothenberg",
"given": "Eva F"
},
{
"family": "Shetler",
"given": "C Oliver"
},
{
"family": "Posani",
"given": "Lorenzo"
},
{
"family": "Vajram",
"given": "Srujan V"
},
{
"family": "Tedesco",
"given": "Thomas"
},
{
"family": "Sharma",
"given": "Anurag"
},
{
"family": "Fusi",
"given": "Stefano"
},
{
"family": "Hussaini",
"given": "S Abid"
}
],
"container-title-short": "Cell Rep",
"volume": "45",
"issue": "6",
"page": "117505",
"DOI": "10.1016/j.celrep.2026.117505",
"PMID": "42250220",
"PMCID": "PMC13560757",
"ISSN": "2211-1247",
"publisher": "Cell Press",
"URL": "https://doi.org/10.1016/j.celrep.2026.117505",
"language": "en",
"issued": {
"date-parts": [
[
2026,
6,
6
]
]
}
}

The tracing map gets a citation of its own once an author has validated it and it has a DOI.

Similar papers

The papers with a page that share the most with this one: the tools found in their code, their categories, datasets, cited references and authors, the rarest counting most.

[1] doi:10.1016/j.celrep.2026.117646 [code]
Medial entorhinal-hippocampal desynchronization parallels the emergence of memory impairment in a mouse model of Alzheimer's disease pathology.
Journal: Cell reports
In common: car, broom, emmeans, 7 other tools, Alzheimer's / dementia, mouse, 19 references
[2] doi:10.1038/s41467-026-69866-3 [code]
Selective weakening of population-coupled synaptic activity in vivo in a mouse model of amyloid-beta pathology.
Journal: Nature communications
In common: patchwork, ggplot2, tidyverse, Alzheimer's / dementia, mouse, 17 references
[3] doi:10.1016/j.isci.2026.116747 [code]
Age and loneliness relate to reduced trust learning and alterations in amygdala function.
Journal: iScience
In common: Stan, brms, nlme, 9 other tools
[4] doi:10.1111/ejn.70480 [code]
Astrocyte Proximity Protects Synapses From Human Amyloid-Beta Induced Degeneration in a Mouse Ex Vivo Model of Early Alzheimer's Disease.
Journal: The European journal of neuroscience
In common: emmeans, lmerTest, lme4, 6 other tools, Alzheimer's / dementia, mouse, 6 references
[5] doi:10.1038/s43587-026-01096-0 [code]
Neuronal APOE4-induced early hippocampal network hyperexcitability in Alzheimer's disease pathogenesis.
Journal: Nature aging
In common: pandas, Matplotlib, NumPy, Alzheimer's / dementia, mouse, 10 references
[6] doi:10.1038/s41467-026-73865-9 [code]
Histamine shapes the neurocomputational dynamics of human learning.
Journal: Nature communications
In common: metafor, brms, easystats, 10 other tools
[7] doi:10.1002/hbm.70605 [code]
BrainEnrich: Revealing Biological Insights for Imaging-Derived Phenotypes Through Transcriptomic Enrichment.
Journal: Human brain mapping
In common: JAGS, nlme, easystats, 10 other tools
[8] doi:10.64898/2026.03.02.709173 [code]
Corpus Callosum Dysgenesis impairs metacognition: evidence from multi-modality and multi-cohort replications
Journal: bioRxiv (preprint)
In common: JAGS, Stan, brms, 7 other tools
[9] doi:10.1038/s41467-025-62798-4 [code]
Distinct manifestations of excitatory-inhibitory imbalance associated with amyloid-β and tau in patients with Alzheimer’s disease
Journal: n/a
In common: Alzheimer's / dementia, 11 references
[10] doi:10.1126/sciadv.aec9291 [code]
Computational mechanisms of perception in autism revealed using games inspired by rodent operant tasks.
Journal: Science advances
In common: Stan, nlme, easystats, 8 other tools

Contribute

The authors of this paper can claim it, correct its record and validate its tracing map, and the maintainers of its code (its owner, or a public member of its organization) correct what it says of their repository; anyone signed in can ask for its removal. Every request goes to OSCR's own machine, which answers it; your account page follows them.

Sign in with ORCID to claim this paper as one of its authors, correct its record or validate its tracing map: when the paper's metadata lists your ORCID iD, you are recognized at once. Maintainers of its code: sign in with GitHub, then claim the repository on your account page.

Request its removal

To ask OSCR to remove this record, the copies of its authors' scripts or its tracing map, use the removal request page: signed in, you say who you are, what to remove and why, then review and confirm the request. Published rules decide every request (how).

Discussion, reproductions, activity

Discussion: questions and error reports about this paper and its code, from signed-in readers and its authors. It opens with sign-in.

Reproductions: reports from readers who ran the authors' code: what they reproduced, with which environment, commit and data. It opens with sign-in.

Activity: what happens around this paper: new versions of its record, its map's validation, discussions and reproductions. It opens with sign-in.