Module: SpatialStats::Queries::Weights

Defined in:
lib/spatial_stats/queries/weights.rb

Overview

Weights includes methods for querying a scope using PostGIS sql methods to determine neighbors and weights based on different weighting schemes/formulas.

Class Method Summary collapse

Class Method Details

._contiguity_neighbors(scope, column, pattern) ⇒ Hash

Generic function to compute contiguity neighbor weights for a given scope. Takes any valid DE-9IM pattern and computes the neighbors based off of that.

Parameters:

  • scope (ActiveRecord::Relation)

    you want to query

  • column (Symbol, String)

    that contains the geometry

  • pattern (String)

    to describe neighbor relation

Returns:

  • (Hash)

See Also:



203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
# File 'lib/spatial_stats/queries/weights.rb', line 203

def self._contiguity_neighbors(scope, column, pattern)
  klass = scope.klass
  column = ActiveRecord::Base.connection.quote_column_name(column)
  primary_key = klass.quoted_primary_key
  klass.find_by_sql([<<-SQL, scope: scope])
    WITH neighbors AS (
      WITH scope AS (:scope)
      SELECT a.#{primary_key} as i_id, b.#{primary_key} as j_id,
      ST_RELATE(a.#{column}, b.#{column}, \'#{pattern}\') as is_neighbor
      FROM scope as a, scope as b
      ORDER BY i_id
    )
    SELECT * FROM neighbors WHERE is_neighbor = 't'
  SQL
end

.distance_band_neighbors(scope, column, bandwidth) ⇒ Hash

Compute distance band weights for a given scope. Identifies neighbors as other observations in scope that are within the distance band from the observation.

Parameters:

  • scope (ActiveRecord::Relation)

    you want to query

  • column (Symbol, String)

    that contains the geometry

  • bandwidth (Numeric)

    to find neighbors in

Returns:

  • (Hash)


146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
# File 'lib/spatial_stats/queries/weights.rb', line 146

def self.distance_band_neighbors(scope, column, bandwidth)
  klass = scope.klass
  column = ActiveRecord::Base.connection.quote_column_name(column)
  primary_key = klass.quoted_primary_key
  klass.find_by_sql([<<-SQL, scope: scope, distance: bandwidth])
    WITH neighbors AS (
      WITH scope AS (:scope)
      SELECT a.#{primary_key} as i_id, b.#{primary_key} as j_id,
      ST_DWithin(a.#{column}, b.#{column}, :distance) as is_neighbor
      FROM scope as a, scope as b
      ORDER BY i_id
    )
    SELECT * FROM neighbors WHERE is_neighbor = 't' AND i_id <> j_id
  SQL
end

.idw_band(scope, column, bandwidth, alpha = 1) ⇒ Hash

Compute inverse distance weighted, band limited weights for a given scope and geometry.

Combines distance_band and idw weightings. Each observation will have neighbers in the bandwidth, but the weights will be calculated by 1/(d**alpha).

Only works for geometry types that implement ST_Distance and ST_DWithin.

Parameters:

  • scope (ActiveRecord::Relation)

    you want to query

  • column (Symbol, String)

    that contains the geometry

  • bandwidth (Numeric)

    to find neighbors in

  • alpha (Integer) (defaults to: 1)

    number used in inverse calculations (usually 1 or 2)

Returns:

  • (Hash)


76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
# File 'lib/spatial_stats/queries/weights.rb', line 76

def self.idw_band(scope, column, bandwidth, alpha = 1)
  klass = scope.klass
  column = ActiveRecord::Base.connection.quote_column_name(column)
  primary_key = klass.quoted_primary_key
  neighbors = klass.find_by_sql([<<-SQL, scope: scope, bandwidth: bandwidth])
    WITH neighbors AS (
      WITH scope AS (:scope)
      SELECT a.#{primary_key} as i_id, b.#{primary_key} as j_id,
      ST_DWithin(a.#{column}, b.#{column}, :bandwidth) as is_neighbor,
      ST_Distance(a.#{column}, b.#{column}) as distance
      FROM scope as a, scope as b
      ORDER BY i_id
    )
    SELECT * FROM neighbors WHERE is_neighbor = 't' AND i_id <> j_id
  SQL

  # if the lowest distance is <1, then we need to scale
  # every distance by the factor that makes the lowest 1
  min_dist = neighbors.map(&:distance).min
  scale = if min_dist < 1
            1 / min_dist
          else
            1
          end

  neighbors.map do |neighbor|
    # formula is 1/(d^alpha)
    weight = 1.0 / ((scale * neighbor.distance)**alpha)
    hash = neighbor.as_json.symbolize_keys
    hash[:weight] = weight
    hash
  end
end

.idw_knn(scope, column, k, alpha = 1) ⇒ Hash

Compute inverse distance weighted, k nearest neighbors weights for a given scope and geometry.

Combines knn and idw weightings. Each observation will have k neighbors, but the weights will be calculated by 1/(d**alpha).

Only works for geometry types that implement ST_Distance.

Parameters:

  • scope (ActiveRecord::Relation)

    you want to query

  • column (Symbol, String)

    that contains the geometry

  • k (Integer)

    neighbors to find

  • alpha (Integer) (defaults to: 1)

    number used in inverse calculations (usually 1 or 2)

Returns:

  • (Hash)


25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/spatial_stats/queries/weights.rb', line 25

def self.idw_knn(scope, column, k, alpha = 1)
  klass = scope.klass
  column = ActiveRecord::Base.connection.quote_column_name(column)
  primary_key = klass.quoted_primary_key
  neighbors = klass.find_by_sql([<<-SQL, scope: scope, k: k])
    WITH scope as (:scope)
    SELECT neighbors.*
    FROM scope AS a
      CROSS JOIN LATERAL (
      SELECT a.#{primary_key} as i_id, b.#{primary_key} as j_id,
      ST_Distance(a.#{column}, b.#{column}) as distance
      FROM scope as b
      WHERE a.#{primary_key} <> b.#{primary_key}
      ORDER BY a.#{column} <-> b.#{column}
      LIMIT :k
    ) AS neighbors
  SQL

  # if the lowest distance is <1, then we need to scale
  # every distance by the factor that makes the lowest 1
  min_dist = neighbors.map(&:distance).min
  scale = if min_dist < 1
            1 / min_dist
          else
            1
          end

  neighbors.map do |neighbor|
    # formula is 1/(d^alpha)
    weight = 1.0 / ((scale * neighbor.distance)**alpha)
    hash = neighbor.as_json.symbolize_keys
    hash[:weight] = weight
    hash
  end
end

.knn(scope, column, k) ⇒ Hash

Compute k nearest neighbor weights for a given scope.

Parameters:

  • scope (ActiveRecord::Relation)

    you want to query

  • column (Symbol, String)

    that contains the geometry

  • k (Integer)

    neighbors to find

Returns:

  • (Hash)


118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# File 'lib/spatial_stats/queries/weights.rb', line 118

def self.knn(scope, column, k)
  klass = scope.klass
  column = ActiveRecord::Base.connection.quote_column_name(column)
  primary_key = klass.quoted_primary_key
  klass.find_by_sql([<<-SQL, scope: scope, k: k])
    WITH scope as (:scope)
    SELECT neighbors.*
    FROM scope AS a
      CROSS JOIN LATERAL (
      SELECT a.#{primary_key} as i_id, b.#{primary_key} as j_id
      FROM scope as b
      WHERE a.#{primary_key} <> b.#{primary_key}
      ORDER BY a.#{column} <-> b.#{column}
      LIMIT :k
    ) AS neighbors
  SQL
end

.queen_contiguity_neighbors(scope, column) ⇒ Hash

Compute queen contiguity weights for a given scope. Queen contiguity weights are defined by geometries sharing an edge or vertex.

DE-9IM pattern = F**T***

Parameters:

  • scope (ActiveRecord::Relation)

    you want to query

  • column (Symbol, String)

    that contains the geometry

Returns:

  • (Hash)


173
174
175
# File 'lib/spatial_stats/queries/weights.rb', line 173

def self.queen_contiguity_neighbors(scope, column)
  _contiguity_neighbors(scope, column, 'F***T****')
end

.rook_contiguity_neighbors(scope, column) ⇒ Hash

Compute rook contiguity weights for a given scope. Rook contiguity weights are defined by geometries sharing an edge.

DE-9IM pattern = 'F**1***'

Parameters:

  • scope (ActiveRecord::Relation)

    you want to query

  • column (Symbol, String)

    that contains the geometry

Returns:

  • (Hash)


187
188
189
# File 'lib/spatial_stats/queries/weights.rb', line 187

def self.rook_contiguity_neighbors(scope, column)
  _contiguity_neighbors(scope, column, 'F***1****')
end