@@ -217,7 +217,7 @@ def __moments(self):
217217 self .VI_norm = v_num / v_den - (1.0 / (n - 1 )) ** 2
218218 self .seI_norm = self .VI_norm ** (1 / 2.0 )
219219
220- # variance under randomization
220+ # variance under total randomization
221221 xd4 = z ** 4
222222 xd2 = z ** 2
223223 k_num = xd4 .sum () / n
@@ -1005,6 +1005,7 @@ def __init__(
10051005 quads = [1 , 3 , 2 , 4 ]
10061006 self .quads = quads
10071007 self .__quads ()
1008+ self .__moments ()
10081009 if permutations :
10091010 self .p_sim , self .rlisas = _crand_plus (
10101011 z ,
@@ -1057,6 +1058,43 @@ def __quads(self):
10571058 + self .quads [3 ] * pn
10581059 )
10591060
1061+ def __moments (self ):
1062+ W = self .w .sparse
1063+ z = self .z
1064+ warnings .simplefilter ('always' , sparse .SparseEfficiencyWarning )
1065+ n = self .n
1066+ m2 = (z * z ).sum ()/ n
1067+ wi = numpy .asarray (W .sum (axis = 1 )).flatten ()
1068+ wi2 = numpy .asarray (W .multiply (W ).sum (axis = 1 )).flatten ()
1069+ # ---------------------------------------------------------
1070+ # Conditional randomization null, Sokal 1998, Eqs. A7 & A8
1071+ # ---------------------------------------------------------
1072+ expectation = - (z ** 2 * wi ) / ((n - 1 )* m2 )
1073+ variance = ((z / m2 )** 2 *
1074+ (n / (n - 2 )) *
1075+ ((wi2 - wi ** 2 ) / (n - 1 )) *
1076+ (m2 - z ** 2 ) / (n - 1 ))
1077+
1078+ self .EIc = expectation
1079+ self .VIc = variance
1080+ # ---------------------------------------------------------
1081+ # Total randomization null, Sokal 1998, Eqs. A3 & A4*
1082+ # ---------------------------------------------------------
1083+ m4 = z ** 4 / n
1084+ b2 = m4 / m2 ** 2
1085+
1086+ expectation = - wi / (n - 1 )
1087+ variance_sokal = (wi2 * (n - b2 )/ (n - 1 )
1088+ + (wi ** 2 - wi2 )* (2 * b2 - n )/ ((n - 1 )* (n - 2 ))
1089+ - (- wi / (n - 1 ))** 2 )
1090+ _wikh = wikh (W , sokal_correction = sokal_correction )
1091+ variance_anselin = (wi2 * (n - b2 )/ (n - 1 )
1092+ + 2 * _wikh * (2 * b2 - n ) / ((n - 1 )* (n - 2 ))
1093+ - wi ** 2 / (n - 1 )** 2 )
1094+ self .EI = expectation
1095+ self .VI_sokal = variance_sokal
1096+ self .VI_anselin = variance_anselin
1097+
10601098 @property
10611099 def _statistic (self ):
10621100 """More consistent hidden attribute to access ESDA statistics"""
@@ -1622,6 +1660,95 @@ def by_col(
16221660 for col in rate_df .columns :
16231661 df [col ] = rate_df [col ]
16241662
1663+ # --------------------------------------------------------------
1664+ # Conditional Randomization Moment Estimators
1665+ # --------------------------------------------------------------
1666+
1667+ def _wikh_fast (W , sokal_correction = False ):
1668+ """
1669+ This computes the outer product of weights for each observation.
1670+
1671+ w_{i(kh)} = \sum_{k \n eq i}^n \sum_{h \n eq i}^n w_ik * w_hk
1672+
1673+ if the sokal correction is used (default), then we also have h \n eq k
1674+ Since the sokal correction introduces a simplification in the expression
1675+ where this is used, the defaults should always return the version in
1676+ the original :cite:`Anselin1995 paper`.
1677+
1678+ Arguments
1679+ ---------
1680+ W : scipy sparse matrix
1681+ a sparse matrix describing the spatial relationships
1682+ between observations.
1683+ sokal_correction: bool
1684+ Whether to avoid self-neighbors in the summation of weights.
1685+ If False (default), then the outer product of all weights
1686+ for observation i are used, regardless if they are of the form
1687+ w_hh or w_kk.
1688+
1689+ Returns
1690+ -------
1691+ (n,) length numpy.ndarray containing the result.
1692+ """
1693+ return _wikh_numba (W .shape [0 ], * W .nonzero (), W .data ,
1694+ sokal_correction = sokal_correction )
1695+
1696+ @_njit (fastmath = True )
1697+ def _wikh_numba (n , row , col , data , sokal_correction = False ):
1698+ """
1699+ This is a fast implementation of the wi(kh) function from
1700+ :cite:`Anselin1995`.
1701+
1702+ This uses numpy to compute the outer product of each observation's
1703+ weights, after removing the w_ii entry. Then, the sum of the outer
1704+ product is taken. If the sokal correction is requested, the trace
1705+ of the outer product matrix is removed from the result.
1706+ """
1707+ result = numpy .empty ((n ,), dtype = data .dtype )
1708+ ixs = numpy .arange (n )
1709+ for i in ixs :
1710+ # all weights that are not the self weight
1711+ row_no_i = data [(row == i ) & (col != i )]
1712+ # compute the pairwise product
1713+ pairwise_product = numpy .outer (row_no_i , row_no_i )
1714+ # get the sum overall (wik*wih)
1715+ result [i ] = pairwise_product .sum ()
1716+ if sokal_correction :
1717+ # minus the diagonal (wik*wih when k==h)
1718+ result [i ] -= numpy .trace (pairwise_product )
1719+ return result / 2
1720+
1721+ def _wikh_slow (W , sokal_correction = False ):
1722+ """
1723+ This is a slow implementation of the wi(kh) function from
1724+ :cite:`Anselin1995`
1725+
1726+ This does three nested for-loops over n, doing the literal operations
1727+ stated by the expression.
1728+ """
1729+ W = W .toarray ()
1730+ (n ,n ) = W .shape
1731+ result = numpy .empty ((n ,))
1732+ # for each observation
1733+ for i in range (n ):
1734+ acc = 0
1735+ # we need the product wik
1736+ for k in range (n ):
1737+ # excluding wii * wih
1738+ if i == k :
1739+ continue
1740+ # and wij
1741+ for h in range (n ):
1742+ # excluding wik * wii
1743+ if (i == h ):
1744+ continue
1745+ if sokal_correction :
1746+ # excluding wih * wih
1747+ if (h == k ):
1748+ continue
1749+ acc += W [i ,k ] * W [i ,h ]
1750+ result [i ] = acc
1751+ return result / 2
16251752
16261753# --------------------------------------------------------------
16271754# Conditional Randomization Function Implementations
0 commit comments