spatial_stats 0.2.2 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +83 -11
- data/Rakefile +7 -0
- data/ext/spatial_stats/csr_matrix.c +365 -0
- data/ext/spatial_stats/csr_matrix.h +35 -0
- data/ext/spatial_stats/extconf.rb +6 -0
- data/ext/spatial_stats/spatial_stats.c +32 -0
- data/lib/spatial_stats/global/bivariate_moran.rb +43 -21
- data/lib/spatial_stats/global/moran.rb +43 -35
- data/lib/spatial_stats/global/stat.rb +31 -27
- data/lib/spatial_stats/local/bivariate_moran.rb +67 -2
- data/lib/spatial_stats/local/geary.rb +36 -5
- data/lib/spatial_stats/local/getis_ord.rb +45 -17
- data/lib/spatial_stats/local/moran.rb +40 -9
- data/lib/spatial_stats/local/multivariate_geary.rb +29 -10
- data/lib/spatial_stats/local/stat.rb +72 -36
- data/lib/spatial_stats/narray_ext.rb +5 -5
- data/lib/spatial_stats/spatial_stats.so +0 -0
- data/lib/spatial_stats/utils/lag.rb +10 -10
- data/lib/spatial_stats/version.rb +1 -1
- data/lib/spatial_stats/weights/contiguous.rb +20 -10
- data/lib/spatial_stats/weights/distant.rb +38 -20
- data/lib/spatial_stats/weights/weights_matrix.rb +60 -26
- data/lib/spatial_stats.rb +1 -0
- metadata +25 -3
@@ -20,6 +20,7 @@ module SpatialStats
|
|
20
20
|
def initialize(scope, field, weights)
|
21
21
|
super(scope, field, weights)
|
22
22
|
end
|
23
|
+
attr_writer :x
|
23
24
|
|
24
25
|
##
|
25
26
|
# Computes the local indicator of spatial autocorrelation (lisa) for
|
@@ -55,12 +56,11 @@ module SpatialStats
|
|
55
56
|
# @return [Array] of variances for each observation
|
56
57
|
def variance
|
57
58
|
# formula is A - B - (E[I])**2
|
58
|
-
wt = w.row_standardized
|
59
59
|
exp = expectation
|
60
60
|
|
61
61
|
vars = []
|
62
|
-
a_terms = a_calc
|
63
|
-
b_terms = b_calc
|
62
|
+
a_terms = a_calc
|
63
|
+
b_terms = b_calc
|
64
64
|
|
65
65
|
a_terms.each_with_index do |a_term, idx|
|
66
66
|
vars << (a_term - b_terms[idx] - (exp**2))
|
@@ -68,6 +68,21 @@ module SpatialStats
|
|
68
68
|
vars
|
69
69
|
end
|
70
70
|
|
71
|
+
##
|
72
|
+
# Computes the groups each observation belongs to.
|
73
|
+
# Potential groups for Moran's I are:
|
74
|
+
# [HH] High-High
|
75
|
+
# [HL] High-Low
|
76
|
+
# [LH] Low-High
|
77
|
+
# [LL] Low-Low
|
78
|
+
#
|
79
|
+
# This is the same as the +#quads+ method in the +Stat+ class.
|
80
|
+
#
|
81
|
+
# @return [Array] groups for each observation
|
82
|
+
def groups
|
83
|
+
quads
|
84
|
+
end
|
85
|
+
|
71
86
|
##
|
72
87
|
# Values of the +field+ queried from the +scope+
|
73
88
|
#
|
@@ -85,7 +100,7 @@ module SpatialStats
|
|
85
100
|
def z_lag
|
86
101
|
# w is already row_standardized, so we are using
|
87
102
|
# neighbor sum instead of neighbor_average to save cost
|
88
|
-
@z_lag ||= SpatialStats::Utils::Lag.neighbor_sum(
|
103
|
+
@z_lag ||= SpatialStats::Utils::Lag.neighbor_sum(weights, z)
|
89
104
|
end
|
90
105
|
|
91
106
|
private
|
@@ -102,6 +117,15 @@ module SpatialStats
|
|
102
117
|
z[idx] * z_lag_i
|
103
118
|
end
|
104
119
|
|
120
|
+
def mc_observation_calc(stat_i_orig, stat_i_new, _permutations)
|
121
|
+
# Since moran can be positive or negative, go by this definition
|
122
|
+
if stat_i_orig.positive?
|
123
|
+
(stat_i_new >= stat_i_orig).count
|
124
|
+
else
|
125
|
+
(stat_i_new <= stat_i_orig).count
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
105
129
|
def si2
|
106
130
|
# @si2 ||= z.sample_variance
|
107
131
|
# we standardize so sample_variance is 1
|
@@ -109,20 +133,27 @@ module SpatialStats
|
|
109
133
|
end
|
110
134
|
|
111
135
|
# https://pro.arcgis.com/en/pro-app/tool-reference/spatial-statistics/h-local-morans-i-additional-math.htm
|
112
|
-
|
113
|
-
|
136
|
+
# TODO: sparse
|
137
|
+
def a_calc
|
138
|
+
n = weights.n
|
114
139
|
b2i = b2i_calc
|
140
|
+
|
141
|
+
wts = weights.sparse.values
|
142
|
+
row_index = weights.sparse.row_index
|
143
|
+
|
115
144
|
a_terms = []
|
116
145
|
|
117
146
|
(0..n - 1).each do |idx|
|
118
|
-
|
147
|
+
row_range = row_index[idx]..(row_index[idx + 1] - 1)
|
148
|
+
wt = wts[row_range]
|
149
|
+
sigma_term = wt.sum { |v| v**2 }
|
119
150
|
a_terms << (n - b2i) * sigma_term / (n - 1)
|
120
151
|
end
|
121
152
|
a_terms
|
122
153
|
end
|
123
154
|
|
124
|
-
def b_calc
|
125
|
-
n =
|
155
|
+
def b_calc
|
156
|
+
n = weights.n
|
126
157
|
b2i = b2i_calc
|
127
158
|
b_terms = []
|
128
159
|
|
@@ -23,7 +23,7 @@ module SpatialStats
|
|
23
23
|
def initialize(scope, fields, weights)
|
24
24
|
@scope = scope
|
25
25
|
@fields = fields
|
26
|
-
@weights = weights
|
26
|
+
@weights = weights.standardize
|
27
27
|
end
|
28
28
|
attr_accessor :scope, :fields, :weights
|
29
29
|
|
@@ -67,22 +67,24 @@ module SpatialStats
|
|
67
67
|
stat_orig = stat
|
68
68
|
rs = [0] * n
|
69
69
|
|
70
|
-
|
70
|
+
row_index = weights.sparse.row_index
|
71
|
+
ws = weights.sparse.values
|
71
72
|
|
72
73
|
idx = 0
|
73
74
|
while idx < n
|
74
75
|
stat_i_orig = stat_orig[idx]
|
75
|
-
|
76
|
+
|
77
|
+
row_range = row_index[idx]..(row_index[idx + 1] - 1)
|
78
|
+
if row_range.size.zero?
|
79
|
+
rs[idx] = permutations
|
80
|
+
idx += 1
|
81
|
+
next
|
82
|
+
end
|
83
|
+
wi = Numo::DFloat.cast(ws[row_range])
|
76
84
|
|
77
85
|
# for each field, compute the C value at that index.
|
78
86
|
stat_i_new = mc_i(wi, shuffles[idx], idx)
|
79
|
-
|
80
|
-
rs[idx] = if stat_i_orig.positive?
|
81
|
-
(stat_i_new >= stat_i_orig).count
|
82
|
-
else
|
83
|
-
(stat_i_new <= stat_i_orig).count
|
84
|
-
end
|
85
|
-
|
87
|
+
rs[idx] = mc_observation_calc(stat_i_orig, stat_i_new, permutations)
|
86
88
|
idx += 1
|
87
89
|
end
|
88
90
|
|
@@ -91,6 +93,10 @@ module SpatialStats
|
|
91
93
|
end
|
92
94
|
end
|
93
95
|
|
96
|
+
def groups
|
97
|
+
raise NotImplementedError, 'groups not implemented'
|
98
|
+
end
|
99
|
+
|
94
100
|
private
|
95
101
|
|
96
102
|
def mc_i(wi, perms, idx)
|
@@ -108,6 +114,19 @@ module SpatialStats
|
|
108
114
|
cs.mean(0)
|
109
115
|
end
|
110
116
|
|
117
|
+
def mc_observation_calc(stat_i_orig, stat_i_new, _permutations)
|
118
|
+
# Geary cannot be negative, so we have to use this technique from
|
119
|
+
# GeoDa to determine p values. Note I slightly modified it to be inclusive
|
120
|
+
# on both tails not just the lower tail.
|
121
|
+
# https://github.com/GeoDaCenter/geoda/blob/master/Explore/LocalGearyCoordinator.cpp#L981 mean = stat_i_new.mean
|
122
|
+
mean = stat_i_new.mean
|
123
|
+
if stat_i_orig <= mean
|
124
|
+
(stat_i_new <= stat_i_orig).count
|
125
|
+
else
|
126
|
+
(stat_i_new >= stat_i_orig).count
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
111
130
|
def field_data
|
112
131
|
@field_data ||= fields.map do |field|
|
113
132
|
SpatialStats::Queries::Variables.query_field(@scope, field)
|
@@ -12,7 +12,7 @@ module SpatialStats
|
|
12
12
|
def initialize(scope, field, weights)
|
13
13
|
@scope = scope
|
14
14
|
@field = field
|
15
|
-
@weights = weights
|
15
|
+
@weights = weights.standardize
|
16
16
|
end
|
17
17
|
attr_accessor :scope, :field, :weights
|
18
18
|
|
@@ -62,17 +62,20 @@ module SpatialStats
|
|
62
62
|
# need to get k for max_neighbors
|
63
63
|
# and wc for cardinalities of each item
|
64
64
|
# this returns an array of length n with
|
65
|
-
# (permutations x
|
65
|
+
# (permutations x neighbors) Numo Arrays.
|
66
66
|
# This helps reduce computation time because
|
67
67
|
# we are only dealing with neighbors for each
|
68
68
|
# entry not the entire list of permutations for each entry.
|
69
69
|
n_1 = weights.n - 1
|
70
70
|
|
71
|
+
sparse = weights.sparse
|
72
|
+
row_index = sparse.row_index
|
73
|
+
|
71
74
|
# weight counts
|
72
|
-
wc =
|
75
|
+
wc = Array.new(weights.n)
|
73
76
|
k = 0
|
74
77
|
(0..n_1).each do |idx|
|
75
|
-
wc[idx] =
|
78
|
+
wc[idx] = row_index[idx + 1] - row_index[idx]
|
76
79
|
end
|
77
80
|
|
78
81
|
k = wc.max + 1
|
@@ -112,26 +115,43 @@ module SpatialStats
|
|
112
115
|
# of the entire set. This will be done for each item.
|
113
116
|
rng = gen_rng(seed)
|
114
117
|
shuffles = crand(x, permutations, rng)
|
118
|
+
|
115
119
|
n = weights.n
|
116
120
|
# r is the number of equal to or more extreme samples
|
117
121
|
stat_orig = stat
|
118
122
|
rs = [0] * n
|
119
123
|
|
120
|
-
|
124
|
+
row_index = weights.sparse.row_index
|
125
|
+
ws = weights.sparse.values
|
121
126
|
|
122
127
|
idx = 0
|
123
128
|
while idx < n
|
129
|
+
# need to truncate because floats from
|
130
|
+
# c in sparse matrix are inconsistent with
|
131
|
+
# dfloats
|
124
132
|
stat_i_orig = stat_orig[idx]
|
125
133
|
|
126
|
-
|
134
|
+
# account for case where there are no neighbors
|
135
|
+
# the way Numo handles negative ranges, it returns the max
|
136
|
+
# so there will be a len 0 z array being multiplied by a
|
137
|
+
# max_neighbor width permutation matrix.
|
138
|
+
# Need to skip.
|
139
|
+
row_range = row_index[idx]..(row_index[idx + 1] - 1)
|
140
|
+
if row_range.size.zero?
|
141
|
+
rs[idx] = permutations
|
142
|
+
idx += 1
|
143
|
+
next
|
144
|
+
end
|
145
|
+
wi = Numo::DFloat.cast(ws[row_range])
|
127
146
|
stat_i_new = mc_i(wi, shuffles[idx], idx)
|
128
147
|
|
129
|
-
rs[idx] =
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
148
|
+
rs[idx] = mc_observation_calc(stat_i_orig, stat_i_new,
|
149
|
+
permutations)
|
150
|
+
# rs[idx] = if stat_i_orig.positive?
|
151
|
+
# (stat_i_new >= stat_i_orig).count
|
152
|
+
# else
|
153
|
+
# (stat_i_new <= stat_i_orig).count
|
154
|
+
# end
|
135
155
|
idx += 1
|
136
156
|
end
|
137
157
|
|
@@ -160,19 +180,30 @@ module SpatialStats
|
|
160
180
|
stat_orig = stat
|
161
181
|
rs = [0] * n
|
162
182
|
|
163
|
-
|
183
|
+
row_index = weights.sparse.row_index
|
184
|
+
ws = weights.sparse.values
|
164
185
|
|
165
186
|
idx = 0
|
166
187
|
while idx < n
|
167
188
|
stat_i_orig = stat_orig[idx]
|
168
|
-
|
189
|
+
|
190
|
+
row_range = row_index[idx]..(row_index[idx + 1] - 1)
|
191
|
+
if row_range.size.zero?
|
192
|
+
rs[idx] = permutations
|
193
|
+
idx += 1
|
194
|
+
next
|
195
|
+
end
|
196
|
+
wi = Numo::DFloat.cast(ws[row_range])
|
197
|
+
|
169
198
|
stat_i_new = mc_i(wi, shuffles[idx], idx)
|
170
199
|
|
171
|
-
rs[idx] =
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
200
|
+
rs[idx] = mc_observation_calc(stat_i_orig, stat_i_new,
|
201
|
+
permutations)
|
202
|
+
# if stat_i_orig.positive?
|
203
|
+
# (stat_i_new >= stat_i_orig).count
|
204
|
+
# else
|
205
|
+
# (stat_i_new <= stat_i_orig).count
|
206
|
+
# end
|
176
207
|
|
177
208
|
idx += 1
|
178
209
|
end
|
@@ -200,8 +231,7 @@ module SpatialStats
|
|
200
231
|
# @return [Array] of labels
|
201
232
|
def quads
|
202
233
|
# https://github.com/pysal/esda/blob/master/esda/moran.py#L925
|
203
|
-
|
204
|
-
z_lag = SpatialStats::Utils::Lag.neighbor_average(w, z)
|
234
|
+
z_lag = SpatialStats::Utils::Lag.neighbor_average(weights, z)
|
205
235
|
zp = z.map(&:positive?)
|
206
236
|
lp = z_lag.map(&:positive?)
|
207
237
|
|
@@ -221,6 +251,22 @@ module SpatialStats
|
|
221
251
|
end
|
222
252
|
end
|
223
253
|
|
254
|
+
##
|
255
|
+
# Summary of the statistic. Computes +stat+, +mc+, and +groups+ then returns the values
|
256
|
+
# in a hash array.
|
257
|
+
#
|
258
|
+
# @param [Integer] permutations to run. Last digit should be 9 to produce round numbers.
|
259
|
+
# @param [Integer] seed used in random number generator for shuffles.
|
260
|
+
#
|
261
|
+
# @return [Array]
|
262
|
+
def summary(permutations = 99, seed = nil)
|
263
|
+
p_vals = mc(permutations, seed)
|
264
|
+
data = weights.keys.zip(stat, p_vals, groups)
|
265
|
+
data.map do |row|
|
266
|
+
{ key: row[0], stat: row[1], p: row[2], group: row[3] }
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
224
270
|
private
|
225
271
|
|
226
272
|
def stat_i
|
@@ -231,8 +277,12 @@ module SpatialStats
|
|
231
277
|
raise NotImplementedError, 'method mc_i not defined'
|
232
278
|
end
|
233
279
|
|
280
|
+
def mc_observation_calc(_stat_i_orig, _stat_i_new, _permutations)
|
281
|
+
raise NotImplementedError, 'method mc_observation_calc not defined'
|
282
|
+
end
|
283
|
+
|
234
284
|
def w
|
235
|
-
weights.
|
285
|
+
@w ||= weights.dense
|
236
286
|
end
|
237
287
|
|
238
288
|
def gen_rng(seed = nil)
|
@@ -242,20 +292,6 @@ module SpatialStats
|
|
242
292
|
Random.new
|
243
293
|
end
|
244
294
|
end
|
245
|
-
|
246
|
-
def neighbor_weights
|
247
|
-
# record the non-zero weights in variable length arrays for each
|
248
|
-
# row in the weights table
|
249
|
-
ws = [[]] * weights.n
|
250
|
-
(0..weights.n - 1).each do |idx|
|
251
|
-
neighbors = []
|
252
|
-
w[idx, true].each do |wij|
|
253
|
-
neighbors << wij if wij != 0
|
254
|
-
end
|
255
|
-
ws[idx] = neighbors
|
256
|
-
end
|
257
|
-
ws
|
258
|
-
end
|
259
295
|
end
|
260
296
|
end
|
261
297
|
end
|
@@ -12,11 +12,11 @@ module Numo
|
|
12
12
|
#
|
13
13
|
# @ example
|
14
14
|
#
|
15
|
-
# Numo::DFloat [[0, 1, 1], [1, 1, 1]].
|
15
|
+
# Numo::DFloat [[0, 1, 1], [1, 1, 1]].row_standardize
|
16
16
|
# Numo::DFloat [[0, 0.5, 0.5], [0.33333, 0.33333, 0.33333]]
|
17
17
|
#
|
18
18
|
# @return [Numo::NArray]
|
19
|
-
def
|
19
|
+
def row_standardize
|
20
20
|
# every row will sum up to 1, or if they are all 0, do nothing
|
21
21
|
standardized = each_over_axis.map do |row|
|
22
22
|
sum = row.sum
|
@@ -38,16 +38,16 @@ module Numo
|
|
38
38
|
#
|
39
39
|
# @ example
|
40
40
|
#
|
41
|
-
# Numo::DFloat [[0, 1, 0], [1, 0, 1], [0, 1, 0]].
|
41
|
+
# Numo::DFloat [[0, 1, 0], [1, 0, 1], [0, 1, 0]].window
|
42
42
|
# Numo::DFloat [[1, 1, 0], [1, 1, 1], [0, 1, 1]]
|
43
43
|
#
|
44
44
|
# @ example
|
45
45
|
# # Input will be equivalent to output in this case
|
46
|
-
# Numo::DFloat [[1, 1, 0], [1, 0, 1], [0, 1, 0]].
|
46
|
+
# Numo::DFloat [[1, 1, 0], [1, 0, 1], [0, 1, 0]].window
|
47
47
|
# Numo::DFloat [[1, 1, 0], [1, 0, 1], [0, 1, 0]]
|
48
48
|
#
|
49
49
|
# @return [Numo::NArray]
|
50
|
-
def
|
50
|
+
def window
|
51
51
|
# in windowed calculations, the diagonal is set to 1
|
52
52
|
# if trace (sum of diag) is 0, add it, else return input
|
53
53
|
if trace.zero?
|
Binary file
|
@@ -11,36 +11,36 @@ module SpatialStats
|
|
11
11
|
# Dot product of the row_standardized input matrix
|
12
12
|
# by the input vector, variables.
|
13
13
|
#
|
14
|
-
# @param [
|
14
|
+
# @param [WeightsMatrix] matrix holding target weights.
|
15
15
|
# @param [Array] variables vector multiplying the matrix
|
16
16
|
#
|
17
17
|
# @return [Array] resultant vector
|
18
18
|
def self.neighbor_average(matrix, variables)
|
19
|
-
matrix = matrix.
|
19
|
+
matrix = matrix.standardize
|
20
20
|
neighbor_sum(matrix, variables)
|
21
21
|
end
|
22
22
|
|
23
23
|
##
|
24
24
|
# Dot product of the input matrix by the input vector, variables.
|
25
25
|
#
|
26
|
-
# @param [
|
26
|
+
# @param [WeightsMatrix] matrix holding target weights.
|
27
27
|
# @param [Array] variables vector multiplying the matrix
|
28
28
|
#
|
29
29
|
# @return [Array] resultant vector
|
30
30
|
def self.neighbor_sum(matrix, variables)
|
31
|
-
matrix.
|
31
|
+
matrix.sparse.mulvec(variables)
|
32
32
|
end
|
33
33
|
|
34
34
|
##
|
35
|
-
# Dot product of the input windowed, row
|
35
|
+
# Dot product of the input windowed, row standardized matrix by
|
36
36
|
# the input vector, variables.
|
37
37
|
#
|
38
|
-
# @param [
|
38
|
+
# @param [WeightsMatrix] matrix holding target weights.
|
39
39
|
# @param [Array] variables vector multiplying the matrix
|
40
40
|
#
|
41
41
|
# @return [Array] resultant vector
|
42
42
|
def self.window_average(matrix, variables)
|
43
|
-
matrix = matrix.
|
43
|
+
matrix = matrix.window.standardize
|
44
44
|
window_sum(matrix, variables)
|
45
45
|
end
|
46
46
|
|
@@ -48,13 +48,13 @@ module SpatialStats
|
|
48
48
|
# Dot product of the input windowed matrix by
|
49
49
|
# the input vector, variables.
|
50
50
|
#
|
51
|
-
# @param [
|
51
|
+
# @param [WeightsMatrix] matrix holding target weights.
|
52
52
|
# @param [Array] variables vector multiplying the matrix
|
53
53
|
#
|
54
54
|
# @return [Array] resultant vector
|
55
55
|
def self.window_sum(matrix, variables)
|
56
|
-
matrix = matrix.
|
57
|
-
matrix.
|
56
|
+
matrix = matrix.window
|
57
|
+
matrix.sparse.mulvec(variables)
|
58
58
|
end
|
59
59
|
end
|
60
60
|
end
|
@@ -15,21 +15,26 @@ module SpatialStats
|
|
15
15
|
#
|
16
16
|
# @return [WeightsMatrix]
|
17
17
|
def self.rook(scope, field)
|
18
|
-
p_key = scope.primary_key
|
19
|
-
keys = scope.pluck(p_key).sort
|
20
|
-
|
21
18
|
neighbors = SpatialStats::Queries::Weights
|
22
19
|
.rook_contiguity_neighbors(scope, field)
|
23
20
|
|
21
|
+
# get keys to make sure we have consistent dimensions when
|
22
|
+
# some entries don't have neighbors.
|
23
|
+
# define a new hash that has all the keys from scope
|
24
|
+
keys = SpatialStats::Queries::Variables.query_field(scope, scope.klass.primary_key)
|
25
|
+
|
24
26
|
neighbors = neighbors.group_by(&:i_id)
|
27
|
+
missing_neighbors = Hash[(keys - neighbors.keys).map { |key| [key, []] }]
|
28
|
+
neighbors = neighbors.merge(missing_neighbors)
|
29
|
+
|
25
30
|
weights = neighbors.transform_values do |value|
|
26
31
|
value.map do |neighbor|
|
27
|
-
hash =
|
32
|
+
hash = { id: neighbor[:j_id] }
|
28
33
|
hash[:weight] = 1
|
29
34
|
hash
|
30
35
|
end
|
31
36
|
end
|
32
|
-
SpatialStats::Weights::WeightsMatrix.new(
|
37
|
+
SpatialStats::Weights::WeightsMatrix.new(weights)
|
33
38
|
end
|
34
39
|
|
35
40
|
##
|
@@ -40,21 +45,26 @@ module SpatialStats
|
|
40
45
|
#
|
41
46
|
# @return [WeightsMatrix]
|
42
47
|
def self.queen(scope, field)
|
43
|
-
p_key = scope.primary_key
|
44
|
-
keys = scope.pluck(p_key).sort
|
45
|
-
|
46
48
|
neighbors = SpatialStats::Queries::Weights
|
47
49
|
.queen_contiguity_neighbors(scope, field)
|
48
50
|
|
51
|
+
# get keys to make sure we have consistent dimensions when
|
52
|
+
# some entries don't have neighbors.
|
53
|
+
# define a new hash that has all the keys from scope
|
54
|
+
keys = SpatialStats::Queries::Variables.query_field(scope, scope.klass.primary_key)
|
55
|
+
|
49
56
|
neighbors = neighbors.group_by(&:i_id)
|
57
|
+
missing_neighbors = Hash[(keys - neighbors.keys).map { |key| [key, []] }]
|
58
|
+
neighbors = neighbors.merge(missing_neighbors)
|
59
|
+
|
50
60
|
weights = neighbors.transform_values do |value|
|
51
61
|
value.map do |neighbor|
|
52
|
-
hash =
|
62
|
+
hash = { id: neighbor[:j_id] }
|
53
63
|
hash[:weight] = 1
|
54
64
|
hash
|
55
65
|
end
|
56
66
|
end
|
57
|
-
SpatialStats::Weights::WeightsMatrix.new(
|
67
|
+
SpatialStats::Weights::WeightsMatrix.new(weights)
|
58
68
|
end
|
59
69
|
end
|
60
70
|
end
|
@@ -15,21 +15,26 @@ module SpatialStats
|
|
15
15
|
#
|
16
16
|
# @return [WeightsMatrix]
|
17
17
|
def self.distance_band(scope, field, bandwidth)
|
18
|
-
p_key = scope.primary_key
|
19
|
-
keys = scope.pluck(p_key).sort
|
20
|
-
|
21
18
|
neighbors = SpatialStats::Queries::Weights
|
22
19
|
.distance_band_neighbors(scope, field, bandwidth)
|
23
20
|
|
21
|
+
# get keys to make sure we have consistent dimensions when
|
22
|
+
# some entries don't have neighbors.
|
23
|
+
# define a new hash that has all the keys from scope
|
24
|
+
keys = SpatialStats::Queries::Variables.query_field(scope, scope.klass.primary_key)
|
25
|
+
|
24
26
|
neighbors = neighbors.group_by(&:i_id)
|
27
|
+
missing_neighbors = Hash[(keys - neighbors.keys).map { |key| [key, []] }]
|
28
|
+
neighbors = neighbors.merge(missing_neighbors)
|
29
|
+
|
25
30
|
weights = neighbors.transform_values do |value|
|
26
31
|
value.map do |neighbor|
|
27
|
-
hash =
|
32
|
+
hash = { id: neighbor[:j_id] }
|
28
33
|
hash[:weight] = 1
|
29
34
|
hash
|
30
35
|
end
|
31
36
|
end
|
32
|
-
SpatialStats::Weights::WeightsMatrix.new(
|
37
|
+
SpatialStats::Weights::WeightsMatrix.new(weights)
|
33
38
|
end
|
34
39
|
|
35
40
|
##
|
@@ -41,21 +46,26 @@ module SpatialStats
|
|
41
46
|
#
|
42
47
|
# @return [WeightsMatrix]
|
43
48
|
def self.knn(scope, field, k)
|
44
|
-
p_key = scope.primary_key
|
45
|
-
keys = scope.pluck(p_key).sort
|
46
|
-
|
47
49
|
neighbors = SpatialStats::Queries::Weights
|
48
50
|
.knn(scope, field, k)
|
49
51
|
|
52
|
+
# get keys to make sure we have consistent dimensions when
|
53
|
+
# some entries don't have neighbors.
|
54
|
+
# define a new hash that has all the keys from scope
|
55
|
+
keys = SpatialStats::Queries::Variables.query_field(scope, scope.klass.primary_key)
|
56
|
+
|
50
57
|
neighbors = neighbors.group_by(&:i_id)
|
58
|
+
missing_neighbors = Hash[(keys - neighbors.keys).map { |key| [key, []] }]
|
59
|
+
neighbors = neighbors.merge(missing_neighbors)
|
60
|
+
|
51
61
|
weights = neighbors.transform_values do |value|
|
52
62
|
value.map do |neighbor|
|
53
|
-
hash =
|
63
|
+
hash = { id: neighbor[:j_id] }
|
54
64
|
hash[:weight] = 1
|
55
65
|
hash
|
56
66
|
end
|
57
67
|
end
|
58
|
-
SpatialStats::Weights::WeightsMatrix.new(
|
68
|
+
SpatialStats::Weights::WeightsMatrix.new(weights)
|
59
69
|
end
|
60
70
|
|
61
71
|
##
|
@@ -68,20 +78,24 @@ module SpatialStats
|
|
68
78
|
#
|
69
79
|
# @return [WeightsMatrix]
|
70
80
|
def self.idw_band(scope, field, bandwidth, alpha = 1)
|
71
|
-
p_key = scope.primary_key
|
72
|
-
keys = scope.pluck(p_key).sort
|
73
|
-
|
74
81
|
neighbors = SpatialStats::Queries::Weights
|
75
82
|
.idw_band(scope, field, bandwidth, alpha)
|
83
|
+
|
84
|
+
# get keys to make sure we have consistent dimensions when
|
85
|
+
# some entries don't have neighbors.
|
86
|
+
# define a new hash that has all the keys from scope
|
87
|
+
keys = SpatialStats::Queries::Variables.query_field(scope, scope.klass.primary_key)
|
76
88
|
neighbors = neighbors.group_by { |pair| pair[:i_id] }
|
89
|
+
missing_neighbors = Hash[(keys - neighbors.keys).map { |key| [key, []] }]
|
90
|
+
neighbors = neighbors.merge(missing_neighbors)
|
77
91
|
|
78
92
|
# only keep j_id and weight
|
79
93
|
weights = neighbors.transform_values do |value|
|
80
94
|
value.map do |neighbor|
|
81
|
-
{ weight: neighbor[:weight],
|
95
|
+
{ weight: neighbor[:weight], id: neighbor[:j_id] }
|
82
96
|
end
|
83
97
|
end
|
84
|
-
SpatialStats::Weights::WeightsMatrix.new(
|
98
|
+
SpatialStats::Weights::WeightsMatrix.new(weights)
|
85
99
|
end
|
86
100
|
|
87
101
|
##
|
@@ -94,20 +108,24 @@ module SpatialStats
|
|
94
108
|
#
|
95
109
|
# @return [WeightsMatrix]
|
96
110
|
def self.idw_knn(scope, field, k, alpha = 1)
|
97
|
-
p_key = scope.primary_key
|
98
|
-
keys = scope.pluck(p_key).sort
|
99
|
-
|
100
111
|
neighbors = SpatialStats::Queries::Weights
|
101
112
|
.idw_knn(scope, field, k, alpha)
|
113
|
+
|
114
|
+
# get keys to make sure we have consistent dimensions when
|
115
|
+
# some entries don't have neighbors.
|
116
|
+
# define a new hash that has all the keys from scope
|
117
|
+
keys = SpatialStats::Queries::Variables.query_field(scope, scope.klass.primary_key)
|
102
118
|
neighbors = neighbors.group_by { |pair| pair[:i_id] }
|
119
|
+
missing_neighbors = Hash[(keys - neighbors.keys).map { |key| [key, []] }]
|
120
|
+
neighbors = neighbors.merge(missing_neighbors)
|
103
121
|
|
104
122
|
# only keep j_id and weight
|
105
123
|
weights = neighbors.transform_values do |value|
|
106
124
|
value.map do |neighbor|
|
107
|
-
{ weight: neighbor[:weight],
|
125
|
+
{ weight: neighbor[:weight], id: neighbor[:j_id] }
|
108
126
|
end
|
109
127
|
end
|
110
|
-
SpatialStats::Weights::WeightsMatrix.new(
|
128
|
+
SpatialStats::Weights::WeightsMatrix.new(weights)
|
111
129
|
end
|
112
130
|
end
|
113
131
|
end
|