spatial_stats 0.2.2 → 1.0.4

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