spatial_stats 0.1.1 → 0.2.1
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 +185 -9
- data/lib/spatial_stats.rb +7 -4
- data/lib/spatial_stats/enumerable_ext.rb +29 -0
- data/lib/spatial_stats/global.rb +15 -0
- data/lib/spatial_stats/global/bivariate_moran.rb +48 -4
- data/lib/spatial_stats/global/moran.rb +69 -19
- data/lib/spatial_stats/global/stat.rb +29 -17
- data/lib/spatial_stats/local.rb +16 -1
- data/lib/spatial_stats/local/bivariate_moran.rb +45 -4
- data/lib/spatial_stats/local/geary.rb +34 -47
- data/lib/spatial_stats/local/getis_ord.rb +109 -0
- data/lib/spatial_stats/local/moran.rb +55 -22
- data/lib/spatial_stats/local/multivariate_geary.rb +77 -22
- data/lib/spatial_stats/local/stat.rb +160 -88
- data/lib/spatial_stats/narray_ext.rb +27 -0
- data/lib/spatial_stats/queries.rb +6 -0
- data/lib/spatial_stats/queries/variables.rb +16 -3
- data/lib/spatial_stats/queries/weights.rb +91 -9
- data/lib/spatial_stats/utils.rb +7 -0
- data/lib/spatial_stats/utils/lag.rb +34 -2
- data/lib/spatial_stats/version.rb +1 -1
- data/lib/spatial_stats/weights.rb +9 -0
- data/lib/spatial_stats/weights/contiguous.rb +18 -0
- data/lib/spatial_stats/weights/distant.rb +41 -4
- data/lib/spatial_stats/weights/weights_matrix.rb +25 -0
- metadata +5 -4
- data/lib/spatial_stats/local/g.rb +0 -75
@@ -3,7 +3,19 @@
|
|
3
3
|
require 'numo/narray'
|
4
4
|
|
5
5
|
module Numo
|
6
|
+
##
|
7
|
+
# Extension to Numo::NArray base class.
|
6
8
|
class NArray
|
9
|
+
##
|
10
|
+
# For a 2-D NArray, transform the non-zero values so that the sum of each
|
11
|
+
# row is 1.
|
12
|
+
#
|
13
|
+
# @ example
|
14
|
+
#
|
15
|
+
# Numo::DFloat [[0, 1, 1], [1, 1, 1]].row_standardized
|
16
|
+
# Numo::DFloat [[0, 0.5, 0.5], [0.33333, 0.33333, 0.33333]]
|
17
|
+
#
|
18
|
+
# @return [Numo::NArray]
|
7
19
|
def row_standardized
|
8
20
|
# every row will sum up to 1, or if they are all 0, do nothing
|
9
21
|
standardized = each_over_axis.map do |row|
|
@@ -20,6 +32,21 @@ module Numo
|
|
20
32
|
self.class.cast(standardized)
|
21
33
|
end
|
22
34
|
|
35
|
+
##
|
36
|
+
# For a 2-D, n x n NArray, if the trace is 0, add an n x n eye matrix to the matrix
|
37
|
+
# and return the result.
|
38
|
+
#
|
39
|
+
# @ example
|
40
|
+
#
|
41
|
+
# Numo::DFloat [[0, 1, 0], [1, 0, 1], [0, 1, 0]].windowed
|
42
|
+
# Numo::DFloat [[1, 1, 0], [1, 1, 1], [0, 1, 1]]
|
43
|
+
#
|
44
|
+
# @ example
|
45
|
+
# # Input will be equivalent to output in this case
|
46
|
+
# Numo::DFloat [[1, 1, 0], [1, 0, 1], [0, 1, 0]].windowed
|
47
|
+
# Numo::DFloat [[1, 1, 0], [1, 0, 1], [0, 1, 0]]
|
48
|
+
#
|
49
|
+
# @return [Numo::NArray]
|
23
50
|
def windowed
|
24
51
|
# in windowed calculations, the diagonal is set to 1
|
25
52
|
# if trace (sum of diag) is 0, add it, else return input
|
@@ -2,10 +2,23 @@
|
|
2
2
|
|
3
3
|
module SpatialStats
|
4
4
|
module Queries
|
5
|
+
##
|
6
|
+
# Variables includes a method to query a field from a given scope and
|
7
|
+
# keep it in a consistent order with how weights are queried.
|
5
8
|
module Variables
|
6
|
-
|
7
|
-
#
|
8
|
-
#
|
9
|
+
##
|
10
|
+
# Query the given field for a scope and order by primary key
|
11
|
+
#
|
12
|
+
# @example
|
13
|
+
# scope = County.all
|
14
|
+
# field = :avg_income
|
15
|
+
# SpatialStats::Queries::Variables.query_field(scope, field)
|
16
|
+
# # => [30023, 23400, 57800, ...]
|
17
|
+
#
|
18
|
+
# @param [ActiveRecord::Relation] scope you want to query
|
19
|
+
# @param [Symbol, String] field you want to query from the scope
|
20
|
+
#
|
21
|
+
# @return [Array]
|
9
22
|
def self.query_field(scope, field)
|
10
23
|
klass = scope.klass
|
11
24
|
column = ActiveRecord::Base.connection.quote_column_name(field)
|
@@ -2,14 +2,31 @@
|
|
2
2
|
|
3
3
|
module SpatialStats
|
4
4
|
module Queries
|
5
|
-
|
6
|
-
#
|
5
|
+
##
|
6
|
+
# Weights includes methods for querying a scope using PostGIS sql methods
|
7
|
+
# to determine neighbors and weights based on different weighting
|
8
|
+
# schemes/formulas.
|
7
9
|
module Weights
|
8
|
-
|
10
|
+
##
|
11
|
+
# Compute inverse distance weighted, k nearest neighbors weights
|
12
|
+
# for a given scope and geometry.
|
13
|
+
#
|
14
|
+
# Combines knn and idw weightings. Each observation will have
|
15
|
+
# k neighbors, but the weights will be calculated by 1/(d**alpha).
|
16
|
+
#
|
17
|
+
# Only works for geometry types that implement ST_Distance.
|
18
|
+
#
|
19
|
+
# @param [ActiveRecord::Relation] scope you want to query
|
20
|
+
# @param [Symbol, String] column that contains the geometry
|
21
|
+
# @param [Integer] k neighbors to find
|
22
|
+
# @param [Integer] alpha number used in inverse calculations (usually 1 or 2)
|
23
|
+
#
|
24
|
+
# @return [Hash]
|
25
|
+
def self.idw_knn(scope, column, k, alpha = 1)
|
9
26
|
klass = scope.klass
|
10
27
|
column = ActiveRecord::Base.connection.quote_column_name(column)
|
11
28
|
primary_key = klass.quoted_primary_key
|
12
|
-
neighbors = klass.find_by_sql([<<-SQL, scope: scope,
|
29
|
+
neighbors = klass.find_by_sql([<<-SQL, scope: scope, k: k])
|
13
30
|
WITH scope as (:scope)
|
14
31
|
SELECT neighbors.*
|
15
32
|
FROM scope AS a
|
@@ -19,7 +36,7 @@ module SpatialStats
|
|
19
36
|
FROM scope as b
|
20
37
|
WHERE a.#{primary_key} <> b.#{primary_key}
|
21
38
|
ORDER BY a.#{column} <-> b.#{column}
|
22
|
-
LIMIT :
|
39
|
+
LIMIT :k
|
23
40
|
) AS neighbors
|
24
41
|
SQL
|
25
42
|
|
@@ -41,6 +58,21 @@ module SpatialStats
|
|
41
58
|
end
|
42
59
|
end
|
43
60
|
|
61
|
+
##
|
62
|
+
# Compute inverse distance weighted, band limited weights
|
63
|
+
# for a given scope and geometry.
|
64
|
+
#
|
65
|
+
# Combines distance_band and idw weightings. Each observation will have
|
66
|
+
# neighbers in the bandwidth, but the weights will be calculated by 1/(d**alpha).
|
67
|
+
#
|
68
|
+
# Only works for geometry types that implement ST_Distance and ST_DWithin.
|
69
|
+
#
|
70
|
+
# @param [ActiveRecord::Relation] scope you want to query
|
71
|
+
# @param [Symbol, String] column that contains the geometry
|
72
|
+
# @param [Numeric] bandwidth to find neighbors in
|
73
|
+
# @param [Integer] alpha number used in inverse calculations (usually 1 or 2)
|
74
|
+
#
|
75
|
+
# @return [Hash]
|
44
76
|
def self.idw_band(scope, column, bandwidth, alpha = 1)
|
45
77
|
klass = scope.klass
|
46
78
|
column = ActiveRecord::Base.connection.quote_column_name(column)
|
@@ -75,11 +107,19 @@ module SpatialStats
|
|
75
107
|
end
|
76
108
|
end
|
77
109
|
|
78
|
-
|
110
|
+
##
|
111
|
+
# Compute k nearest neighbor weights for a given scope.
|
112
|
+
#
|
113
|
+
# @param [ActiveRecord::Relation] scope you want to query
|
114
|
+
# @param [Symbol, String] column that contains the geometry
|
115
|
+
# @param [Integer] k neighbors to find
|
116
|
+
#
|
117
|
+
# @return [Hash]
|
118
|
+
def self.knn(scope, column, k)
|
79
119
|
klass = scope.klass
|
80
120
|
column = ActiveRecord::Base.connection.quote_column_name(column)
|
81
121
|
primary_key = klass.quoted_primary_key
|
82
|
-
klass.find_by_sql([<<-SQL, scope: scope,
|
122
|
+
klass.find_by_sql([<<-SQL, scope: scope, k: k])
|
83
123
|
WITH scope as (:scope)
|
84
124
|
SELECT neighbors.*
|
85
125
|
FROM scope AS a
|
@@ -88,11 +128,21 @@ module SpatialStats
|
|
88
128
|
FROM scope as b
|
89
129
|
WHERE a.#{primary_key} <> b.#{primary_key}
|
90
130
|
ORDER BY a.#{column} <-> b.#{column}
|
91
|
-
LIMIT :
|
131
|
+
LIMIT :k
|
92
132
|
) AS neighbors
|
93
133
|
SQL
|
94
134
|
end
|
95
135
|
|
136
|
+
##
|
137
|
+
# Compute distance band weights for a given scope. Identifies neighbors
|
138
|
+
# as other observations in scope that are within the distance band
|
139
|
+
# from the observation.
|
140
|
+
#
|
141
|
+
# @param [ActiveRecord::Relation] scope you want to query
|
142
|
+
# @param [Symbol, String] column that contains the geometry
|
143
|
+
# @param [Numeric] bandwidth to find neighbors in
|
144
|
+
#
|
145
|
+
# @return [Hash]
|
96
146
|
def self.distance_band_neighbors(scope, column, bandwidth)
|
97
147
|
klass = scope.klass
|
98
148
|
column = ActiveRecord::Base.connection.quote_column_name(column)
|
@@ -109,15 +159,47 @@ module SpatialStats
|
|
109
159
|
SQL
|
110
160
|
end
|
111
161
|
|
112
|
-
|
162
|
+
##
|
163
|
+
# Compute queen contiguity weights for a given scope. Queen
|
164
|
+
# contiguity weights are defined by geometries sharing an edge
|
165
|
+
# or vertex.
|
166
|
+
#
|
167
|
+
# DE-9IM pattern = +F***T****+
|
168
|
+
#
|
169
|
+
# @param [ActiveRecord::Relation] scope you want to query
|
170
|
+
# @param [Symbol, String] column that contains the geometry
|
171
|
+
#
|
172
|
+
# @return [Hash]
|
113
173
|
def self.queen_contiguity_neighbors(scope, column)
|
114
174
|
_contiguity_neighbors(scope, column, 'F***T****')
|
115
175
|
end
|
116
176
|
|
177
|
+
##
|
178
|
+
# Compute rook contiguity weights for a given scope. Rook
|
179
|
+
# contiguity weights are defined by geometries sharing an edge.
|
180
|
+
#
|
181
|
+
# DE-9IM pattern = +'F***1****'+
|
182
|
+
#
|
183
|
+
# @param [ActiveRecord::Relation] scope you want to query
|
184
|
+
# @param [Symbol, String] column that contains the geometry
|
185
|
+
#
|
186
|
+
# @return [Hash]
|
117
187
|
def self.rook_contiguity_neighbors(scope, column)
|
118
188
|
_contiguity_neighbors(scope, column, 'F***1****')
|
119
189
|
end
|
120
190
|
|
191
|
+
##
|
192
|
+
# Generic function to compute contiguity neighbor weights for a
|
193
|
+
# given scope. Takes any valid DE-9IM pattern and computes the
|
194
|
+
# neighbors based off of that.
|
195
|
+
#
|
196
|
+
# @see https://en.wikipedia.org/wiki/DE-9IM
|
197
|
+
#
|
198
|
+
# @param [ActiveRecord::Relation] scope you want to query
|
199
|
+
# @param [Symbol, String] column that contains the geometry
|
200
|
+
# @param [String] pattern to describe neighbor relation
|
201
|
+
#
|
202
|
+
# @return [Hash]
|
121
203
|
def self._contiguity_neighbors(scope, column, pattern)
|
122
204
|
klass = scope.klass
|
123
205
|
column = ActiveRecord::Base.connection.quote_column_name(column)
|
data/lib/spatial_stats/utils.rb
CHANGED
@@ -3,23 +3,55 @@
|
|
3
3
|
require 'numo/narray'
|
4
4
|
module SpatialStats
|
5
5
|
module Utils
|
6
|
+
##
|
7
|
+
# Lag includes methdos for computing spatially lagged variables under
|
8
|
+
# different contexts.
|
6
9
|
module Lag
|
7
|
-
|
8
|
-
#
|
10
|
+
##
|
11
|
+
# Dot product of the row_standardized input matrix
|
12
|
+
# by the input vector, variables.
|
13
|
+
#
|
14
|
+
# @param [Numo::NArray] matrix 2-D square matrix.
|
15
|
+
# @param [Array] variables vector multiplying the matrix
|
16
|
+
#
|
17
|
+
# @return [Array] resultant vector
|
9
18
|
def self.neighbor_average(matrix, variables)
|
10
19
|
matrix = matrix.row_standardized
|
11
20
|
neighbor_sum(matrix, variables)
|
12
21
|
end
|
13
22
|
|
23
|
+
##
|
24
|
+
# Dot product of the input matrix by the input vector, variables.
|
25
|
+
#
|
26
|
+
# @param [Numo::NArray] matrix 2-D square matrix.
|
27
|
+
# @param [Array] variables vector multiplying the matrix
|
28
|
+
#
|
29
|
+
# @return [Array] resultant vector
|
14
30
|
def self.neighbor_sum(matrix, variables)
|
15
31
|
matrix.dot(variables).to_a
|
16
32
|
end
|
17
33
|
|
34
|
+
##
|
35
|
+
# Dot product of the input windowed, row standardizd matrix by
|
36
|
+
# the input vector, variables.
|
37
|
+
#
|
38
|
+
# @param [Numo::NArray] matrix 2-D square matrix.
|
39
|
+
# @param [Array] variables vector multiplying the matrix
|
40
|
+
#
|
41
|
+
# @return [Array] resultant vector
|
18
42
|
def self.window_average(matrix, variables)
|
19
43
|
matrix = matrix.windowed.row_standardized
|
20
44
|
window_sum(matrix, variables)
|
21
45
|
end
|
22
46
|
|
47
|
+
##
|
48
|
+
# Dot product of the input windowed matrix by
|
49
|
+
# the input vector, variables.
|
50
|
+
#
|
51
|
+
# @param [Numo::NArray] matrix 2-D square matrix.
|
52
|
+
# @param [Array] variables vector multiplying the matrix
|
53
|
+
#
|
54
|
+
# @return [Array] resultant vector
|
23
55
|
def self.window_sum(matrix, variables)
|
24
56
|
matrix = matrix.windowed
|
25
57
|
matrix.dot(variables).to_a
|
@@ -3,3 +3,12 @@
|
|
3
3
|
require 'spatial_stats/weights/contiguous'
|
4
4
|
require 'spatial_stats/weights/distant'
|
5
5
|
require 'spatial_stats/weights/weights_matrix'
|
6
|
+
|
7
|
+
module SpatialStats
|
8
|
+
##
|
9
|
+
# The Weights module contains the classes for computing different types of
|
10
|
+
# weight matrices and a weights matrix class to work with the results of
|
11
|
+
# queries.
|
12
|
+
module Weights
|
13
|
+
end
|
14
|
+
end
|
@@ -2,7 +2,18 @@
|
|
2
2
|
|
3
3
|
module SpatialStats
|
4
4
|
module Weights
|
5
|
+
##
|
6
|
+
# Contiguous weights module includes methods that provide an interface to
|
7
|
+
# coniguous weights queries and formats the result properly to utilize
|
8
|
+
# a weights matrix.
|
5
9
|
module Contiguous
|
10
|
+
##
|
11
|
+
# Compute rook weights matrix for a scope.
|
12
|
+
#
|
13
|
+
# @param [ActiveRecord::Relation] scope to query
|
14
|
+
# @param [Symbol, String] field with geometry in it
|
15
|
+
#
|
16
|
+
# @return [WeightsMatrix]
|
6
17
|
def self.rook(scope, field)
|
7
18
|
p_key = scope.primary_key
|
8
19
|
keys = scope.pluck(p_key).sort
|
@@ -21,6 +32,13 @@ module SpatialStats
|
|
21
32
|
SpatialStats::Weights::WeightsMatrix.new(keys, weights)
|
22
33
|
end
|
23
34
|
|
35
|
+
##
|
36
|
+
# Compute queen weights matrix for a scope.
|
37
|
+
#
|
38
|
+
# @param [ActiveRecord::Relation] scope to query
|
39
|
+
# @param [Symbol, String] field with geometry in it
|
40
|
+
#
|
41
|
+
# @return [WeightsMatrix]
|
24
42
|
def self.queen(scope, field)
|
25
43
|
p_key = scope.primary_key
|
26
44
|
keys = scope.pluck(p_key).sort
|
@@ -2,7 +2,18 @@
|
|
2
2
|
|
3
3
|
module SpatialStats
|
4
4
|
module Weights
|
5
|
+
# Distant weights module includes methods that provide an interface to
|
6
|
+
# distance-based weights queries and formats the result properly to utilize
|
7
|
+
# a weights matrix.
|
5
8
|
module Distant
|
9
|
+
##
|
10
|
+
# Compute distance band weights matrix for a scope.
|
11
|
+
#
|
12
|
+
# @param [ActiveRecord::Relation] scope to query
|
13
|
+
# @param [Symbol, String] field with geometry in it
|
14
|
+
# @param [Numeric] bandwidth of distance band
|
15
|
+
#
|
16
|
+
# @return [WeightsMatrix]
|
6
17
|
def self.distance_band(scope, field, bandwidth)
|
7
18
|
p_key = scope.primary_key
|
8
19
|
keys = scope.pluck(p_key).sort
|
@@ -21,12 +32,20 @@ module SpatialStats
|
|
21
32
|
SpatialStats::Weights::WeightsMatrix.new(keys, weights)
|
22
33
|
end
|
23
34
|
|
24
|
-
|
35
|
+
##
|
36
|
+
# Compute distance band weights matrix for a scope.
|
37
|
+
#
|
38
|
+
# @param [ActiveRecord::Relation] scope to query
|
39
|
+
# @param [Symbol, String] field with geometry in it
|
40
|
+
# @param [Integer] k neighbors to find
|
41
|
+
#
|
42
|
+
# @return [WeightsMatrix]
|
43
|
+
def self.knn(scope, field, k)
|
25
44
|
p_key = scope.primary_key
|
26
45
|
keys = scope.pluck(p_key).sort
|
27
46
|
|
28
47
|
neighbors = SpatialStats::Queries::Weights
|
29
|
-
.knn(scope, field,
|
48
|
+
.knn(scope, field, k)
|
30
49
|
|
31
50
|
neighbors = neighbors.group_by(&:i_id)
|
32
51
|
weights = neighbors.transform_values do |value|
|
@@ -39,6 +58,15 @@ module SpatialStats
|
|
39
58
|
SpatialStats::Weights::WeightsMatrix.new(keys, weights)
|
40
59
|
end
|
41
60
|
|
61
|
+
##
|
62
|
+
# Compute idw, distance band weights matrix for a scope.
|
63
|
+
#
|
64
|
+
# @param [ActiveRecord::Relation] scope to query
|
65
|
+
# @param [Symbol, String] field with geometry in it
|
66
|
+
# @param [Numeric] bandwidth of distance band
|
67
|
+
# @param [Numeric] alpha used in weighting calculation (usually 1 or 2)
|
68
|
+
#
|
69
|
+
# @return [WeightsMatrix]
|
42
70
|
def self.idw_band(scope, field, bandwidth, alpha = 1)
|
43
71
|
p_key = scope.primary_key
|
44
72
|
keys = scope.pluck(p_key).sort
|
@@ -56,12 +84,21 @@ module SpatialStats
|
|
56
84
|
SpatialStats::Weights::WeightsMatrix.new(keys, weights)
|
57
85
|
end
|
58
86
|
|
59
|
-
|
87
|
+
##
|
88
|
+
# Compute idw, knn weights matrix for a scope.
|
89
|
+
#
|
90
|
+
# @param [ActiveRecord::Relation] scope to query
|
91
|
+
# @param [Symbol, String] field with geometry in it
|
92
|
+
# @param [Integer] k neighbors to find
|
93
|
+
# @param [Numeric] alpha used in weighting calculation (usually 1 or 2)
|
94
|
+
#
|
95
|
+
# @return [WeightsMatrix]
|
96
|
+
def self.idw_knn(scope, field, k, alpha = 1)
|
60
97
|
p_key = scope.primary_key
|
61
98
|
keys = scope.pluck(p_key).sort
|
62
99
|
|
63
100
|
neighbors = SpatialStats::Queries::Weights
|
64
|
-
.idw_knn(scope, field,
|
101
|
+
.idw_knn(scope, field, k, alpha)
|
65
102
|
neighbors = neighbors.group_by { |pair| pair[:i_id] }
|
66
103
|
|
67
104
|
# only keep j_id and weight
|
@@ -4,7 +4,17 @@ require 'numo/narray'
|
|
4
4
|
|
5
5
|
module SpatialStats
|
6
6
|
module Weights
|
7
|
+
##
|
8
|
+
# WeightsMatrix class is used to store spatial weights and related
|
9
|
+
# information in various formats.
|
7
10
|
class WeightsMatrix
|
11
|
+
##
|
12
|
+
# A new instance of WeightsMatrix
|
13
|
+
#
|
14
|
+
# @param [Array] keys ordered list of keys used in weights
|
15
|
+
# @param [Hash] weights hash of format +{key: [{j_id: neighbor_key, weight: 1}]}+ that describe the relations between neighbors
|
16
|
+
#
|
17
|
+
# @return [WeightsMatrix]
|
8
18
|
def initialize(keys, weights)
|
9
19
|
@keys = keys
|
10
20
|
@weights = weights
|
@@ -12,6 +22,17 @@ module SpatialStats
|
|
12
22
|
end
|
13
23
|
attr_accessor :keys, :weights, :n
|
14
24
|
|
25
|
+
##
|
26
|
+
# Compute the n x n Numo::Narray of the weights hash.
|
27
|
+
#
|
28
|
+
# @example
|
29
|
+
# hash = {1 => [{j_id: 2, weight: 1}], 2 => [{j_id: 1, weight: 1},
|
30
|
+
# {j_id: 3, weight: 1}], 3 => [{j_id: 2, weight: 1}]}
|
31
|
+
# wm = WeightsMatrix.new(hash.keys, hash)
|
32
|
+
# wm.full
|
33
|
+
# # => Numo::DFloat[[0, 1, 0], [1, 0, 1], [0, 1, 0]]
|
34
|
+
#
|
35
|
+
# @return [Numo::DFloat]
|
15
36
|
def full
|
16
37
|
# returns a square matrix Wij using @keys as the order of items
|
17
38
|
@full ||= begin
|
@@ -34,6 +55,10 @@ module SpatialStats
|
|
34
55
|
end
|
35
56
|
end
|
36
57
|
|
58
|
+
##
|
59
|
+
# Row standardized version of +#full+
|
60
|
+
#
|
61
|
+
# @return [Numo::DFloat]
|
37
62
|
def standardized
|
38
63
|
@standardized ||= full.row_standardized
|
39
64
|
end
|