spatial_stats 0.2.2 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +83 -11
- data/Rakefile +7 -0
- data/ext/spatial_stats/csr_matrix.c +365 -0
- data/ext/spatial_stats/csr_matrix.h +35 -0
- data/ext/spatial_stats/extconf.rb +6 -0
- data/ext/spatial_stats/spatial_stats.c +32 -0
- data/lib/spatial_stats/global/bivariate_moran.rb +43 -21
- data/lib/spatial_stats/global/moran.rb +43 -35
- data/lib/spatial_stats/global/stat.rb +31 -27
- data/lib/spatial_stats/local/bivariate_moran.rb +67 -2
- data/lib/spatial_stats/local/geary.rb +36 -5
- data/lib/spatial_stats/local/getis_ord.rb +45 -17
- data/lib/spatial_stats/local/moran.rb +40 -9
- data/lib/spatial_stats/local/multivariate_geary.rb +29 -10
- data/lib/spatial_stats/local/stat.rb +72 -36
- data/lib/spatial_stats/narray_ext.rb +5 -5
- data/lib/spatial_stats/spatial_stats.so +0 -0
- data/lib/spatial_stats/utils/lag.rb +10 -10
- data/lib/spatial_stats/version.rb +1 -1
- data/lib/spatial_stats/weights/contiguous.rb +20 -10
- data/lib/spatial_stats/weights/distant.rb +38 -20
- data/lib/spatial_stats/weights/weights_matrix.rb +60 -26
- data/lib/spatial_stats.rb +1 -0
- metadata +25 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '08787d83d402843b9d711fd88cdc5f22cbdacfb7a960d8bdb185591f10215f85'
|
4
|
+
data.tar.gz: 96241b1ef7099ce6371f24dee56258121e0d6686ec746ac0411c4ed8ccaf5cad
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4ec63da930afeae2b68e3f821e82c8c18f18b11ffa3296ad34e56706b54db11170e5319133d84e9440360e984776cdc5adbd6193d7de252f6f618136a431696b
|
7
|
+
data.tar.gz: 13964eb6a903e5ba0881b0ea2dfeab5e10fcfe51722d9e8a3342f2084200a60bc046d7375e29f651a5267d04a7b9b3a571f26b1ecf13ebf1c3fa2a3211ac6d8c
|
data/README.md
CHANGED
@@ -64,14 +64,14 @@ Example: Define WeightsMatrix and get the matrix in row_standardized format.
|
|
64
64
|
|
65
65
|
```ruby
|
66
66
|
weights = {
|
67
|
-
1 => [{
|
68
|
-
2 => [{
|
69
|
-
3 => [{
|
70
|
-
4 => [{
|
67
|
+
1 => [{ id: 2, weight: 1 }, { id: 4, weight: 1 }],
|
68
|
+
2 => [{ id: 1, weight: 1 }],
|
69
|
+
3 => [{ id: 4, weight: 1 }],
|
70
|
+
4 => [{ id: 1, weight: 1 }, { id: 3, weight: 1 }]
|
71
71
|
}
|
72
72
|
keys = weights.keys
|
73
73
|
wm = SpatialStats::Weights::WeightsMatrix.new(keys, weights)
|
74
|
-
# => #<SpatialStats::Weights::WeightsMatrix:0x0000561e205677c0 @keys=[1, 2, 3, 4], @weights={1=>[{:
|
74
|
+
# => #<SpatialStats::Weights::WeightsMatrix:0x0000561e205677c0 @keys=[1, 2, 3, 4], @weights={1=>[{:id=>2, :weight=>1}, {:id=>4, :weight=>1}], 2=>[{:id=>1, :weight=>1}], 3=>[{:id=>4, :weight=>1}], 4=>[{:id=>1, :weight=>1}, {:id=>3, :weight=>1}]}, @n=4>
|
75
75
|
|
76
76
|
wm.standardized
|
77
77
|
# => Numo::DFloat#shape=[4,4]
|
@@ -118,6 +118,26 @@ moran.i
|
|
118
118
|
# => 0.834
|
119
119
|
```
|
120
120
|
|
121
|
+
#### Compute Moran's I without Querying Data
|
122
|
+
|
123
|
+
To calculate the statistic by using an array of data and not querying a database field. The order of the data must correspond to the order of `weights.keys`.
|
124
|
+
|
125
|
+
```ruby
|
126
|
+
scope = County.all
|
127
|
+
weights = SpatialStats::Weights::Contiguous.rook(scope, :geom)
|
128
|
+
|
129
|
+
field = nil
|
130
|
+
moran = SpatialStats::Global::Moran.new(scope, field, weights)
|
131
|
+
# => <SpatialStats::Global::Moran>
|
132
|
+
|
133
|
+
# important to standardize the data!
|
134
|
+
data = [1,2,3,4,5,6].standardize
|
135
|
+
moran.x = data
|
136
|
+
|
137
|
+
moran.stat
|
138
|
+
# => 0.521
|
139
|
+
```
|
140
|
+
|
121
141
|
#### Compute Moran's I Z-Score
|
122
142
|
|
123
143
|
```ruby
|
@@ -144,6 +164,20 @@ moran.mc(999, 123_456)
|
|
144
164
|
# => 0.003
|
145
165
|
```
|
146
166
|
|
167
|
+
#### Get Summary of Permutation Test
|
168
|
+
|
169
|
+
All stat classes have the `summary` method which takes `permutations` and `seed` as its parameters. `summary` runs `stat` and `mc` then combines the results into a hash.
|
170
|
+
|
171
|
+
```ruby
|
172
|
+
scope = County.all
|
173
|
+
weights = SpatialStats::Weights::Contiguous.rook(scope, :geom)
|
174
|
+
moran = SpatialStats::Global::Moran.new(scope, :avg_income, weights)
|
175
|
+
# => <SpatialStats::Global::Moran>
|
176
|
+
|
177
|
+
moran.summary(999, 123_456)
|
178
|
+
# => {stat: 0.834, p: 0.003}
|
179
|
+
```
|
180
|
+
|
147
181
|
### Local Stats
|
148
182
|
|
149
183
|
Local stats compute a value each observation in the dataset, like how similar its neighbors are to itself. Local stats operate similarly to global stats, except that almost every operation will return an array of length `n` where `n` is the number of observations in the dataset.
|
@@ -165,6 +199,26 @@ moran.i
|
|
165
199
|
# => [0.888, 0.675, 0.2345, -0.987, -0.42, ...]
|
166
200
|
```
|
167
201
|
|
202
|
+
#### Compute Moran's I without Querying Data
|
203
|
+
|
204
|
+
To calculate the statistic by using an array of data and not querying a database field. The order of the data must correspond to the order of `weights.keys`.
|
205
|
+
|
206
|
+
```ruby
|
207
|
+
scope = County.all
|
208
|
+
weights = SpatialStats::Weights::Contiguous.rook(scope, :geom)
|
209
|
+
|
210
|
+
field = nil
|
211
|
+
moran = SpatialStats::Local::Moran.new(scope, field, weights)
|
212
|
+
# => <SpatialStats::Local::Moran>
|
213
|
+
|
214
|
+
# important to standardize the data!
|
215
|
+
data = [1,2,3,4,5,6].standardize
|
216
|
+
moran.x = data
|
217
|
+
|
218
|
+
moran.stat
|
219
|
+
# => [0.521, 0.123, -0.432, -0.56,. ...]
|
220
|
+
```
|
221
|
+
|
168
222
|
#### Compute Moran's I Z-Scores
|
169
223
|
|
170
224
|
Note: Many classes do not have a variance or expectation method implemented and this will raise a `NotImplementedError`.
|
@@ -193,6 +247,20 @@ moran.mc(999, 123_456)
|
|
193
247
|
# => [0.24, 0.13, 0.53, 0.023, 0.65, ...]
|
194
248
|
```
|
195
249
|
|
250
|
+
#### Get Summary of Permutation Test
|
251
|
+
|
252
|
+
All stat classes have the `summary` method which takes `permutations` and `seed` as its parameters. `summary` runs `stat`, `mc`, and `groups` then combines the results into a hash array indexed by `weight.keys`.
|
253
|
+
|
254
|
+
```ruby
|
255
|
+
scope = County.all
|
256
|
+
weights = SpatialStats::Weights::Contiguous.rook(scope, :geom)
|
257
|
+
moran = SpatialStats::Local::Moran.new(scope, :avg_income, weights)
|
258
|
+
# => <SpatialStats::Local::Moran>
|
259
|
+
|
260
|
+
moran.summary(999, 123_456)
|
261
|
+
# => [{key: 1, stat: 0.521, p: 0.24, group: 'HH'}, ...]
|
262
|
+
```
|
263
|
+
|
196
264
|
## Contributing
|
197
265
|
|
198
266
|
Once cloned, run the following commands to setup the test database.
|
@@ -240,16 +308,20 @@ RGeo::Geos.supported?
|
|
240
308
|
- ~~Make star a parameter to getis-ord class~~
|
241
309
|
- ~~Add examples/usage to docs~~
|
242
310
|
- ~~Create RDocs~~
|
243
|
-
- Refactor Global Moran and BVMoran
|
244
|
-
- Support non-numeric keys in WeightsMatrix/General refactor
|
245
|
-
- Write SparseMatrix C ext
|
311
|
+
- ~~Refactor Global Moran and BVMoran~~
|
312
|
+
- ~~Support non-numeric keys in WeightsMatrix/General refactor~~
|
313
|
+
- ~~Write SparseMatrix C ext~~
|
314
|
+
- ~~Change instances of `standardized` and `windowed` to `standardize` and `window`, respectively.~~
|
315
|
+
- ~~Add `positive` and `negative` groups for `GetisOrd` and `Geary`, similar to how `#quads` is implemented.~~
|
316
|
+
- ~~Add `#summary` method to statistics that will combine stat vals with p-vals, and quads or hot/cold spot info.~~
|
317
|
+
- ~~Add ability to assign `x` or `z` on stat classes so users are not forced to query data to input it into models. Add example to README.~~
|
246
318
|
|
247
319
|
## Future Work
|
248
320
|
|
249
321
|
#### General
|
250
322
|
|
251
323
|
- ~~Refactor stats to inherit an abstract class.~~
|
252
|
-
- Change WeightsMatrix class and Stat classes to utilize sparse matrix methods
|
324
|
+
- ~~Change WeightsMatrix class and Stat classes to utilize sparse matrix methods.~~
|
253
325
|
- Split into two separate gems spatial_stats and spatial_stats-activerecord
|
254
326
|
|
255
327
|
#### Weights
|
@@ -261,7 +333,7 @@ RGeo::Geos.supported?
|
|
261
333
|
- Rate smoothing
|
262
334
|
- Bayes smoothing
|
263
335
|
|
264
|
-
|
336
|
+
#### Global
|
265
337
|
|
266
338
|
- Geary class
|
267
339
|
- GetisOrd class
|
@@ -270,7 +342,7 @@ RGeo::Geos.supported?
|
|
270
342
|
|
271
343
|
- Join Count Statistic
|
272
344
|
|
273
|
-
|
345
|
+
#### PPA
|
274
346
|
|
275
347
|
- Add descriptive stat methods for point clusters.
|
276
348
|
|
data/Rakefile
CHANGED
@@ -18,6 +18,12 @@ end
|
|
18
18
|
|
19
19
|
require 'bundler/gem_tasks'
|
20
20
|
|
21
|
+
require 'rake/extensiontask'
|
22
|
+
|
23
|
+
Rake::ExtensionTask.new 'spatial_stats' do |ext|
|
24
|
+
ext.lib_dir = 'lib/spatial_stats'
|
25
|
+
end
|
26
|
+
|
21
27
|
require 'rake/testtask'
|
22
28
|
|
23
29
|
Rake::TestTask.new(:test) do |t|
|
@@ -27,4 +33,5 @@ Rake::TestTask.new(:test) do |t|
|
|
27
33
|
t.warning = false # shut up annoying warnings
|
28
34
|
end
|
29
35
|
|
36
|
+
task test: :compile
|
30
37
|
task default: :test
|
@@ -0,0 +1,365 @@
|
|
1
|
+
#include <ruby.h>
|
2
|
+
#include <stdlib.h>
|
3
|
+
#include <stdio.h>
|
4
|
+
#include "csr_matrix.h"
|
5
|
+
|
6
|
+
void csr_matrix_free(void *mat)
|
7
|
+
{
|
8
|
+
csr_matrix *csr = (csr_matrix *)mat;
|
9
|
+
|
10
|
+
if (csr->init == 1)
|
11
|
+
{
|
12
|
+
free(csr->values);
|
13
|
+
free(csr->col_index);
|
14
|
+
free(csr->row_index);
|
15
|
+
}
|
16
|
+
free(mat);
|
17
|
+
}
|
18
|
+
|
19
|
+
size_t csr_matrix_memsize(const void *ptr)
|
20
|
+
{
|
21
|
+
const csr_matrix *csr = (const csr_matrix *)ptr;
|
22
|
+
return sizeof(*csr);
|
23
|
+
}
|
24
|
+
|
25
|
+
VALUE csr_matrix_alloc(VALUE self)
|
26
|
+
{
|
27
|
+
csr_matrix *csr = malloc(sizeof(csr_matrix));
|
28
|
+
return TypedData_Wrap_Struct(self, &csr_matrix_type, csr);
|
29
|
+
}
|
30
|
+
|
31
|
+
void mat_to_sparse(csr_matrix *csr, VALUE data, VALUE num_rows, VALUE num_cols)
|
32
|
+
{
|
33
|
+
int nnz = 0;
|
34
|
+
int m = NUM2INT(num_rows);
|
35
|
+
int n = NUM2INT(num_cols);
|
36
|
+
|
37
|
+
double *values;
|
38
|
+
int *col_index;
|
39
|
+
int *row_index;
|
40
|
+
|
41
|
+
int nz_idx;
|
42
|
+
double entry;
|
43
|
+
|
44
|
+
int i;
|
45
|
+
int j;
|
46
|
+
int index;
|
47
|
+
|
48
|
+
// first get number non zero count so we can alloc values and col_index
|
49
|
+
for (i = 0; i < m; i++)
|
50
|
+
{
|
51
|
+
for (j = 0; j < n; j++)
|
52
|
+
{
|
53
|
+
index = i * n + j;
|
54
|
+
if (NUM2DBL(rb_ary_entry(data, index)) != 0)
|
55
|
+
{
|
56
|
+
nnz++;
|
57
|
+
}
|
58
|
+
}
|
59
|
+
}
|
60
|
+
|
61
|
+
values = malloc(sizeof(double) * nnz);
|
62
|
+
col_index = malloc(sizeof(int) * nnz);
|
63
|
+
row_index = malloc(sizeof(int) * (m + 1));
|
64
|
+
|
65
|
+
// for every non-zero, record value, column and then get values per row
|
66
|
+
nz_idx = 0;
|
67
|
+
for (i = 0; i < m; i++)
|
68
|
+
{
|
69
|
+
row_index[i] = nz_idx;
|
70
|
+
for (j = 0; j < n; j++)
|
71
|
+
{
|
72
|
+
index = i * n + j;
|
73
|
+
entry = NUM2DBL(rb_ary_entry(data, index));
|
74
|
+
if (entry != 0)
|
75
|
+
{
|
76
|
+
values[nz_idx] = entry;
|
77
|
+
col_index[nz_idx] = j;
|
78
|
+
nz_idx++;
|
79
|
+
}
|
80
|
+
}
|
81
|
+
}
|
82
|
+
row_index[m] = nnz;
|
83
|
+
|
84
|
+
csr->m = m;
|
85
|
+
csr->n = n;
|
86
|
+
csr->nnz = nnz;
|
87
|
+
csr->values = values;
|
88
|
+
csr->col_index = col_index;
|
89
|
+
csr->row_index = row_index;
|
90
|
+
csr->init = 1;
|
91
|
+
}
|
92
|
+
|
93
|
+
/**
|
94
|
+
* A new instance of CSRMatrix.
|
95
|
+
* Uses a 1-D representation of a 2-D array as input.
|
96
|
+
* @example
|
97
|
+
* dummy_data = [
|
98
|
+
* [0, 1, 2]
|
99
|
+
* [3, 4, 5],
|
100
|
+
* [6, 7, 8]
|
101
|
+
* ]
|
102
|
+
* num_rows = 3
|
103
|
+
* num_cols = 3
|
104
|
+
* data = dummy_data.flatten
|
105
|
+
* # => [0, 1, 2, 3, 4, 5, 6, 7, 8]
|
106
|
+
*
|
107
|
+
* csr = CSRMatrix.new(data, num_rows, num_cols)
|
108
|
+
*
|
109
|
+
* @param [Array] data in 1-D format
|
110
|
+
* @param [Integer] num_rows in the 2-D representation
|
111
|
+
* @param [Integer] num_cols in the 2-D representation
|
112
|
+
*
|
113
|
+
* @return [CSRMatrix]
|
114
|
+
*/
|
115
|
+
VALUE csr_matrix_initialize(VALUE self, VALUE data, VALUE num_rows, VALUE num_cols)
|
116
|
+
{
|
117
|
+
|
118
|
+
csr_matrix *csr;
|
119
|
+
TypedData_Get_Struct(self, csr_matrix, &csr_matrix_type, csr);
|
120
|
+
csr->init = 0;
|
121
|
+
|
122
|
+
Check_Type(data, T_ARRAY);
|
123
|
+
Check_Type(num_rows, T_FIXNUM);
|
124
|
+
Check_Type(num_cols, T_FIXNUM);
|
125
|
+
|
126
|
+
// check dimensions are correct
|
127
|
+
if (NUM2INT(num_rows) * NUM2INT(num_cols) != rb_array_len(data))
|
128
|
+
{
|
129
|
+
rb_raise(rb_eArgError, "n_rows * n_cols != data.size, check your dimensions");
|
130
|
+
}
|
131
|
+
|
132
|
+
mat_to_sparse(csr, data, num_rows, num_cols);
|
133
|
+
|
134
|
+
rb_iv_set(self, "@m", num_rows);
|
135
|
+
rb_iv_set(self, "@n", num_cols);
|
136
|
+
rb_iv_set(self, "@nnz", INT2NUM(csr->nnz));
|
137
|
+
|
138
|
+
return self;
|
139
|
+
}
|
140
|
+
|
141
|
+
/**
|
142
|
+
* Non-zero values in the matrix.
|
143
|
+
*
|
144
|
+
* @return [Array] of the non-zero values.
|
145
|
+
*/
|
146
|
+
VALUE csr_matrix_values(VALUE self)
|
147
|
+
{
|
148
|
+
csr_matrix *csr;
|
149
|
+
VALUE result;
|
150
|
+
|
151
|
+
int i;
|
152
|
+
|
153
|
+
TypedData_Get_Struct(self, csr_matrix, &csr_matrix_type, csr);
|
154
|
+
|
155
|
+
result = rb_ary_new_capa(csr->nnz);
|
156
|
+
for (i = 0; i < csr->nnz; i++)
|
157
|
+
{
|
158
|
+
rb_ary_store(result, i, DBL2NUM(csr->values[i]));
|
159
|
+
}
|
160
|
+
|
161
|
+
return result;
|
162
|
+
}
|
163
|
+
|
164
|
+
/**
|
165
|
+
* Column indices of the non-zero values.
|
166
|
+
*
|
167
|
+
* @return [Array] of the column indices.
|
168
|
+
*/
|
169
|
+
VALUE csr_matrix_col_index(VALUE self)
|
170
|
+
{
|
171
|
+
csr_matrix *csr;
|
172
|
+
VALUE result;
|
173
|
+
|
174
|
+
int i;
|
175
|
+
|
176
|
+
TypedData_Get_Struct(self, csr_matrix, &csr_matrix_type, csr);
|
177
|
+
|
178
|
+
result = rb_ary_new_capa(csr->nnz);
|
179
|
+
for (i = 0; i < csr->nnz; i++)
|
180
|
+
{
|
181
|
+
rb_ary_store(result, i, INT2NUM(csr->col_index[i]));
|
182
|
+
}
|
183
|
+
|
184
|
+
return result;
|
185
|
+
}
|
186
|
+
|
187
|
+
/**
|
188
|
+
* Row indices of the non-zero values. Represents the start index
|
189
|
+
* of values in a row. For example [0,2,3] would represent a matrix
|
190
|
+
* with 2 rows, the first containing 2 non-zero values and the second
|
191
|
+
* containing 1. Length is num_rows + 1.
|
192
|
+
*
|
193
|
+
* Used for row slicing operations.
|
194
|
+
*
|
195
|
+
* @return [Array] of the row indices.
|
196
|
+
*/
|
197
|
+
VALUE csr_matrix_row_index(VALUE self)
|
198
|
+
{
|
199
|
+
csr_matrix *csr;
|
200
|
+
VALUE result;
|
201
|
+
|
202
|
+
int i;
|
203
|
+
|
204
|
+
TypedData_Get_Struct(self, csr_matrix, &csr_matrix_type, csr);
|
205
|
+
|
206
|
+
result = rb_ary_new_capa(csr->m + 1);
|
207
|
+
for (i = 0; i <= csr->m; i++)
|
208
|
+
{
|
209
|
+
rb_ary_store(result, i, INT2NUM(csr->row_index[i]));
|
210
|
+
}
|
211
|
+
|
212
|
+
return result;
|
213
|
+
}
|
214
|
+
|
215
|
+
/**
|
216
|
+
* Multiply matrix by the input vector.
|
217
|
+
*
|
218
|
+
* @see https://github.com/scipy/scipy/blob/53fac7a1d8a81d48be757632ad285b6fc76529ba/scipy/sparse/sparsetools/csr.h#L1120
|
219
|
+
*
|
220
|
+
* @param [Array] vec of length n.
|
221
|
+
*
|
222
|
+
* @return [Array] of the result of the multiplication.
|
223
|
+
*/
|
224
|
+
VALUE csr_matrix_mulvec(VALUE self, VALUE vec)
|
225
|
+
{
|
226
|
+
csr_matrix *csr;
|
227
|
+
VALUE result;
|
228
|
+
|
229
|
+
int i;
|
230
|
+
int jj;
|
231
|
+
double tmp;
|
232
|
+
|
233
|
+
Check_Type(vec, T_ARRAY);
|
234
|
+
|
235
|
+
TypedData_Get_Struct(self, csr_matrix, &csr_matrix_type, csr);
|
236
|
+
|
237
|
+
if (rb_array_len(vec) != csr->n)
|
238
|
+
{
|
239
|
+
rb_raise(rb_eArgError, "Dimension Mismatch CSRMatrix.n != vec.size");
|
240
|
+
}
|
241
|
+
|
242
|
+
result = rb_ary_new_capa(csr->m);
|
243
|
+
|
244
|
+
// float *vals = (float *)DATA_PTR(result);
|
245
|
+
|
246
|
+
for (i = 0; i < csr->m; i++)
|
247
|
+
{
|
248
|
+
tmp = 0;
|
249
|
+
for (jj = csr->row_index[i]; jj < csr->row_index[i + 1]; jj++)
|
250
|
+
{
|
251
|
+
tmp += csr->values[jj] * NUM2DBL(rb_ary_entry(vec, csr->col_index[jj]));
|
252
|
+
}
|
253
|
+
rb_ary_store(result, i, DBL2NUM(tmp));
|
254
|
+
}
|
255
|
+
|
256
|
+
return result;
|
257
|
+
}
|
258
|
+
|
259
|
+
/**
|
260
|
+
* Compute the dot product of the given row with the input vector.
|
261
|
+
* Equivalent to +mulvec(vec)[row]+.
|
262
|
+
*
|
263
|
+
* @param [Array] vec of length n.
|
264
|
+
* @param [Integer] row of the dot product.
|
265
|
+
*
|
266
|
+
* @return [Float] of the result of the dot product.
|
267
|
+
*/
|
268
|
+
VALUE csr_matrix_dot_row(VALUE self, VALUE vec, VALUE row)
|
269
|
+
{
|
270
|
+
csr_matrix *csr;
|
271
|
+
VALUE result;
|
272
|
+
|
273
|
+
int i;
|
274
|
+
int jj;
|
275
|
+
double tmp;
|
276
|
+
|
277
|
+
Check_Type(vec, T_ARRAY);
|
278
|
+
Check_Type(row, T_FIXNUM);
|
279
|
+
|
280
|
+
TypedData_Get_Struct(self, csr_matrix, &csr_matrix_type, csr);
|
281
|
+
|
282
|
+
if (rb_array_len(vec) != csr->n)
|
283
|
+
{
|
284
|
+
rb_raise(rb_eArgError, "Dimension Mismatch CSRMatrix.n != vec.size");
|
285
|
+
}
|
286
|
+
|
287
|
+
i = NUM2INT(row);
|
288
|
+
if (!(i >= 0 && i < csr->m))
|
289
|
+
{
|
290
|
+
rb_raise(rb_eArgError, "Index Error row_idx >= m or idx < 0");
|
291
|
+
}
|
292
|
+
|
293
|
+
tmp = 0;
|
294
|
+
for (jj = csr->row_index[i]; jj < csr->row_index[i + 1]; jj++)
|
295
|
+
{
|
296
|
+
tmp += csr->values[jj] * NUM2DBL(rb_ary_entry(vec, csr->col_index[jj]));
|
297
|
+
}
|
298
|
+
|
299
|
+
result = DBL2NUM(tmp);
|
300
|
+
return result;
|
301
|
+
}
|
302
|
+
|
303
|
+
/**
|
304
|
+
* A hash representation of the matrix with coordinates as keys.
|
305
|
+
* @example
|
306
|
+
* data = [
|
307
|
+
* [0, 1, 0]
|
308
|
+
* [0, 0, 0],
|
309
|
+
* [1, 0, 1]
|
310
|
+
* ]
|
311
|
+
* num_rows = 3
|
312
|
+
* num_cols = 3
|
313
|
+
* data = data.flatten!
|
314
|
+
* csr = CSRMatrix.new(data, num_rows, num_cols)
|
315
|
+
*
|
316
|
+
* csr.coordinates
|
317
|
+
* # => {
|
318
|
+
* [0,1] => 1,
|
319
|
+
* [2,0] => 1,
|
320
|
+
* [2,2] => 1
|
321
|
+
* }
|
322
|
+
*
|
323
|
+
* @return [Hash]
|
324
|
+
*/
|
325
|
+
VALUE csr_matrix_coordinates(VALUE self)
|
326
|
+
{
|
327
|
+
csr_matrix *csr;
|
328
|
+
VALUE result;
|
329
|
+
|
330
|
+
int i;
|
331
|
+
int k;
|
332
|
+
|
333
|
+
VALUE key;
|
334
|
+
VALUE val;
|
335
|
+
int row_end;
|
336
|
+
|
337
|
+
TypedData_Get_Struct(self, csr_matrix, &csr_matrix_type, csr);
|
338
|
+
|
339
|
+
result = rb_hash_new();
|
340
|
+
|
341
|
+
// iterate through every value in the matrix and assign it's coordinates
|
342
|
+
// [x,y] as the key to the hash, with the value as the value.
|
343
|
+
// Use i to keep track of what row we are on.
|
344
|
+
i = 0;
|
345
|
+
row_end = csr->row_index[1];
|
346
|
+
for (k = 0; k < csr->nnz; k++)
|
347
|
+
{
|
348
|
+
if (k == row_end)
|
349
|
+
{
|
350
|
+
i++;
|
351
|
+
row_end = csr->row_index[i + 1];
|
352
|
+
}
|
353
|
+
|
354
|
+
// store i,j coordinates j is col_index[k]
|
355
|
+
key = rb_ary_new_capa(2);
|
356
|
+
rb_ary_store(key, 0, INT2NUM(i));
|
357
|
+
rb_ary_store(key, 1, INT2NUM(csr->col_index[k]));
|
358
|
+
|
359
|
+
val = DBL2NUM(csr->values[k]);
|
360
|
+
|
361
|
+
rb_hash_aset(result, key, val);
|
362
|
+
}
|
363
|
+
|
364
|
+
return result;
|
365
|
+
}
|
@@ -0,0 +1,35 @@
|
|
1
|
+
#ifndef CSR_MATRIX
|
2
|
+
#define CSR_MATRIX
|
3
|
+
|
4
|
+
typedef struct csr_matrix
|
5
|
+
{
|
6
|
+
char init;
|
7
|
+
int m;
|
8
|
+
int n;
|
9
|
+
int nnz;
|
10
|
+
double *values;
|
11
|
+
int *col_index;
|
12
|
+
int *row_index;
|
13
|
+
} csr_matrix;
|
14
|
+
|
15
|
+
void csr_matrix_free(void *mat);
|
16
|
+
size_t csr_matrix_memsize(const void *ptr);
|
17
|
+
|
18
|
+
// ruby VALUE for csr_matrix
|
19
|
+
static const rb_data_type_t csr_matrix_type = {
|
20
|
+
"SpatialStats::Weights::CSRMatrix",
|
21
|
+
{NULL, csr_matrix_free, csr_matrix_memsize},
|
22
|
+
0,
|
23
|
+
0,
|
24
|
+
RUBY_TYPED_FREE_IMMEDIATELY};
|
25
|
+
|
26
|
+
void mat_to_sparse(csr_matrix *csr, VALUE data, VALUE num_rows, VALUE num_cols);
|
27
|
+
VALUE csr_matrix_alloc(VALUE self);
|
28
|
+
VALUE csr_matrix_initialize(VALUE self, VALUE data, VALUE num_rows, VALUE num_cols);
|
29
|
+
VALUE csr_matrix_values(VALUE self);
|
30
|
+
VALUE csr_matrix_col_index(VALUE self);
|
31
|
+
VALUE csr_matrix_row_index(VALUE self);
|
32
|
+
VALUE csr_matrix_mulvec(VALUE self, VALUE vec);
|
33
|
+
VALUE csr_matrix_dot_row(VALUE self, VALUE vec, VALUE row);
|
34
|
+
VALUE csr_matrix_coordinates(VALUE self);
|
35
|
+
#endif
|
@@ -0,0 +1,32 @@
|
|
1
|
+
#include <ruby.h>
|
2
|
+
#include "csr_matrix.h"
|
3
|
+
|
4
|
+
/**
|
5
|
+
* Document-class: SpatialStats::Weights::CSRMatrix
|
6
|
+
*
|
7
|
+
* CSRMatrix partially implements a compressed sparse row matrix to perform
|
8
|
+
* spatial lag and other calculations. This will generally be used
|
9
|
+
* to store the weights of an observation set.
|
10
|
+
*
|
11
|
+
* @see https://en.wikipedia.org/wiki/Sparse_matrix#Compressed_sparse_row_(CSR,_CRS_or_Yale_format)
|
12
|
+
*
|
13
|
+
*/
|
14
|
+
void Init_spatial_stats()
|
15
|
+
{
|
16
|
+
VALUE spatial_stats_mod = rb_define_module("SpatialStats");
|
17
|
+
VALUE weights_mod = rb_define_module_under(spatial_stats_mod, "Weights");
|
18
|
+
VALUE csr_matrix_class = rb_define_class_under(weights_mod, "CSRMatrix", rb_cData);
|
19
|
+
|
20
|
+
rb_define_alloc_func(csr_matrix_class, csr_matrix_alloc);
|
21
|
+
rb_define_method(csr_matrix_class, "initialize", csr_matrix_initialize, 3);
|
22
|
+
rb_define_method(csr_matrix_class, "values", csr_matrix_values, 0);
|
23
|
+
rb_define_method(csr_matrix_class, "col_index", csr_matrix_col_index, 0);
|
24
|
+
rb_define_method(csr_matrix_class, "row_index", csr_matrix_row_index, 0);
|
25
|
+
rb_define_method(csr_matrix_class, "mulvec", csr_matrix_mulvec, 1);
|
26
|
+
rb_define_method(csr_matrix_class, "dot_row", csr_matrix_dot_row, 2);
|
27
|
+
rb_define_method(csr_matrix_class, "coordinates", csr_matrix_coordinates, 0);
|
28
|
+
|
29
|
+
rb_define_attr(csr_matrix_class, "m", 1, 0);
|
30
|
+
rb_define_attr(csr_matrix_class, "n", 1, 0);
|
31
|
+
rb_define_attr(csr_matrix_class, "nnz", 1, 0);
|
32
|
+
}
|