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