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.
@@ -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,3 +2,9 @@
2
2
 
3
3
  require 'spatial_stats/queries/variables'
4
4
  require 'spatial_stats/queries/weights'
5
+
6
+ module SpatialStats
7
+ # The Queries module contains the ActiveRecord/PostGIS interface for the gem.
8
+ module Queries
9
+ end
10
+ end
@@ -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
- # Module to query for the desired variable from the given scope
7
- # and include the primary keys so that the weights matrix
8
- # will know that its keys will match up with the variables.
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
- # This provides PostGIS queries for calculating weights/neighbors
6
- # of spatial data sets
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
- def self.idw_knn(scope, column, n, alpha)
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, n: n])
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 :n
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
- def self.knn(scope, column, n)
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, n: n])
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 :n
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
- # DE-9IM queen contiguiety = F***T****
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)
@@ -1,3 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'spatial_stats/utils/lag'
4
+
5
+ module SpatialStats
6
+ ##
7
+ # The Utils module contains various utilities used in the gem.
8
+ module Utils
9
+ end
10
+ end
@@ -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
- # module for computing spatially lagged variables
8
- # from a weights matrix and variable array
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SpatialStats
4
- VERSION = '0.1.1'
4
+ VERSION = '0.2.1'
5
5
  end
@@ -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
- def self.knn(scope, field, n)
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, n)
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
- def self.idw_knn(scope, field, n, alpha = 1)
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, n, alpha)
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