object_table 0.1.0 → 0.2.0
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.
- checksums.yaml +4 -4
- data/README.md +249 -56
- data/lib/object_table/basic_grid.rb +1 -1
- data/lib/object_table/group.rb +10 -0
- data/lib/object_table/grouped.rb +8 -13
- data/lib/object_table/masked_column.rb +4 -2
- data/lib/object_table/static_view.rb +24 -0
- data/lib/object_table/table_methods.rb +21 -9
- data/lib/object_table/temp_grouped.rb +1 -1
- data/lib/object_table/version.rb +1 -1
- data/lib/object_table/view.rb +46 -7
- data/lib/object_table.rb +8 -7
- data/spec/object_table/basic_grid_spec.rb +20 -0
- data/spec/object_table/grouped_spec.rb +52 -7
- data/spec/object_table/static_view_spec.rb +10 -0
- data/spec/object_table/temp_grouped_spec.rb +38 -0
- data/spec/object_table/view_spec.rb +61 -106
- data/spec/object_table_spec.rb +29 -0
- data/spec/subclassing_spec.rb +164 -0
- data/spec/support/object_table_example.rb +49 -11
- data/spec/{object_table/temp_view_spec.rb → support/view_example.rb} +50 -86
- metadata +10 -5
- data/lib/object_table/temp_view.rb +0 -63
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4b1327f62f465f8305a265b6a7f865c5ca43a66b
|
4
|
+
data.tar.gz: 8dc9d380fecb0ac14556f3cbe38acae87ec943b3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 666c0551cef6acff77df23fee41986c49b05e2959bb770d0c1ed06cee357774c9e57a31f359eb50603be8d9f87e03b216eec4272a1cb6e2b8ab420a934513c40
|
7
|
+
data.tar.gz: 18841d8933d04755b955fd6882fb7d8480d199d442306c0a02d44b041984f7f131f0f81d7bd4e5c4da277b4fca91847bae7d35e2a8fc63771119f8a14989e222
|
data/README.md
CHANGED
@@ -57,7 +57,18 @@ Otherwise the scalars are extended to match the length of the vector columns
|
|
57
57
|
- `#stack(table1, table2, ...)` appends then supplied tables
|
58
58
|
- `#apply(&block)` evaluates `block` in the context of the table
|
59
59
|
- `#where(&block)` filters the table
|
60
|
-
- `#
|
60
|
+
- `#group_by(&block)` splits the table into groups
|
61
|
+
|
62
|
+
For any methods taking a block, when passing a block which takes an argument, the block will be called with the table as the argument, otherwise (block with no arguments), the block is `#instance_eval`ed in the context of the block.
|
63
|
+
|
64
|
+
```ruby
|
65
|
+
>>> data = ObjectTable.new
|
66
|
+
# block with argument, binding is preserved
|
67
|
+
>>> data.apply{|tbl| self.class }
|
68
|
+
Object
|
69
|
+
>>> data.apply{ self.class }
|
70
|
+
ObjectTable
|
71
|
+
```
|
61
72
|
|
62
73
|
### Getting columns
|
63
74
|
|
@@ -79,7 +90,7 @@ You can get a column by using `#[]` or using the column name as a method.
|
|
79
90
|
|
80
91
|
### Setting columns
|
81
92
|
|
82
|
-
You can set/add columns by using `#[]=`.
|
93
|
+
You can set/add columns by using `#[]=`. This works for both vectors and scalars. Scalars are given a default type of object.
|
83
94
|
|
84
95
|
```ruby
|
85
96
|
>>> data = ObjectTable.new(a: [1, 2, 3], b: 100, c: ['a', 'b', 'c'])
|
@@ -121,6 +132,37 @@ IndexError: dst.shape[0]=3 != src.shape[0]=4
|
|
121
132
|
IndexError: dst.shape[0]=3 != src.shape[0]=4
|
122
133
|
```
|
123
134
|
|
135
|
+
#### `#set_column(name, value, typecode='object', shape...)`
|
136
|
+
|
137
|
+
`#[]=` really just calls `#set_column`, but you can have more control over the columns by calling `#set_column` yourself and adding additional arguments. Additional arguments control the shape and type of the column. They are the same as for `NArray.new`
|
138
|
+
|
139
|
+
```ruby
|
140
|
+
>>> data = ObjectTable.new(col0: [0]*3)
|
141
|
+
>>> data[:col1] = [1, 2, 3]
|
142
|
+
>>> data.col1
|
143
|
+
=> ObjectTable::Column.int(3):
|
144
|
+
[ 1, 2, 3 ]
|
145
|
+
|
146
|
+
# this time, let's make it a float instead
|
147
|
+
>>> data.set_column(:col2, [1, 2, 3], 'float')
|
148
|
+
>>> data.col2
|
149
|
+
=> ObjectTable::Column.float(3):
|
150
|
+
[ 1.0, 2.0, 3.0 ]
|
151
|
+
|
152
|
+
>>> data[:col3] = 4
|
153
|
+
>>> data.col3
|
154
|
+
=> ObjectTable::Column.object(3):
|
155
|
+
[ 4, 4, 4 ]
|
156
|
+
|
157
|
+
# this time, let's make it multi dimensional
|
158
|
+
>>> data.set_column(:col4, 4, 'int', 5)
|
159
|
+
>>> data.col4
|
160
|
+
=> ObjectTable::Column.int(5,3):
|
161
|
+
[ [ 4, 4, 4, 4, 4 ],
|
162
|
+
[ 4, 4, 4, 4, 4 ],
|
163
|
+
[ 4, 4, 4, 4, 4 ] ]
|
164
|
+
```
|
165
|
+
|
124
166
|
### Operating on columns
|
125
167
|
|
126
168
|
All standard NArray operations apply (addition, subtraction etc.)
|
@@ -136,7 +178,6 @@ Missing methods are vectorised over the column
|
|
136
178
|
### `#apply`
|
137
179
|
|
138
180
|
This is just a convenience method.
|
139
|
-
It basically `#instance_eval`s the block passed to it.
|
140
181
|
|
141
182
|
```ruby
|
142
183
|
>>> data = ObjectTable.new(a: [1, 2, 3], b: [4, 5, 6])
|
@@ -154,20 +195,47 @@ It basically `#instance_eval`s the block passed to it.
|
|
154
195
|
0: 1 4 4
|
155
196
|
1: 2 5 10
|
156
197
|
2: 3 6 18
|
157
|
-
a b c
|
198
|
+
a b c
|
199
|
+
|
200
|
+
# if you don't want it to steal the binding (self), make the block take an argument
|
201
|
+
>>> data.apply{|tbl| tbl.a + tbl.c }
|
202
|
+
=> ObjectTable::Column.int(3):
|
203
|
+
[ 5, 12, 21 ]
|
204
|
+
```
|
205
|
+
|
206
|
+
If you return a grid (e.g. through the `@R` shortcut) it will be coerced to a table.
|
207
|
+
|
208
|
+
```ruby
|
209
|
+
>>> data = ObjectTable.new(a: [1, 2, 3], b: [4, 5, 6])
|
210
|
+
# let's make a new table but with a=a*3
|
211
|
+
>>> data.apply{ @R[a: a*3, b: b] }
|
212
|
+
=> ObjectTable(3, 2)
|
213
|
+
a b
|
214
|
+
0: 3 4
|
215
|
+
1: 6 5
|
216
|
+
2: 9 6
|
217
|
+
a b
|
218
|
+
|
219
|
+
# or if you called apply expecting an argument
|
220
|
+
>>> data.apply{|tbl| tbl.R[a: tbl.a*3, b: tbl.b] }
|
221
|
+
=> ObjectTable(3, 2)
|
222
|
+
a b
|
223
|
+
0: 3 4
|
224
|
+
1: 6 5
|
225
|
+
2: 9 6
|
226
|
+
a b
|
158
227
|
```
|
159
228
|
|
160
229
|
## Filtering
|
161
230
|
|
162
231
|
Use the `#where` method and pass a filtering block.
|
163
|
-
|
164
|
-
This creates a `TempView` which syncs with the parent table.
|
232
|
+
This creates a `View`, which syncs with the parent table.
|
165
233
|
This means any changes made to the parent also affect the view.
|
166
234
|
|
167
235
|
```ruby
|
168
236
|
>>> data = ObjectTable.new(a: 0...5, b: 5...10)
|
169
237
|
>>> a_lt_3 = data.where{ a < 3 }
|
170
|
-
=> ObjectTable::
|
238
|
+
=> ObjectTable::View(3, 2)
|
171
239
|
a b
|
172
240
|
0: 0 5
|
173
241
|
1: 1 6
|
@@ -178,7 +246,7 @@ This means any changes made to the parent also affect the view.
|
|
178
246
|
>>> data[:b] = data.b.reverse
|
179
247
|
# and the view gets updated too
|
180
248
|
>>> a_lt_3
|
181
|
-
=> ObjectTable::
|
249
|
+
=> ObjectTable::View(3, 2)
|
182
250
|
a b
|
183
251
|
0: 0 9
|
184
252
|
1: 1 8
|
@@ -187,7 +255,7 @@ This means any changes made to the parent also affect the view.
|
|
187
255
|
|
188
256
|
# you can also chain #where calls
|
189
257
|
>>> data.where{ a < 3 }.where{ b > 7 }
|
190
|
-
=> ObjectTable::
|
258
|
+
=> ObjectTable::View(3, 2)
|
191
259
|
a b
|
192
260
|
0: 0 9
|
193
261
|
1: 1 8
|
@@ -196,8 +264,7 @@ This means any changes made to the parent also affect the view.
|
|
196
264
|
>>> data.where{ a < 3 && b > 7 }
|
197
265
|
```
|
198
266
|
|
199
|
-
|
200
|
-
This means any changes made to the view also affect the parent.
|
267
|
+
Any changes made to the view also affect the parent.
|
201
268
|
|
202
269
|
```ruby
|
203
270
|
>>> data.where{ a < 3 }[:b] = 100
|
@@ -243,66 +310,131 @@ Added columns have a default value of `nil` outside the view.
|
|
243
310
|
a b c
|
244
311
|
```
|
245
312
|
|
246
|
-
###
|
313
|
+
### `#apply`
|
314
|
+
|
315
|
+
Using `#apply` creates a `StaticView`. Any modifications made to the parent will not refresh the static view. Changes to the static view still affect the parent however.
|
316
|
+
|
317
|
+
```ruby
|
318
|
+
>>> data = ObjectTable.new(a: 0...5, b: 5...10)
|
319
|
+
|
320
|
+
>>> a_lt_3 = data.where{ a < 3 }
|
321
|
+
=> ObjectTable::View(3, 2)
|
322
|
+
a b
|
323
|
+
0: 0 5
|
324
|
+
1: 1 6
|
325
|
+
2: 2 7
|
326
|
+
a b
|
327
|
+
>>> a_lt_3[:a] = 5
|
328
|
+
# our view will refresh, so we can't see the changes!
|
329
|
+
>>> a_lt_3
|
330
|
+
=> ObjectTable::View(0, 2)
|
331
|
+
a b
|
332
|
+
a b
|
247
333
|
|
248
|
-
|
334
|
+
# use apply instead
|
335
|
+
>>> data = ObjectTable.new(a: 0...5, b: 5...10)
|
336
|
+
>>> data.where{a < 3}.apply{ self[:a] = 5; p self; nil }
|
337
|
+
ObjectTable::StaticView(3, 2)
|
338
|
+
a b
|
339
|
+
0: 5 5
|
340
|
+
1: 5 6
|
341
|
+
2: 5 7
|
342
|
+
a b
|
343
|
+
=> nil
|
344
|
+
```
|
345
|
+
|
346
|
+
You should never try to use a static view outside of its `#apply` block.
|
347
|
+
|
348
|
+
|
349
|
+
### Other notes
|
249
350
|
|
250
351
|
If you want to filter a table and keep that data (i.e. without it syncing with the parent, propagating changes etc.) just `#clone` it.
|
251
352
|
|
353
|
+
|
252
354
|
## Grouping (and aggregating)
|
253
355
|
|
254
|
-
Use the `#
|
356
|
+
Use the `#group_by` method and pass column names or a block that returns grouping keys.
|
255
357
|
Then call `#each` to iterate through the groups or `#apply` to aggregate the results.
|
256
|
-
The blocks are evaluated in the context of the table (in the case of `#apply`, the context of the group).
|
257
358
|
|
258
|
-
The argument to `#
|
359
|
+
The argument to `#group_by` should be a hash mapping key name => key. See the below example.
|
259
360
|
|
260
361
|
```ruby
|
261
|
-
>>> data = ObjectTable.new(name: ['John', 'Tom', '
|
362
|
+
>>> data = ObjectTable.new(name: ['John', 'Tom', 'John', 'Tom', 'Jim'], value: 1..5)
|
363
|
+
=> ObjectTable(5, 2)
|
262
364
|
name value
|
263
365
|
0: "John" 1
|
264
366
|
1: "Tom" 2
|
265
|
-
2:
|
266
|
-
3: "
|
267
|
-
4:
|
268
|
-
name value
|
269
|
-
|
270
|
-
# group by the first letter of the name and print out each group
|
271
|
-
>>> data.group{ {initial: name.map{|n| n[0]}} }.each{ p self; puts }
|
272
|
-
ObjectTable::View(3, 2)
|
273
|
-
name value
|
274
|
-
0: "John" 1
|
275
|
-
1: "Jim" 3
|
276
|
-
2: "John" 5
|
367
|
+
2: "John" 3
|
368
|
+
3: "Tom" 4
|
369
|
+
4: "Jim" 5
|
277
370
|
name value
|
278
371
|
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
#
|
286
|
-
>>>
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
372
|
+
# group by the name and get the no. of rows in each group
|
373
|
+
>>> num_rows = []
|
374
|
+
>>> data.group_by(:name).each{ num_rows.push(nrows) }
|
375
|
+
>>> num_rows
|
376
|
+
=> [2, 2, 1]
|
377
|
+
|
378
|
+
# or group with a block
|
379
|
+
>>> num_rows = []
|
380
|
+
# let's group by initial letter of the name
|
381
|
+
>>> data.group_by{ {initial: name.map{|n| n[0]}} }.each{ num_rows.push(nrows) }
|
382
|
+
>>> num_rows
|
383
|
+
=> [3, 2]
|
384
|
+
```
|
385
|
+
|
386
|
+
The group keys are accessible through the `@K` shortcut
|
387
|
+
|
388
|
+
```ruby
|
389
|
+
>>> data = ObjectTable.new(name: ['John', 'Tom', 'John', 'Tom', 'Jim'], value: 1..5)
|
390
|
+
>>> data.group_by(:name).each{ p @K }
|
391
|
+
{:name=>"John"}
|
392
|
+
{:name=>"Tom"}
|
393
|
+
{:name=>"Jim"}
|
394
|
+
|
395
|
+
# or if you are using a block with args
|
396
|
+
>>> data.group_by(:name).each{|grp| p grp.K }
|
397
|
+
{:name=>"John"}
|
398
|
+
{:name=>"Tom"}
|
399
|
+
{:name=>"Jim"}
|
292
400
|
```
|
293
401
|
|
402
|
+
|
294
403
|
### Aggregation
|
295
404
|
|
405
|
+
Call `#apply` and the results are stored into a table.
|
406
|
+
|
407
|
+
```ruby
|
408
|
+
>>> data = ObjectTable.new(name: ['John', 'Tom', 'John', 'Tom', 'Jim'], value: 1..5)
|
409
|
+
>>> data.group_by(:name).apply{ value.mean }
|
410
|
+
=> ObjectTable(3, 2)
|
411
|
+
name v_0
|
412
|
+
0: "John" 2.0
|
413
|
+
1: "Tom" 3.0
|
414
|
+
2: "Jim" 5.0
|
415
|
+
name v_0
|
416
|
+
```
|
417
|
+
|
296
418
|
Normally you can only have one aggregated column with a default name of v_0.
|
297
419
|
You can have more columns and set column names by making a `ObjectTable` or using the @R shortcut.
|
298
420
|
|
299
421
|
```ruby
|
300
|
-
>>> data.
|
301
|
-
=> ObjectTable(
|
302
|
-
|
303
|
-
0:
|
304
|
-
1:
|
305
|
-
|
422
|
+
>>> data.group_by(:name).apply{ @R[ mean: value.mean, sum: value.sum] }
|
423
|
+
=> ObjectTable(3, 3)
|
424
|
+
name mean sum
|
425
|
+
0: "John" 2.0 4
|
426
|
+
1: "Tom" 3.0 6
|
427
|
+
2: "Jim" 5.0 5
|
428
|
+
name mean sum
|
429
|
+
|
430
|
+
# or if you are using a block with args
|
431
|
+
>>> data.group_by(:name).apply{|grp| grp.R[ mean: grp.value.mean, sum: grp.value.sum] }
|
432
|
+
=> ObjectTable(3, 3)
|
433
|
+
name mean sum
|
434
|
+
0: "John" 2.0 4
|
435
|
+
1: "Tom" 3.0 6
|
436
|
+
2: "Jim" 5.0 5
|
437
|
+
name mean sum
|
306
438
|
```
|
307
439
|
|
308
440
|
### Assigning to columns
|
@@ -310,13 +442,74 @@ You can have more columns and set column names by making a `ObjectTable` or usin
|
|
310
442
|
Assigning to columns will assign by group.
|
311
443
|
|
312
444
|
```ruby
|
313
|
-
|
445
|
+
# every row with the same name will get the same group_values
|
446
|
+
>>> data.group_by(:name).each{|grp| grp[:group_values] = grp.value.to_a.join(',') }
|
314
447
|
=> ObjectTable(5, 3)
|
315
|
-
name value
|
316
|
-
0: "John" 1
|
317
|
-
1: "Tom" 2
|
318
|
-
2:
|
319
|
-
3: "
|
320
|
-
4:
|
321
|
-
name value
|
448
|
+
name value group_values
|
449
|
+
0: "John" 1 "1,3"
|
450
|
+
1: "Tom" 2 "2,4"
|
451
|
+
2: "John" 3 "1,3"
|
452
|
+
3: "Tom" 4 "2,4"
|
453
|
+
4: "Jim" 5 "5"
|
454
|
+
name value group_values
|
455
|
+
```
|
456
|
+
|
457
|
+
## Subclassing ObjectTable
|
458
|
+
|
459
|
+
The act of subclassing itself is easy, but any methods you add won't be available to child views and groups.
|
460
|
+
|
461
|
+
```ruby
|
462
|
+
>>> class BrokenTable < ObjectTable
|
463
|
+
def a_plus_b
|
464
|
+
a + b
|
465
|
+
end
|
466
|
+
end
|
467
|
+
|
468
|
+
>>> data = BrokenTable.new(a: 1..3, b: 4..6)
|
469
|
+
>>> data.a_plus_b
|
470
|
+
=> ObjectTable::Column.int(3):
|
471
|
+
[ 5, 7, 9 ]
|
472
|
+
|
473
|
+
# this won't work!
|
474
|
+
>>> data.where{ a > 1 }.a_plus_b
|
475
|
+
NoMethodError: undefined method `a_plus_b' for #<ObjectTable::View:0x000000011d4dd0>
|
476
|
+
```
|
477
|
+
|
478
|
+
To make it work, you'll need to subclass `View`, `StaticView` and `Group` too. Then set the `Table` constant on each. The easiest way is just to include a module.
|
479
|
+
|
480
|
+
```ruby
|
481
|
+
>>> class WorkingTable < ObjectTable
|
482
|
+
module Mixin
|
483
|
+
# this mixin will set the Table constant on each of the subclasses
|
484
|
+
Table = WorkingTable
|
485
|
+
|
486
|
+
def a_plus_b
|
487
|
+
a + b
|
488
|
+
end
|
489
|
+
end
|
490
|
+
|
491
|
+
include Mixin
|
492
|
+
|
493
|
+
# subclass each of these and include the Mixin too
|
494
|
+
class StaticView < StaticView; include Mixin; end
|
495
|
+
class View < View; include Mixin; end
|
496
|
+
class Group < Group; include Mixin; end
|
497
|
+
end
|
498
|
+
|
499
|
+
>>> data = WorkingTable.new(a: 1..3, b: 4..6)
|
500
|
+
>>> data.a_plus_b
|
501
|
+
=> ObjectTable::Column.int(3):
|
502
|
+
[ 5, 7, 9 ]
|
503
|
+
|
504
|
+
# hurrah!
|
505
|
+
>>> data.where{ a > 1 }.a_plus_b
|
506
|
+
=> ObjectTable::Column.int(2):
|
507
|
+
[ 7, 9 ]
|
508
|
+
|
509
|
+
# also works in groups!
|
510
|
+
>>> data.group_by{{odd: a % 2}}.each do
|
511
|
+
p "when a % 2 == #{@K[:odd]}, a + b == #{a_plus_b.to_a}"
|
512
|
+
end
|
513
|
+
"when a % 2 == 1, a + b == [5, 9]"
|
514
|
+
"when a % 2 == 0, a + b == [7]"
|
322
515
|
```
|
@@ -13,7 +13,7 @@ class ObjectTable::BasicGrid < Hash
|
|
13
13
|
narrays, scalars = scalars.partition{|k, v| v.is_a?(NArray) }
|
14
14
|
|
15
15
|
unique_rows = arrays.map{|k, v| v.count}
|
16
|
-
unique_rows += narrays.map{|k, v| v.shape.last}
|
16
|
+
unique_rows += narrays.map{|k, v| v.shape.last or 0}
|
17
17
|
unique_rows = unique_rows.uniq
|
18
18
|
|
19
19
|
if rows
|
data/lib/object_table/grouped.rb
CHANGED
@@ -1,17 +1,8 @@
|
|
1
|
-
require_relative '
|
1
|
+
require_relative 'group'
|
2
2
|
|
3
3
|
class ObjectTable::Grouped
|
4
4
|
DEFAULT_VALUE_PREFIX = 'v_'
|
5
5
|
|
6
|
-
class Group < ObjectTable::View
|
7
|
-
attr_reader :K
|
8
|
-
|
9
|
-
def initialize(parent, keys, value)
|
10
|
-
super(parent, value)
|
11
|
-
@K = keys
|
12
|
-
end
|
13
|
-
end
|
14
|
-
|
15
6
|
def initialize(parent, names, groups)
|
16
7
|
@parent = parent
|
17
8
|
@names = names
|
@@ -19,19 +10,23 @@ class ObjectTable::Grouped
|
|
19
10
|
end
|
20
11
|
|
21
12
|
def each(&block)
|
13
|
+
group_cls = @parent.class::Table::Group
|
14
|
+
|
22
15
|
@groups.each do |k, v|
|
23
16
|
names = @names.zip(k)
|
24
|
-
|
17
|
+
group_cls.new(@parent, Hash[names], v).apply &block
|
25
18
|
end
|
26
19
|
@parent
|
27
20
|
end
|
28
21
|
|
29
22
|
def apply(&block)
|
23
|
+
table_cls = @parent.class::Table
|
24
|
+
group_cls = table_cls::Group
|
30
25
|
value_key = self.class._generate_name(DEFAULT_VALUE_PREFIX, @names).to_sym
|
31
26
|
|
32
27
|
data = @groups.map do |k, v|
|
33
28
|
names = @names.zip(k)
|
34
|
-
value =
|
29
|
+
value = group_cls.new(@parent, Hash[names], v).apply &block
|
35
30
|
|
36
31
|
if value.is_a?(ObjectTable::TableMethods)
|
37
32
|
value = value.columns
|
@@ -46,7 +41,7 @@ class ObjectTable::Grouped
|
|
46
41
|
grid._ensure_uniform_columns!
|
47
42
|
end
|
48
43
|
|
49
|
-
|
44
|
+
table_cls.stack(*data)
|
50
45
|
end
|
51
46
|
|
52
47
|
def self._generate_name(prefix, existing_names)
|
@@ -28,8 +28,10 @@ class ObjectTable::MaskedColumn < ObjectTable::Column
|
|
28
28
|
alias_method :super_slice_assign, :[]=
|
29
29
|
|
30
30
|
def []=(*keys, value)
|
31
|
-
|
32
|
-
|
31
|
+
unless (value.is_a?(Array) or value.is_a?(NArray)) and value.empty?
|
32
|
+
parent[*padded_dims, indices[*keys]] = value
|
33
|
+
super
|
34
|
+
end
|
33
35
|
end
|
34
36
|
|
35
37
|
# make destructive methods affect parent
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require_relative 'view_methods'
|
2
|
+
require_relative 'basic_grid'
|
3
|
+
require_relative 'masked_column'
|
4
|
+
|
5
|
+
class ObjectTable::StaticView
|
6
|
+
include ObjectTable::ViewMethods
|
7
|
+
attr_reader :indices
|
8
|
+
|
9
|
+
def initialize(parent, indices)
|
10
|
+
super()
|
11
|
+
@parent = parent
|
12
|
+
@indices = indices
|
13
|
+
columns
|
14
|
+
end
|
15
|
+
|
16
|
+
def columns
|
17
|
+
@columns ||= super
|
18
|
+
end
|
19
|
+
|
20
|
+
def add_column(name, *args)
|
21
|
+
@columns[name] = super
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
@@ -1,6 +1,11 @@
|
|
1
1
|
require 'forwardable'
|
2
2
|
|
3
3
|
module ObjectTable::TableMethods
|
4
|
+
# this line is important!! which classes to use to make Views/StaticViews/Groups
|
5
|
+
# are taken from this constant, e.g. Table::View
|
6
|
+
Table = ObjectTable
|
7
|
+
|
8
|
+
|
4
9
|
extend Forwardable
|
5
10
|
|
6
11
|
attr_reader :R
|
@@ -19,7 +24,7 @@ module ObjectTable::TableMethods
|
|
19
24
|
end
|
20
25
|
|
21
26
|
def nrows
|
22
|
-
columns.empty? ? 0 : columns.values.first.shape[-1]
|
27
|
+
columns.empty? ? 0 : (columns.values.first.shape[-1] or 0)
|
23
28
|
end
|
24
29
|
|
25
30
|
def ncols
|
@@ -41,14 +46,16 @@ module ObjectTable::TableMethods
|
|
41
46
|
|
42
47
|
if (value.is_a?(Array) or value.is_a?(NArray)) and args.empty?
|
43
48
|
value = NArray.to_na(value)
|
44
|
-
unless value.shape[-1] == nrows
|
45
|
-
raise ArgumentError.new("Expected size of last dimension to be #{nrows}, was #{value.shape[-1]}")
|
49
|
+
unless (value.shape[-1] or 0) == nrows
|
50
|
+
raise ArgumentError.new("Expected size of last dimension to be #{nrows}, was #{value.shape[-1].inspect}")
|
46
51
|
end
|
47
52
|
|
48
53
|
args = [value.typecode] + value.shape[0...-1]
|
49
54
|
end
|
50
55
|
|
51
56
|
column = add_column(name, *args)
|
57
|
+
return column if value.is_a?(NArray) and value.empty?
|
58
|
+
|
52
59
|
begin
|
53
60
|
column[] = value
|
54
61
|
rescue Exception => e
|
@@ -63,25 +70,30 @@ module ObjectTable::TableMethods
|
|
63
70
|
end
|
64
71
|
|
65
72
|
def apply(&block)
|
66
|
-
|
73
|
+
if block.arity == 0
|
74
|
+
result = instance_eval &block
|
75
|
+
else
|
76
|
+
result = block.call(self)
|
77
|
+
end
|
78
|
+
|
67
79
|
if result.is_a? ObjectTable::BasicGrid
|
68
|
-
result =
|
80
|
+
result = self.class::Table.new(result)
|
69
81
|
end
|
70
82
|
result
|
71
83
|
end
|
72
84
|
|
73
85
|
def where(&block)
|
74
|
-
|
86
|
+
self.class::Table::View.new(self, &block)
|
75
87
|
end
|
76
88
|
|
77
|
-
def
|
89
|
+
def group_by(*args, &block)
|
78
90
|
ObjectTable::TempGrouped.new(self, *args, &block)
|
79
91
|
end
|
80
92
|
|
81
93
|
def sort_by(*keys)
|
82
94
|
sort_index = _get_sort_index(keys)
|
83
95
|
cols = ObjectTable::BasicGrid[columns.map{|k, v| [k, v[sort_index]]}]
|
84
|
-
|
96
|
+
self.class::Table.new(cols)
|
85
97
|
end
|
86
98
|
|
87
99
|
def method_missing(meth, *args, &block)
|
@@ -136,7 +148,7 @@ module ObjectTable::TableMethods
|
|
136
148
|
|
137
149
|
def clone
|
138
150
|
cols = ObjectTable::BasicGrid[columns.map{|k, v| [k, v.clone]}]
|
139
|
-
|
151
|
+
self.class::Table.new(cols)
|
140
152
|
end
|
141
153
|
|
142
154
|
def _get_sort_index(columns)
|
@@ -22,7 +22,7 @@ class ObjectTable::TempGrouped
|
|
22
22
|
|
23
23
|
def _keys
|
24
24
|
if @names.empty?
|
25
|
-
keys = @parent.
|
25
|
+
keys = @parent.apply(&@grouper)
|
26
26
|
raise 'Group keys must be hashes' unless keys.is_a?(Hash)
|
27
27
|
keys = ObjectTable::BasicGrid.new.replace keys
|
28
28
|
else
|
data/lib/object_table/version.rb
CHANGED