spatial_stats 0.2.2 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|