Skip to content

Commit b523f85

Browse files
authored
Merge pull request #142 from r-spatial/kb23
Kb23 Add SD.RStests for Koley & Bera 2023 and in progress
2 parents 5ed17a7 + dfdcfdb commit b523f85

File tree

121 files changed

+1197
-406
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

121 files changed

+1197
-406
lines changed

DESCRIPTION

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Package: spdep
22
Version: 1.3-2
3-
Date: 2023-11-24
3+
Date: 2024-01-17
44
Title: Spatial Dependence: Weighting Schemes, Statistics
55
Encoding: UTF-8
66
Authors@R: c(person("Roger", "Bivand", role = c("cre", "aut"),
@@ -9,6 +9,7 @@ Authors@R: c(person("Roger", "Bivand", role = c("cre", "aut"),
99
person("Micah", "Altman", role = "ctb"),
1010
person("Luc", "Anselin", role = "ctb"),
1111
person("Renato", "Assunção", role = "ctb"),
12+
person("Anil", "Bera", role = "ctb"),
1213
person("Olaf", "Berke", role = "ctb"),
1314
person("F. Guillaume", "Blanchet", role = "ctb"),
1415
person("Marilia", "Carvalho", role = "ctb"),
@@ -19,6 +20,7 @@ Authors@R: c(person("Roger", "Bivand", role = c("cre", "aut"),
1920
person("Dewey", "Dunnington", role = c("ctb"),
2021
comment = c(ORCID = "0000-0002-9415-4582")),
2122
person("Virgilio", "Gómez-Rubio", role = "ctb"),
23+
person("Malabika", "Koley", role = "ctb"),
2224
person("Elias", "Krainski", role = "ctb"),
2325
person("Pierre", "Legendre", role = "ctb"),
2426
person("Nicholas", "Lewin-Koh", role = "ctb"),
@@ -62,9 +64,13 @@ Description: A collection of functions to create spatial weights matrix
6264
<doi:10.1016/j.csda.2008.07.021> and 'LOSH' local indicators
6365
of spatial heteroscedasticity ('Ord' and 'Getis')
6466
<doi:10.1007/s00168-011-0492-y>. The implementation of most of
65-
the measures is described in 'Bivand' and 'Wong' (2018)
67+
these measures is described in 'Bivand' and 'Wong' (2018)
6668
<doi:10.1007/s11749-018-0599-x>, with further extensions in 'Bivand' (2022)
67-
<doi:10.1111/gean.12319>.
69+
<doi:10.1111/gean.12319>. Lagrange multiplier tests for spatial dependence
70+
in linear models are provided (Anselin et al. 1996)
71+
<doi:10.1016/0166-0462(95)02111-6>, as are Rao's score tests for hypothesised
72+
spatial Durbin models based in fitted linear models (Koley and Bera 2024)
73+
<doi:10.1080/17421772.2023.2256810>.
6874
From 'spdep' and 'spatialreg' versions >= 1.2-1, the model fitting functions
6975
previously present in this package are defunct in 'spdep' and may be found
7076
in 'spatialreg'.

NAMESPACE

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ export(gabrielneigh, geary.test, geary, geary.mc, globalG.test, graph2nb,
3939
joincount.test, joincount.mc, joincount.multi, print.jcmulti,
4040
knearneigh, knn2nb)
4141

42-
export(listw2sn, sn2listw, read.gwt2nb, write.sn2gwt, lm.LMtests,
42+
export(listw2sn, sn2listw, read.gwt2nb, write.sn2gwt, lm.LMtests,
43+
lm.RStests, SD.RStests,
4344
lm.morantest, localG, localG_perm, localmoran, localmoran_perm, moran,
4445
moran.test, moran.mc, moran.plot, localmoran.sad, lm.morantest.sad,
4546
nb2listw, nb2listwdist, nb2mat, listw2mat, mat2listw, nbdists, nblag,
@@ -50,7 +51,7 @@ export(listw2sn, sn2listw, read.gwt2nb, write.sn2gwt, lm.LMtests,
5051
sym.attr.nb, include.self, make.sym.nb, union.nb, intersect.nb,
5152
setdiff.nb, complement.nb, Szero, spdep,
5253
plot.nb, edit.nb, subset.nb, subset.listw,
53-
plot.Gabriel, plot.relative, print.jclist, print.LMtestlist,
54+
plot.Gabriel, plot.relative, print.jclist,
5455
plot.mc.sim, as.data.frame.localmoransad, print.localmoransad,
5556
summary.localmoransad, print.summary.localmoransad, print.moransad,
5657
summary.moransad, print.summary.moransad, print.spcor, plot.spcor,
@@ -109,9 +110,9 @@ S3method(plot, relative)
109110

110111
S3method(print, jclist)
111112
S3method(print, jcmulti)
112-
S3method(print, LMtestlist)
113-
S3method(summary, LMtestlist)
114-
S3method(print, LMtestlist.summary)
113+
S3method(print, RStestlist)
114+
S3method(summary, RStestlist)
115+
S3method(print, RStestlist.summary)
115116
S3method(plot, mc.sim)
116117

117118
S3method(as.data.frame, localmoransad)

NEWS.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Version 1.3-2 (development)
22

3+
* change `lm.LMtests` to `lm.RStests` and re-name Lagrange multiplier to Rao's score; add `GNM_` prefix to test names if the input object inherits from `SlX` created by `spatialreg::lmSLX` (Koley, forthcoming)
4+
5+
* add `SD.RStests` implementation of Rao's score tests for spatial Durbin models (Koley and Bera, 2024) and for SDEM models (Koley, forthcoming)
6+
37
* #143 `row.names` pass-through in `poly2nb` corrected, harmonised `row.names` pass-through also in `nbdists` and `dnearneigh`
48

59
* #139 add `na.action` argument to `geary.test`, `geary.mc` and `globalG.test`

R/SD.RStests.R

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
# Copyright 2023-4 by Roger Bivand
2+
#
3+
4+
is.formula <- function(x){
5+
inherits(x,"formula")
6+
}
7+
8+
create_X0 <- function(X, listw, Durbin=TRUE, data=NULL, na.act=NULL) {
9+
if (isTRUE(Durbin)) {
10+
n <- NROW(X)
11+
m <- NCOL(X)
12+
# check if there are enough regressors
13+
xcolnames <- colnames(X)
14+
stopifnot(!is.null(xcolnames))
15+
K <- ifelse(xcolnames[1] == "(Intercept)", 2, 1)
16+
vars <- NULL
17+
xI <- NULL
18+
X0 <- NULL
19+
if (K == 2) {
20+
# unnormalized weight matrices
21+
if (!(listw$style == "W")) {
22+
xI <- as.double(rep(1, n))
23+
vars <-"X0.(Intercept)"
24+
}
25+
}
26+
if (m > 1 || (m == 1 && K == 1)) {
27+
X0 <- matrix(as.numeric(NA), nrow=n,
28+
ncol=ifelse(m==1, 1, (m-(K-1))))
29+
for (k in K:m) {
30+
j <- ifelse(k==1, 1, k-(K-1))
31+
X0[,j] <- X[,xcolnames[k]]
32+
vars <- c(vars, xcolnames[k])
33+
}
34+
}
35+
if (!is.null(xI)) X0 <- cbind(xI, X0)
36+
colnames(X0) <- vars
37+
rownames(X0) <- rownames(X)
38+
} else if (is.formula(Durbin)) {
39+
data1 <- data
40+
if (!is.null(na.act) && (inherits(na.act, "omit") ||
41+
inherits(na.act, "exclude"))) {
42+
data1 <- data1[-c(na.act),]
43+
}
44+
dmf <- lm(Durbin, data1, na.action=na.fail,
45+
method="model.frame")
46+
# dmf <- lm(Durbin, data, na.action=na.action,
47+
# method="model.frame")
48+
X0 <- try(model.matrix(Durbin, dmf), silent=TRUE)
49+
if (inherits(X0, "try-error"))
50+
stop("Durbin variable mis-match")
51+
52+
inds <- match(colnames(X0), colnames(X))
53+
if (anyNA(inds)) {
54+
wna <- which(is.na(inds)) #TR: continue if Durbin has intercept, but formula has not
55+
if (length(wna) == 1 && grepl("Intercept", colnames(X0)[wna])
56+
&& attr(terms(Durbin), "intercept") == 1) {
57+
inds <- inds[-wna]
58+
} else {
59+
stop("X0 variables not in X: ",
60+
paste(colnames(X0)[is.na(inds)], collapse=" "))
61+
}
62+
}
63+
icept <- grep("(Intercept)", colnames(X0))
64+
if (length(icept) == 1L && listw$style == "W")
65+
X0 <- X0[, -icept, drop=FALSE]
66+
} else stop("Durbin argument neither TRUE nor formula")
67+
X0
68+
}
69+
70+
SD.RStests <- function(model, listw, zero.policy=attr(listw, "zero.policy"), test="SDM", Durbin=TRUE) {
71+
72+
if (inherits(model, "lm")) na.act <- model$na.action
73+
else na.act <- attr(model, "na.action")
74+
75+
listw_name <- deparse(substitute(listw))
76+
77+
SDM.tests <- c("SDM_RSlag", "SDM_adjRSlag", "SDM_RSWX", "SDM_adjRSWX", "SDM_Joint")
78+
SDEM.tests <- c("SDEM_RSerr", "SDEM_RSWX", "SDEM_Joint")
79+
all.tests <- c(SDM.tests, SDEM.tests)
80+
if (test[1] == "SDM") test <- SDM.tests
81+
if (test[1] == "SDEM") test <- SDEM.tests
82+
if (test[1] == "all") test <- all.tests
83+
if (!all(test %in% all.tests))
84+
stop("Invalid test selected - must be either \"all\", \"SDM\", \"SDEM\" or a vector of tests")
85+
nt <- length(test)
86+
if (nt < 1) stop("non-positive number of tests")
87+
88+
if (!inherits(listw, "listw")) stop(paste(listw_name,
89+
"is not a listw object"))
90+
if (is.null(zero.policy))
91+
zero.policy <- get("zeroPolicy", envir = .spdepOptions)
92+
stopifnot(is.logical(zero.policy))
93+
if (!is.null(na.act)) {
94+
subset <- !(1:length(listw$neighbours) %in% na.act)
95+
listw <- subset(listw, subset, zero.policy=zero.policy)
96+
}
97+
98+
if(!inherits(model, "lm")) stop(paste(deparse(substitute(model)),
99+
"not an lm object"))
100+
N <- length(listw$neighbours)
101+
u <- resid(model)
102+
if (N != length(u)) stop("objects of different length")
103+
u <- as.vector(u)
104+
105+
if (is.null(attr(listw$weights, "W")) || !attr(listw$weights, "W"))
106+
warning("Spatial weights matrix not row standardized")
107+
108+
if (is.formula(Durbin)) {
109+
dt <- try(eval(model$call[["data"]]), silent=TRUE)
110+
if (inherits(dt, "try-error") || !is.data.frame(dt))
111+
stop("data object used to fit linear model not available for formula Durbin")
112+
}
113+
114+
y <- model.response(model.frame(model))
115+
X <- model.matrix(terms(model), model.frame(model))
116+
X0 <- create_X0(X=X, listw=listw, Durbin=Durbin, data=dt, na.act=na.act)
117+
yhat <- as.vector(fitted(model))
118+
p <- model$rank
119+
p1 <- 1:p
120+
nacoefs <- which(is.na(coefficients(model)))
121+
# fixed after looking at TOWN dummy in Boston data
122+
if (length(nacoefs) > 0L) X <- X[,-nacoefs]
123+
XtXinv <- chol2inv(model$qr$qr[p1, p1, drop = FALSE])
124+
sigma2 <- c(t(u) %*% u) / N
125+
TrW <- tracew(listw)
126+
Wu <- lag.listw(listw, u, zero.policy)
127+
Wy <- lag.listw(listw, y, zero.policy)
128+
dr <- (t(Wy) %*% u)/sigma2 # lagged y
129+
dl <- (t(Wu) %*% u)/sigma2 # lagged residuals
130+
Wyhat <- lag.listw(listw, yhat, zero.policy)
131+
WX0 <- lag.listw(listw, X0, zero.policy)
132+
dg <- c(t(WX0) %*% u)/sigma2
133+
k <- ncol(X)
134+
k0 <- ncol(X0)
135+
J_11 <- rbind(cbind((crossprod(X)/(N*sigma2)), rep(0, k)),
136+
cbind(t(rep(0, k)), (1/(2*(sigma2^2)))))
137+
invJ_11 <- solve(J_11)
138+
Jrp <- rbind((t(X) %*% Wyhat)/(N*sigma2), t(rep(0, 1)))
139+
Jgb <- (t(X) %*% WX0)/(N*sigma2)
140+
Jgp <- rbind(Jgb, t(rep(0, k0)))
141+
J_12 <- cbind(Jrp, Jgp)
142+
Jrr <- (c(crossprod(Wyhat)) + TrW*sigma2)/(N*sigma2)
143+
Jgg <- crossprod(WX0)/(N*sigma2)
144+
Jrg <- (t(WX0) %*% Wyhat)/(N*sigma2)
145+
J_22 <- rbind(cbind(Jrr, t(Jrg)), cbind(Jrg, Jgg))
146+
Jrg.p <- t(Jrg) - c(t(Jrp) %*% invJ_11 %*% Jgp)
147+
Jr.p <- Jrr - c(t(Jrp) %*% invJ_11 %*% Jrp)
148+
Jg.p <- Jgg - (t(Jgp) %*% invJ_11 %*% Jgp)
149+
invJg.p <- solve(Jg.p)
150+
dr_adj <- dr - (Jrg.p %*% invJg.p %*% dg)
151+
Jr.p_adj <- Jr.p - (Jrg.p %*% invJg.p %*% t(Jrg.p))
152+
dg_adj <- dg - c(dr * (1/Jr.p)) * Jrg.p
153+
Jg.p_adj <- Jg.p - ((1/Jr.p) * crossprod(Jrg.p))
154+
J.22 <- solve(J_22 - t(J_12) %*% invJ_11 %*% J_12)
155+
invJg.b <- solve(Jgg - t(Jgb) %*% solve(crossprod(X)/(N*sigma2)) %*%
156+
Jgb)
157+
tres <- vector(mode="list", length=nt)
158+
names(tres) <- test
159+
for (i in 1:nt) {
160+
testi <- test[i]
161+
zz <- switch(testi,
162+
SDM_RSlag = vec <- c((1/N) * ((dr^2) * 1/Jr.p), 1),
163+
SDM_adjRSlag = vec <- c((1/N)*((dr_adj^2)*(1/Jr.p_adj)), 1),
164+
SDM_RSWX = vec <- c((1/N) * (t(dg) %*% invJg.p %*% dg),
165+
ncol(X0)),
166+
SDM_adjRSWX = vec <- c((1/N) * (dg_adj %*% solve(Jg.p_adj) %*%
167+
t(dg_adj)), ncol(X0)),
168+
SDM_Joint = vec <- c(((1/N) * (t(c(dr, dg)) %*%
169+
J.22 %*% c(dr, dg))), ncol(X0)+1),
170+
SDEM_RSerr = vec <- c((dl^2) / TrW, 1),
171+
SDEM_RSWX = vec <- c(((t(dg) %*% invJg.b %*% dg) / N),
172+
ncol(X0)),
173+
SDEM_Joint = vec <- c(((t(dg) %*% invJg.b %*% dg) / N) +
174+
((dl^2) / TrW), ncol(X0)+1)
175+
)
176+
if (is.null(zz)) stop(paste(testi, ": no such test", sep=""))
177+
statistic <- vec[1]
178+
names(statistic) <- testi
179+
parameter <- vec[2]
180+
names(parameter) <- "df"
181+
p.value <- 1 - pchisq(statistic, parameter)
182+
if (!is.finite(p.value) || p.value < 0 || p.value > 1)
183+
warning("Out-of-range p-value: reconsider test arguments")
184+
names(p.value) <- ""
185+
method <- "Rao's score test spatial Durbin diagnostics"
186+
Durf <- ""
187+
if (is.formula(Durbin))
188+
Durf <- paste0("Durbin: ", paste(as.character(Durbin),
189+
collapse=" "), "\n")
190+
data.name <- paste("\n", paste(strwrap(paste("model: ",
191+
gsub("[ ]+", " ", paste(deparse(model$call),
192+
sep="", collapse="")))), collapse="\n"),
193+
"\nweights: ", listw_name, "\n", Durf, sep="")
194+
tres[[i]] <- list(statistic=statistic, parameter=parameter,
195+
p.value=p.value, method=method, data.name=data.name)
196+
class(tres[[i]]) <- "htest"
197+
}
198+
class(tres) <- "RStestlist"
199+
tres
200+
}
201+
202+

0 commit comments

Comments
 (0)