spatial_stats 0.2.2 → 1.0.4

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.
@@ -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 = neighbor.as_json(only: [:j_id]).symbolize_keys
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(keys, weights)
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 = neighbor.as_json(only: [:j_id]).symbolize_keys
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(keys, weights)
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], j_id: neighbor[:j_id] }
95
+ { weight: neighbor[:weight], id: neighbor[:j_id] }
82
96
  end
83
97
  end
84
- SpatialStats::Weights::WeightsMatrix.new(keys, weights)
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], j_id: neighbor[:j_id] }
125
+ { weight: neighbor[:weight], id: neighbor[:j_id] }
108
126
  end
109
127
  end
110
- SpatialStats::Weights::WeightsMatrix.new(keys, weights)
128
+ SpatialStats::Weights::WeightsMatrix.new(weights)
111
129
  end
112
130
  end
113
131
  end
@@ -11,56 +11,113 @@ module SpatialStats
11
11
  ##
12
12
  # A new instance of WeightsMatrix
13
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
14
+ # @param [Hash] weights hash of format +{key: [{id: neighbor_key, weight: 1}]}+ that describe the relations between neighbors
16
15
  #
17
16
  # @return [WeightsMatrix]
18
- def initialize(keys, weights)
19
- @keys = keys
17
+ def initialize(weights)
20
18
  @weights = weights
19
+ @keys = weights.keys
21
20
  @n = keys.size
22
21
  end
23
22
  attr_accessor :keys, :weights, :n
24
23
 
24
+ ##
25
+ # Equality operator
26
+ #
27
+ # @param [WeightsMatrix] other WeightsMatrix
28
+ #
29
+ # @return [TrueClass, FalseClass] equality result
30
+ def ==(other)
31
+ weights == other.weights
32
+ end
33
+
25
34
  ##
26
35
  # Compute the n x n Numo::Narray of the weights hash.
27
36
  #
28
37
  # @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}]}
38
+ # hash = {1 => [{id: 2, weight: 1}], 2 => [{id: 1, weight: 1},
39
+ # {id: 3, weight: 1}], 3 => [{id: 2, weight: 1}]}
31
40
  # wm = WeightsMatrix.new(hash.keys, hash)
32
41
  # wm.full
33
42
  # # => Numo::DFloat[[0, 1, 0], [1, 0, 1], [0, 1, 0]]
34
43
  #
35
44
  # @return [Numo::DFloat]
36
- def full
37
- # returns a square matrix Wij using @keys as the order of items
38
- @full ||= begin
39
- rows = []
40
- @keys.each do |i|
41
- # iterate through each key to get the data for the row
42
- row = @keys.map do |j|
43
- neighbors = @weights[i]
44
- match = neighbors.find { |neighbor| neighbor[:j_id] == j }
45
- if match
46
- match[:weight]
47
- else
48
- 0
49
- end
45
+ def dense
46
+ @dense ||= begin
47
+ mat = Numo::DFloat.zeros(n, n)
48
+ keys.each_with_index do |key, i|
49
+ neighbors = weights[key]
50
+ neighbors.each do |neighbor|
51
+ j = keys.index(neighbor[:id])
52
+ weight = neighbor[:weight]
53
+
54
+ # assign the weight to row and column
55
+ mat[i, j] = weight
50
56
  end
51
- rows << row
52
57
  end
53
58
 
54
- Numo::DFloat.cast(rows)
59
+ mat
55
60
  end
56
61
  end
57
62
 
58
63
  ##
59
- # Row standardized version of +#full+
64
+ # Compute the CSR representation of the weights.
60
65
  #
61
- # @return [Numo::DFloat]
62
- def standardized
63
- @standardized ||= full.row_standardized
66
+ # @return [CSRMatrix]
67
+ def sparse
68
+ @sparse ||= CSRMatrix.new(weights, n)
69
+ end
70
+
71
+ ##
72
+ # Compute the cardinalities of each neighbor into an array
73
+ #
74
+ # @return [Array]
75
+ def wc
76
+ @wc ||= begin
77
+ row_index = sparse.row_index
78
+ (0..n - 1).map do |idx|
79
+ row_index[idx + 1] - row_index[idx]
80
+ end
81
+ end
82
+ end
83
+
84
+ ##
85
+ # Row standardized version of the weights matrix.
86
+ # Will return a new version of the weights matrix with standardized
87
+ # weights.
88
+ #
89
+ # @return [WeightsMatrix]
90
+ def standardize
91
+ new_weights = weights
92
+
93
+ new_weights.transform_values do |neighbors|
94
+ sum = neighbors.reduce(0.0) { |acc, neighbor| acc + neighbor[:weight] }
95
+
96
+ neighbors.map do |neighbor|
97
+ hash = neighbor
98
+ hash[:weight] /= sum
99
+ end
100
+ end
101
+
102
+ self.class.new(new_weights)
103
+ end
104
+
105
+ ##
106
+ # Windowed version of the weights matrix.
107
+ # If a row already has an entry for itself, it will be skipped.
108
+ #
109
+ # @return [WeightsMatrix]
110
+ def window
111
+ new_weights = weights
112
+
113
+ new_weights.each do |key, neighbors|
114
+ unless neighbors.find { |neighbor| neighbor[:id] == key }
115
+ new_neighbors = (neighbors << { id: key, weight: 1 })
116
+ new_weights[key] = new_neighbors.sort_by { |neighbor| neighbor[:id] }
117
+ end
118
+ end
119
+
120
+ self.class.new(new_weights)
64
121
  end
65
122
  end
66
123
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spatial_stats
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 1.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Keith Doggett
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-04-03 00:00:00.000000000 Z
11
+ date: 2020-12-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: numo-narray
@@ -28,14 +28,14 @@ dependencies:
28
28
  name: rails
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "~>"
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
33
  version: 6.0.0
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - "~>"
38
+ - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: 6.0.0
41
41
  - !ruby/object:Gem::Dependency
@@ -80,6 +80,20 @@ dependencies:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
82
  version: '1.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rake-compiler
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 1.1.0
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 1.1.0
83
97
  - !ruby/object:Gem::Dependency
84
98
  name: ruby-prof
85
99
  requirement: !ruby/object:Gem::Requirement
@@ -98,14 +112,14 @@ dependencies:
98
112
  name: tzinfo
99
113
  requirement: !ruby/object:Gem::Requirement
100
114
  requirements:
101
- - - "~>"
115
+ - - ">="
102
116
  - !ruby/object:Gem::Version
103
117
  version: 1.2.6
104
118
  type: :development
105
119
  prerelease: false
106
120
  version_requirements: !ruby/object:Gem::Requirement
107
121
  requirements:
108
- - - "~>"
122
+ - - ">="
109
123
  - !ruby/object:Gem::Version
110
124
  version: 1.2.6
111
125
  description: An ActiveRecord/PostGIS extension that provides statistical methods to
@@ -113,14 +127,18 @@ description: An ActiveRecord/PostGIS extension that provides statistical methods
113
127
  weighting in PostGIS and performs statistical computations inside your rails app.
114
128
  Supports contiguious and distance-based calculations.
115
129
  email:
116
- - keith.doggett887@gmail.com
130
+ - kfdoggett@gmail.com
117
131
  executables: []
118
- extensions: []
132
+ extensions:
133
+ - ext/spatial_stats/extconf.rb
119
134
  extra_rdoc_files: []
120
135
  files:
121
- - MIT-LICENSE
122
136
  - README.md
123
137
  - Rakefile
138
+ - ext/spatial_stats/csr_matrix.c
139
+ - ext/spatial_stats/csr_matrix.h
140
+ - ext/spatial_stats/extconf.rb
141
+ - ext/spatial_stats/spatial_stats.c
124
142
  - lib/spatial_stats.rb
125
143
  - lib/spatial_stats/enumerable_ext.rb
126
144
  - lib/spatial_stats/global.rb
@@ -139,6 +157,7 @@ files:
139
157
  - lib/spatial_stats/queries/variables.rb
140
158
  - lib/spatial_stats/queries/weights.rb
141
159
  - lib/spatial_stats/railtie.rb
160
+ - lib/spatial_stats/spatial_stats.so
142
161
  - lib/spatial_stats/utils.rb
143
162
  - lib/spatial_stats/utils/lag.rb
144
163
  - lib/spatial_stats/version.rb
@@ -149,9 +168,12 @@ files:
149
168
  - lib/tasks/spatial_stats_tasks.rake
150
169
  homepage: https://www.github.com/keithdoggett/spatial_stats
151
170
  licenses:
152
- - MIT
171
+ - BSD-3-Clause
153
172
  metadata:
173
+ homepage_uri: https://www.github.com/keithdoggett/spatial_stats
174
+ source_code_uri: https://www.github.com/keithdoggett/spatial_stats
154
175
  documentation_uri: https://keithdoggett.github.io/spatial_stats/
176
+ changelog_uri: https://www.github.com/keithdoggett/spatial_stats/CHANGELOG.md
155
177
  post_install_message:
156
178
  rdoc_options: []
157
179
  require_paths:
@@ -167,7 +189,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
167
189
  - !ruby/object:Gem::Version
168
190
  version: '0'
169
191
  requirements: []
170
- rubygems_version: 3.0.3
192
+ rubygems_version: 3.0.8
171
193
  signing_key:
172
194
  specification_version: 4
173
195
  summary: An ActiveRecord/PostGIS extension that provides statistical methods to spatial
@@ -1,20 +0,0 @@
1
- Copyright 2020 Keith Doggett
2
-
3
- Permission is hereby granted, free of charge, to any person obtaining
4
- a copy of this software and associated documentation files (the
5
- "Software"), to deal in the Software without restriction, including
6
- without limitation the rights to use, copy, modify, merge, publish,
7
- distribute, sublicense, and/or sell copies of the Software, and to
8
- permit persons to whom the Software is furnished to do so, subject to
9
- the following conditions:
10
-
11
- The above copyright notice and this permission notice shall be
12
- included in all copies or substantial portions of the Software.
13
-
14
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
- LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
- OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.