object_table 0.3.4 → 0.4.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.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +0 -1
  3. data/README.md +206 -108
  4. data/lib/object_table/basic_grid.rb +1 -1
  5. data/lib/object_table/column.rb +6 -7
  6. data/lib/object_table/factory.rb +46 -0
  7. data/lib/object_table/grouping/grid.rb +47 -0
  8. data/lib/object_table/grouping.rb +109 -0
  9. data/lib/object_table/joining.rb +71 -0
  10. data/lib/object_table/masked_column.rb +2 -2
  11. data/lib/object_table/printing.rb +69 -0
  12. data/lib/object_table/stacking.rb +66 -0
  13. data/lib/object_table/static_view.rb +2 -5
  14. data/lib/object_table/table_methods.rb +35 -22
  15. data/lib/object_table/util.rb +19 -0
  16. data/lib/object_table/version.rb +1 -1
  17. data/lib/object_table/view.rb +7 -5
  18. data/lib/object_table/view_methods.rb +3 -2
  19. data/lib/object_table.rb +8 -19
  20. data/object_table.gemspec +2 -0
  21. data/spec/object_table/column_spec.rb +2 -2
  22. data/spec/object_table/grouping_spec.rb +475 -0
  23. data/spec/object_table/static_view_spec.rb +2 -2
  24. data/spec/object_table/util_spec.rb +43 -0
  25. data/spec/object_table/view_spec.rb +6 -16
  26. data/spec/object_table_spec.rb +45 -3
  27. data/spec/subclassing_spec.rb +44 -5
  28. data/spec/support/joining_example.rb +171 -0
  29. data/spec/support/object_table_example.rb +124 -29
  30. data/spec/support/stacking_example.rb +111 -0
  31. data/spec/support/utils.rb +8 -0
  32. data/spec/support/view_example.rb +10 -13
  33. metadata +20 -12
  34. data/lib/object_table/group.rb +0 -10
  35. data/lib/object_table/grouped.rb +0 -93
  36. data/lib/object_table/printable.rb +0 -72
  37. data/lib/object_table/stacker.rb +0 -59
  38. data/lib/object_table/table_child.rb +0 -19
  39. data/spec/object_table/grouped_spec.rb +0 -351
  40. data/spec/support/stacker_example.rb +0 -158
@@ -1,93 +0,0 @@
1
- require_relative 'group'
2
- require_relative 'table_child'
3
-
4
- class ObjectTable::Grouped
5
- DEFAULT_VALUE_PREFIX = 'v_'
6
- include ObjectTable::TableChild
7
-
8
- def initialize(parent, *names, &grouper)
9
- @parent = parent
10
- @grouper = grouper
11
- @names = names
12
- end
13
-
14
- def _groups
15
- names, keys = _keys()
16
- groups = keys.length.times.group_by{|i| keys[i]}
17
- [names, groups]
18
- end
19
-
20
- def _keys
21
- if @names.empty?
22
- keys = @parent.apply(&@grouper)
23
- raise 'Group keys must be hashes' unless keys.is_a?(Hash)
24
- keys = ObjectTable::BasicGrid.new.replace keys
25
- keys._ensure_uniform_columns!(@parent.nrows)
26
- else
27
- keys = ObjectTable::BasicGrid[@names.map{|n| [n, @parent.get_column(n)]}]
28
- end
29
-
30
- names = keys.keys
31
- keys = keys.values.map(&:to_a).transpose
32
- [names, keys]
33
- end
34
-
35
- def each(&block)
36
- names, groups = _groups()
37
- enumerator = _make_groups(names, groups)
38
- return enumerator unless block
39
- enumerator.each{|grp| grp._apply_block(&block)}
40
- end
41
-
42
- def apply(&block)
43
- names, groups = _groups()
44
- value_key = self.class._generate_name(DEFAULT_VALUE_PREFIX, names).to_sym
45
- nrows = []
46
-
47
- data = _make_groups(names, groups).map do |group|
48
- value = group._apply_block(&block)
49
-
50
- case value
51
- when ObjectTable::TableMethods
52
- nrows.push(value.nrows)
53
- value = value.columns
54
- when ObjectTable::BasicGrid
55
- nrows.push(value._ensure_uniform_columns!)
56
- else
57
- nrows.push( (ObjectTable::Column.length_of(value) rescue 1) )
58
- end
59
-
60
- value = ObjectTable::BasicGrid[value_key, value] unless value.is_a?(ObjectTable::BasicGrid)
61
- value
62
- end
63
-
64
- if groups.empty?
65
- # empty table, so make all keys empty
66
- keys = ObjectTable::BasicGrid[names.zip([[]] * names.length)]
67
- else
68
- keys = groups.keys.transpose.map{|col| col.zip(nrows).flat_map{|key, rows| [key] * rows}}
69
- keys = ObjectTable::BasicGrid[names.zip(keys)]
70
- end
71
-
72
- result = __table_cls__.stack(*data)
73
- __table_cls__.new(keys.merge!(result.columns))
74
- end
75
-
76
-
77
- def _make_groups(names, groups)
78
- key_struct = Struct.new(*names.map(&:to_sym))
79
- enumerator = Enumerator.new do |y|
80
- groups.each do |k, v|
81
- y.yield __group_cls__.new(@parent, key_struct.new(*k), v)
82
- end
83
- @parent
84
- end
85
- end
86
-
87
- def self._generate_name(prefix, existing_names)
88
- regex = Regexp.new(Regexp.quote(prefix) + '(\d+)')
89
- i = existing_names.map(&regex.method(:match)).compact.map{|match| match[-1].to_i}.max || -1
90
- "#{prefix}#{i + 1}"
91
- end
92
-
93
- end
@@ -1,72 +0,0 @@
1
- module ObjectTable::Printable
2
-
3
- def self.get_printable_column(column)
4
- column.shape[-1].times.map do |i|
5
- row = column[false, i]
6
- str = row.is_a?(NArray) ? row.inspect.partition("\n")[-1].strip : row.inspect
7
- str.split("\n")
8
- end
9
- end
10
-
11
- def self.calc_column_widths(columns)
12
- columns.map{|col| col.flatten.map(&:length).max}
13
- end
14
-
15
- def _format_section(row_slice)
16
- numbers = row_slice.map{|i| ["#{i}: "]}
17
- section = columns.map do |name, c|
18
- c = c.slice(false, row_slice)
19
- ObjectTable::Printable.get_printable_column(c)
20
- end
21
-
22
- [numbers] + section
23
- end
24
-
25
- def _format_rows(rows, widths)
26
- rows.flat_map do |row|
27
- height = row.map(&:length).max
28
-
29
- row = row.zip(widths).map do |cell, width|
30
- cell += [" "] * (height - cell.length)
31
- cell.map{|i| i.rjust(width)}
32
- end
33
-
34
- row.transpose.map(&:join)
35
- end
36
- end
37
-
38
- def inspect(max_section = 5, col_padding = 2)
39
- header = "#{self.class}(#{nrows}, #{ncols})\n"
40
-
41
- return (header + "(empty table)") if ncols == 0
42
- return (header + "(empty table with columns: #{colnames.join(", ")})") if nrows == 0
43
-
44
- column_headers = [''] + colnames.map(&:to_s)
45
-
46
- if nrows > max_section * 2
47
- head = _format_section(0 ... max_section)
48
- tail = _format_section((nrows - max_section) ... nrows)
49
-
50
- columns = [column_headers, head, tail].transpose
51
- widths = NArray.to_na(ObjectTable::Printable.calc_column_widths(columns)) + col_padding
52
- total_width = widths.sum
53
-
54
- rows = _format_rows(head.transpose, widths)
55
- rows.push('-' * total_width)
56
- rows += _format_rows(tail.transpose, widths)
57
-
58
- else
59
- section = _format_section(0...nrows)
60
- columns = [column_headers, section].transpose
61
- widths = NArray.to_na(ObjectTable::Printable.calc_column_widths(columns)) + col_padding
62
- rows = _format_rows(section.transpose, widths)
63
- end
64
-
65
- column_headers = _format_rows([[column_headers].transpose], widths).join
66
- header + ([column_headers] + rows + [column_headers]).join("\n")
67
-
68
- rescue NoMethodError => e
69
- raise Exception.new(e)
70
- end
71
-
72
- end
@@ -1,59 +0,0 @@
1
- module ObjectTable::Stacker
2
-
3
- def stack!(*others)
4
- @columns.replace( self.class.stack(self, *others).columns )
5
- self
6
- end
7
-
8
- module ClassMethods
9
- def stack(*grids)
10
- keys = nil
11
-
12
- grids = grids.map do |grid|
13
- grid = _process_stackable_grid(grid, keys)
14
- keys ||= grid.keys if grid
15
- grid
16
- end.compact
17
- return self.new if grids.empty?
18
-
19
- result = keys.map do |k|
20
- segments = grids.map{|grid| grid[k]}
21
- [k, _stack_segments(segments)]
22
- end
23
-
24
- self.new(ObjectTable::BasicGrid[result])
25
- end
26
-
27
- def _stack_segments(segments)
28
- if segments.all?{|seg| seg.is_a? Array}
29
- column = NArray.to_na(segments.flatten(1))
30
-
31
- else
32
- segments.map!{|seg| NArray.to_na seg}
33
- column = ObjectTable::Column.stack(*segments)
34
-
35
- end
36
- end
37
-
38
- def _process_stackable_grid(grid, keys)
39
- case grid
40
- when ObjectTable::TableMethods
41
- grid = grid.columns
42
- when ObjectTable::BasicGrid
43
- grid._ensure_uniform_columns!
44
- end
45
-
46
- raise "Don't know how to join a #{grid.class}" unless grid.is_a?(ObjectTable::BasicGrid)
47
- return if grid.empty?
48
- raise 'Mismatch in column names' unless !keys or ( (keys - grid.keys).empty? and (grid.keys - keys).empty? )
49
- return grid
50
- end
51
-
52
- end
53
-
54
-
55
- def self.included(base)
56
- base.extend(ClassMethods)
57
- end
58
-
59
- end
@@ -1,19 +0,0 @@
1
- module ObjectTable::TableChild
2
-
3
- def __static_view_cls__
4
- @__static_view_cls__ ||= @parent.__static_view_cls__
5
- end
6
-
7
- def __view_cls__
8
- @__view_cls__ ||= @parent.__view_cls__
9
- end
10
-
11
- def __group_cls__
12
- @__group_cls__ ||= @parent.__group_cls__
13
- end
14
-
15
- def __table_cls__
16
- @__table_cls__ ||= @parent.__table_cls__
17
- end
18
-
19
- end
@@ -1,351 +0,0 @@
1
- require 'object_table'
2
- require 'object_table/grouped'
3
-
4
- describe ObjectTable::Grouped do
5
- let(:table){ ObjectTable.new(col1: [1, 2, 3, 4], col2: [5, 6, 7, 8] ) }
6
- # group based on parity (even vs odd)
7
- let(:grouped){ ObjectTable::Grouped.new(table){ {parity: col1 % 2} } }
8
-
9
- let(:even){ (table.col1 % 2).eq(0).where }
10
- let(:odd) { (table.col1 % 2).eq(1).where }
11
-
12
- describe '._generate_name' do
13
- let(:prefix){ 'key_' }
14
- subject{ ObjectTable::Grouped._generate_name(prefix, existing_keys) }
15
-
16
- context 'with no matching keys' do
17
- let(:existing_keys){ ['a', 'b', 'c'] }
18
- it 'should suffix the key with 0' do
19
- expect(subject).to eql "key_0"
20
- end
21
- end
22
-
23
- context 'with matching keys' do
24
- let(:existing_keys){ ['key_1', 'key_67', 'key_8', 'abcd'] }
25
- it 'should suffix the key with the next available number' do
26
- expect(subject).to eql "key_68"
27
- end
28
- end
29
-
30
- end
31
-
32
- describe '#initialize' do
33
-
34
- context 'when the block takes an argument' do
35
- it 'should not evaluate in the context of the table' do
36
- rspec_context = self
37
-
38
- grouped = ObjectTable::Grouped.new(table) do |tbl|
39
- receiver = eval('self', binding)
40
- expect(receiver).to_not be table
41
- expect(receiver).to be rspec_context
42
- {}
43
- end
44
- grouped._groups # call _groups to make it call the block
45
- end
46
-
47
- it 'should pass the table into the block' do
48
- grouped = ObjectTable::Grouped.new(table) do |tbl|
49
- expect(tbl).to be table
50
- {}
51
- end
52
- grouped._groups # call _groups to make it call the block
53
- end
54
- end
55
-
56
- context 'when the block takes no arguments' do
57
- it 'should call the block in the context of the table' do
58
- _ = self
59
- grouped = ObjectTable::Grouped.new(table) do
60
- receiver = eval('self', binding)
61
- _.expect(receiver).to _.be _.table
62
- {}
63
- end
64
- grouped._groups # call _groups to make it call the block
65
- end
66
- end
67
-
68
- end
69
-
70
- context 'with changes to the parent' do
71
- subject{ grouped }
72
-
73
- it 'should mirror changes to the parent' do
74
- expect(subject._groups[1]).to eql ({[0] => [1, 3], [1] => [0, 2]})
75
- table[:col1] = [2, 3, 4, 5]
76
- expect(subject._groups[1]).to eql ({[0] => [0, 2], [1] => [1, 3]})
77
- end
78
- end
79
-
80
- describe '#_groups' do
81
- subject{ grouped._groups }
82
-
83
- it 'should return the names' do
84
- expect(subject[0]).to eql [:parity]
85
- end
86
-
87
- it 'should return the group key => row mapping' do
88
- groups = subject[1]
89
- expect(groups[[0]]).to eql even.to_a
90
- expect(groups[[1]]).to eql odd.to_a
91
- end
92
-
93
- context 'when grouping by columns' do
94
- let(:table){ ObjectTable.new(key1: [0]*4 + [1]*4, key2: [0, 0, 1, 1]*2, data: 1..8 ) }
95
- let(:grouped){ ObjectTable::Grouped.new(table, :key1, :key2) }
96
-
97
- it 'should use the columns as group names' do
98
- expect(subject[0]).to eql [:key1, :key2]
99
- end
100
-
101
- it 'should use the columns as groups' do
102
- groups = subject[1]
103
- expect(groups[[0, 0]]).to eql (table.key1.eq(0) & table.key2.eq(0)).where.to_a
104
- expect(groups[[0, 1]]).to eql (table.key1.eq(0) & table.key2.eq(1)).where.to_a
105
- expect(groups[[1, 0]]).to eql (table.key1.eq(1) & table.key2.eq(0)).where.to_a
106
- expect(groups[[1, 1]]).to eql (table.key1.eq(1) & table.key2.eq(1)).where.to_a
107
- end
108
- end
109
- end
110
-
111
- describe '#each' do
112
- let(:even_group){ table.where{ (col1 % 2).eq(0) } }
113
- let(:odd_group) { table.where{ (col1 % 2).eq(1) } }
114
-
115
- context 'when the block takes an argument' do
116
- it 'should not evaluate in the context of the group' do
117
- rspec_context = self
118
-
119
- grouped.each do |group|
120
- receiver = eval('self', binding)
121
- expect(receiver).to_not be_a ObjectTable::Group
122
- expect(receiver).to be rspec_context
123
- end
124
- end
125
- end
126
-
127
- context 'when the block takes no arguments' do
128
- it 'should call the block in the context of the group' do
129
- _ = self
130
- grouped.each do
131
- receiver = eval('self', binding)
132
- _.expect(receiver).to _.be_a ObjectTable::Group
133
- end
134
- end
135
- end
136
-
137
- it 'should yield the groups' do
138
- groups = [even_group, odd_group]
139
- grouped.each do |group|
140
- expect(groups).to include group
141
- groups -= [group]
142
- end
143
- end
144
-
145
- it 'should give access to the keys' do
146
- keys = []
147
- grouped.each{ keys << Hash[@K.each_pair.to_a] }
148
- expect(keys).to match_array [{parity: 0}, {parity: 1}]
149
- end
150
-
151
- it 'should give access to the correct key' do
152
- keys = []
153
- correct_keys = []
154
- grouped.each do
155
- keys << [@K[:parity]]
156
- correct_keys << (self.col1 % 2).to_a.uniq
157
- end
158
-
159
- expect(keys).to match_array(correct_keys)
160
- end
161
-
162
- it 'should give access to the correct key' do
163
- keys = []
164
- correct_keys = []
165
- grouped.each do
166
- keys << [@K.parity]
167
- correct_keys << (self.col1 % 2).to_a.uniq
168
- end
169
-
170
- expect(keys).to match_array(correct_keys)
171
- end
172
-
173
- context 'with no block' do
174
- it 'should return an enumerator' do
175
- expect(grouped.each).to be_a Enumerator
176
- end
177
-
178
- it 'should enumerate the groups' do
179
- groups = [even_group, odd_group]
180
- grouped.each.each do |group|
181
- expect(groups).to include group
182
- groups -= [group]
183
- end
184
- end
185
-
186
- end
187
- end
188
-
189
- describe '#apply' do
190
- let(:even_group){ table.where{ (col1 % 2).eq(0) } }
191
- let(:odd_group) { table.where{ (col1 % 2).eq(1) } }
192
-
193
- subject{ grouped.apply{|group| group.col1.sum} }
194
-
195
- it 'should return a table with the group keys' do
196
- expect(subject).to be_a ObjectTable
197
- expect(subject.colnames).to include :parity
198
- end
199
-
200
- it 'should concatenate the results of the block' do
201
- expect(subject.sort_by(subject.parity)).to eql ObjectTable.new(parity: [0, 1], v_0: [6, 4])
202
- end
203
-
204
- describe 'value column auto naming' do
205
- it 'should auto name the value column' do
206
- grouped = ObjectTable::Grouped.new(table){{parity: 1}}
207
- result = grouped.apply{|group| group.col1.sum}
208
- expect(result).to have_column :v_0
209
- expect(result.v_0.to_a).to eql [table.col1.sum]
210
- end
211
-
212
- it 'should auto name the value column' do
213
- grouped = ObjectTable::Grouped.new(table){{v_0: 1}}
214
- result = grouped.apply{|group| group.col1.sum}
215
- expect(result).to have_column :v_1
216
- expect(result.v_1.to_a).to eql [table.col1.sum]
217
- end
218
- end
219
-
220
- context 'with results that are grids' do
221
- subject{ grouped.apply{ @R[sum: col1.sum, mean: col2.mean] } }
222
-
223
- it 'should return a table with the group keys' do
224
- expect(subject).to be_a ObjectTable
225
- expect(subject.colnames).to include :parity
226
- end
227
-
228
- it 'should stack the grids' do
229
- expect(subject.sort_by(subject.parity)).to eql ObjectTable.new(
230
- parity: [0, 1],
231
- sum: [even_group.col1.sum, odd_group.col1.sum],
232
- mean: [even_group.col2.mean, odd_group.col2.mean],
233
- )
234
- end
235
- end
236
-
237
- context 'with results that are tables' do
238
- subject{ grouped.apply{ ObjectTable.new(sum: col1.sum, mean: col2.mean) } }
239
-
240
- it 'should return a table with the group keys' do
241
- expect(subject).to be_a ObjectTable
242
- expect(subject.colnames).to include :parity
243
- end
244
-
245
- it 'should stack the grids' do
246
- expect(subject.sort_by(subject.parity)).to eql ObjectTable.new(
247
- parity: [0, 1],
248
- sum: [even_group.col1.sum, odd_group.col1.sum],
249
- mean: [even_group.col2.mean, odd_group.col2.mean],
250
- )
251
- end
252
- end
253
-
254
- context 'with results that are arrays' do
255
- subject{ grouped.apply{ [col1[0], col1[-1]] } }
256
-
257
- it 'should return a table with the group keys' do
258
- expect(subject).to be_a ObjectTable
259
- expect(subject.colnames).to include :parity
260
- end
261
-
262
- it 'should stack the grids' do
263
- expect(subject.where{parity.eq 0}.v_0).to eq even_group.col1[[0, -1]]
264
- expect(subject.where{parity.eq 1}.v_0).to eq odd_group.col1[[0, -1]]
265
- end
266
- end
267
-
268
- context 'with results that are narrays' do
269
- subject{ grouped.apply{ col1 < 2 } }
270
-
271
- it 'should return a table with the group keys' do
272
- expect(subject).to be_a ObjectTable
273
- expect(subject.colnames).to include :parity
274
- end
275
-
276
- it 'should stack the grids' do
277
- expect(subject.where{parity.eq 0}.v_0).to eq (even_group.col1 < 2)
278
- expect(subject.where{parity.eq 1}.v_0).to eq (odd_group.col1 < 2)
279
- end
280
- end
281
-
282
- context 'when the block takes an argument' do
283
- it 'should not evaluate in the context of the group' do
284
- rspec_context = self
285
-
286
- grouped.apply do |group|
287
- receiver = eval('self', binding)
288
- expect(receiver).to_not be_a ObjectTable::Group
289
- expect(receiver).to be rspec_context
290
- nil
291
- end
292
- end
293
- end
294
-
295
- context 'when the block takes no arguments' do
296
- it 'should call the block in the context of the group' do
297
- _ = self
298
- grouped.apply do
299
- receiver = eval('self', binding)
300
- _.expect(receiver).to _.be_a ObjectTable::Group
301
- nil
302
- end
303
- end
304
- end
305
-
306
- context 'with a matrix key' do
307
- let(:ngroups) { 10 }
308
- let(:table) do
309
- ObjectTable.new(
310
- key1: 10.times.map{[rand, 'abc']} * ngroups,
311
- key2: 10.times.map{[rand, 'def', 'ghi']} * ngroups,
312
- value: (ngroups*10).times.map{rand},
313
- )
314
- end
315
-
316
- let(:grouped) { ObjectTable::Grouped.new(table, :key1, :key2) }
317
- subject{ grouped.apply{|group| group.value.sum} }
318
-
319
- it 'should return a table with the group keys' do
320
- expect(subject).to be_a ObjectTable
321
- expect(subject.colnames).to include :key1
322
- expect(subject.colnames).to include :key2
323
- end
324
-
325
- it 'should preserve the dimensions of the keys' do
326
- expect(subject.key1.shape[0...-1]).to eql table.key1.shape[0...-1]
327
- expect(subject.key2.shape[0...-1]).to eql table.key2.shape[0...-1]
328
- end
329
-
330
- context 'with vector values' do
331
- subject{ grouped.apply{|group| group.value[0...10]} }
332
-
333
- it 'should work' do
334
- expect{subject}.to_not raise_error
335
- end
336
- end
337
- end
338
-
339
- context 'on an empty table' do
340
- let(:table) { ObjectTable.new(col1: [], col2: []) }
341
-
342
- it 'should return a table with no rows and only key columns' do
343
- expect(subject.nrows).to eql 0
344
- expect(subject.columns.keys).to eql [:parity]
345
- end
346
- end
347
-
348
- end
349
-
350
-
351
- end