object_table 0.2.0 → 0.2.2
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 +6 -4
- data/lib/object_table/column.rb +36 -6
- data/lib/object_table/grouped.rb +51 -16
- data/lib/object_table/masked_column.rb +8 -10
- data/lib/object_table/printable.rb +72 -0
- data/lib/object_table/table_child.rb +19 -0
- data/lib/object_table/table_methods.rb +7 -52
- data/lib/object_table/version.rb +1 -1
- data/lib/object_table/view.rb +2 -3
- data/lib/object_table/view_methods.rb +2 -0
- data/lib/object_table.rb +91 -5
- data/spec/object_table/column_spec.rb +139 -9
- data/spec/object_table/grouped_spec.rb +134 -5
- data/spec/object_table/masked_column_spec.rb +65 -57
- data/spec/object_table_spec.rb +116 -0
- data/spec/subclassing_spec.rb +0 -2
- data/spec/support/object_table_example.rb +36 -1
- data/spec/support/view_example.rb +24 -0
- metadata +4 -5
- data/lib/object_table/temp_grouped.rb +0 -43
- data/spec/object_table/temp_grouped_spec.rb +0 -143
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 47a4b77bf7199464216dd309096200c6870cdda1
|
4
|
+
data.tar.gz: d34284715a8a0340f48e19bfb522c22bd570e5da
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cdb8e5894a5880b36635847962d9fabfb6e998be2f46d633c9d4e0c9519f678b20e9c61906a15cc6ee15b6bf8a1ec896d26c0c6e9a970abe07412b16ea8b48db
|
7
|
+
data.tar.gz: a42440c3488fecaf1e68b0d5ff2f5d0b49ec98a7f899758f13b1bddf9040c4920574a37814219ad312c875c9c0fb646fa375ff9c2ff0bef28830768e8d4d3554
|
data/README.md
CHANGED
@@ -464,6 +464,7 @@ The act of subclassing itself is easy, but any methods you add won't be availabl
|
|
464
464
|
a + b
|
465
465
|
end
|
466
466
|
end
|
467
|
+
...
|
467
468
|
|
468
469
|
>>> data = BrokenTable.new(a: 1..3, b: 4..6)
|
469
470
|
>>> data.a_plus_b
|
@@ -475,14 +476,12 @@ The act of subclassing itself is easy, but any methods you add won't be availabl
|
|
475
476
|
NoMethodError: undefined method `a_plus_b' for #<ObjectTable::View:0x000000011d4dd0>
|
476
477
|
```
|
477
478
|
|
478
|
-
To make it work, you'll need to subclass `View`, `StaticView` and `Group` too
|
479
|
+
To make it work, you'll need to subclass `View`, `StaticView` and `Group` too and assign those subclasses under your ObjectTable subclass.
|
480
|
+
The easiest way is just to include a module with your common methods.
|
479
481
|
|
480
482
|
```ruby
|
481
483
|
>>> class WorkingTable < ObjectTable
|
482
484
|
module Mixin
|
483
|
-
# this mixin will set the Table constant on each of the subclasses
|
484
|
-
Table = WorkingTable
|
485
|
-
|
486
485
|
def a_plus_b
|
487
486
|
a + b
|
488
487
|
end
|
@@ -495,6 +494,7 @@ To make it work, you'll need to subclass `View`, `StaticView` and `Group` too. T
|
|
495
494
|
class View < View; include Mixin; end
|
496
495
|
class Group < Group; include Mixin; end
|
497
496
|
end
|
497
|
+
...
|
498
498
|
|
499
499
|
>>> data = WorkingTable.new(a: 1..3, b: 4..6)
|
500
500
|
>>> data.a_plus_b
|
@@ -510,6 +510,8 @@ To make it work, you'll need to subclass `View`, `StaticView` and `Group` too. T
|
|
510
510
|
>>> data.group_by{{odd: a % 2}}.each do
|
511
511
|
p "when a % 2 == #{@K[:odd]}, a + b == #{a_plus_b.to_a}"
|
512
512
|
end
|
513
|
+
...
|
514
|
+
|
513
515
|
"when a % 2 == 1, a + b == [5, 9]"
|
514
516
|
"when a % 2 == 0, a + b == [7]"
|
515
517
|
```
|
data/lib/object_table/column.rb
CHANGED
@@ -1,6 +1,12 @@
|
|
1
1
|
require 'narray'
|
2
2
|
|
3
3
|
class ObjectTable::Column < NArray
|
4
|
+
NARRAY_ENUMERATORS = [:to_a, :collect, :map, :sort, :all?, :any?, :max, :min, :none?]
|
5
|
+
|
6
|
+
NARRAY_ENUMERATORS.each{|method| alias_method "narray_#{method}", method}
|
7
|
+
include Enumerable
|
8
|
+
NARRAY_ENUMERATORS.each{|method| alias_method method, "narray_#{method}"}
|
9
|
+
|
4
10
|
def self.make(value)
|
5
11
|
value = case value
|
6
12
|
when self
|
@@ -25,17 +31,25 @@ class ObjectTable::Column < NArray
|
|
25
31
|
self.class.make super
|
26
32
|
end
|
27
33
|
|
28
|
-
def [](*)
|
34
|
+
def [](*a)
|
29
35
|
result = super
|
30
36
|
result.is_a?(NArray) ? self.class.make(result) : result
|
31
37
|
end
|
32
38
|
|
33
|
-
def
|
34
|
-
if
|
35
|
-
|
36
|
-
else
|
37
|
-
self[*([nil] * (rank - 1)), rows]
|
39
|
+
def []=(*args)
|
40
|
+
if (args[-1].is_a?(Array) or args[-1].is_a?(NArray)) and args[-1].empty? and self.empty?
|
41
|
+
return args[-1]
|
38
42
|
end
|
43
|
+
|
44
|
+
super
|
45
|
+
end
|
46
|
+
|
47
|
+
def to_object
|
48
|
+
to_type('object')
|
49
|
+
end
|
50
|
+
|
51
|
+
def to_bool
|
52
|
+
map{|i| i ? 1 : 0}.to_type('byte')
|
39
53
|
end
|
40
54
|
|
41
55
|
def uniq
|
@@ -77,4 +91,20 @@ class ObjectTable::Column < NArray
|
|
77
91
|
# # end
|
78
92
|
# end
|
79
93
|
|
94
|
+
def stack(*others)
|
95
|
+
columns = [self] + others
|
96
|
+
new_rows = columns.map{|x| x.shape[-1]}.reduce(:+)
|
97
|
+
new_col = self.class.new(typecode, *shape[0...-1], new_rows)
|
98
|
+
|
99
|
+
padding = [nil] * (rank - 1)
|
100
|
+
|
101
|
+
row = 0
|
102
|
+
columns.each do |col|
|
103
|
+
new_col[*padding, row ... (row + col.shape[-1])] = col
|
104
|
+
row += col.shape[-1]
|
105
|
+
end
|
106
|
+
|
107
|
+
new_col
|
108
|
+
end
|
109
|
+
|
80
110
|
end
|
data/lib/object_table/grouped.rb
CHANGED
@@ -1,32 +1,67 @@
|
|
1
1
|
require_relative 'group'
|
2
|
+
require_relative 'table_child'
|
2
3
|
|
3
4
|
class ObjectTable::Grouped
|
4
5
|
DEFAULT_VALUE_PREFIX = 'v_'
|
6
|
+
include ObjectTable::TableChild
|
5
7
|
|
6
|
-
def initialize(parent, names,
|
8
|
+
def initialize(parent, *names, &grouper)
|
7
9
|
@parent = parent
|
10
|
+
@grouper = grouper
|
8
11
|
@names = names
|
9
|
-
@groups = groups
|
10
12
|
end
|
11
13
|
|
12
|
-
def each(&block)
|
13
|
-
group_cls = @parent.class::Table::Group
|
14
14
|
|
15
|
-
|
16
|
-
|
17
|
-
|
15
|
+
def _groups
|
16
|
+
names, keys = _keys()
|
17
|
+
groups = (0...@parent.nrows).zip(keys).group_by{|row, key| key}
|
18
|
+
groups.each do |k, v|
|
19
|
+
groups[k] = NArray.to_na(v.map(&:first))
|
20
|
+
end
|
21
|
+
[names, groups]
|
22
|
+
end
|
23
|
+
|
24
|
+
def _keys
|
25
|
+
if @names.empty?
|
26
|
+
keys = @parent.apply(&@grouper)
|
27
|
+
raise 'Group keys must be hashes' unless keys.is_a?(Hash)
|
28
|
+
keys = ObjectTable::BasicGrid.new.replace keys
|
29
|
+
else
|
30
|
+
keys = ObjectTable::BasicGrid[@names.map{|n| [n, @parent.get_column(n)]}]
|
31
|
+
end
|
32
|
+
|
33
|
+
keys._ensure_uniform_columns!(@parent.nrows)
|
34
|
+
names = keys.keys
|
35
|
+
keys = keys.values.map(&:to_a).transpose
|
36
|
+
[names, keys]
|
37
|
+
end
|
38
|
+
|
39
|
+
%w{ all? any? collect collect_concat count flat_map map none? one? }.each do |method|
|
40
|
+
define_method(method) do |*args, &block|
|
41
|
+
names, groups = _groups()
|
42
|
+
groups.send(method, *args) do |k, v|
|
43
|
+
keys = names.zip(k)
|
44
|
+
__group_cls__.new(@parent, Hash[keys], v).apply &block
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def each(&block)
|
50
|
+
names, groups = _groups()
|
51
|
+
groups.each do |k, v|
|
52
|
+
keys = names.zip(k)
|
53
|
+
__group_cls__.new(@parent, Hash[keys], v).apply &block
|
18
54
|
end
|
19
55
|
@parent
|
20
56
|
end
|
21
57
|
|
22
58
|
def apply(&block)
|
23
|
-
|
24
|
-
|
25
|
-
value_key = self.class._generate_name(DEFAULT_VALUE_PREFIX, @names).to_sym
|
59
|
+
names, groups = _groups()
|
60
|
+
value_key = self.class._generate_name(DEFAULT_VALUE_PREFIX, names).to_sym
|
26
61
|
|
27
|
-
data =
|
28
|
-
|
29
|
-
value =
|
62
|
+
data = groups.map do |k, v|
|
63
|
+
keys = names.zip(k)
|
64
|
+
value = __group_cls__.new(@parent, Hash[keys], v).apply &block
|
30
65
|
|
31
66
|
if value.is_a?(ObjectTable::TableMethods)
|
32
67
|
value = value.columns
|
@@ -34,14 +69,14 @@ class ObjectTable::Grouped
|
|
34
69
|
|
35
70
|
grid = case value
|
36
71
|
when ObjectTable::BasicGrid
|
37
|
-
ObjectTable::BasicGrid[
|
72
|
+
ObjectTable::BasicGrid[keys].merge!(value)
|
38
73
|
else
|
39
|
-
ObjectTable::BasicGrid[
|
74
|
+
ObjectTable::BasicGrid[keys + [[value_key, value]]]
|
40
75
|
end
|
41
76
|
grid._ensure_uniform_columns!
|
42
77
|
end
|
43
78
|
|
44
|
-
|
79
|
+
__table_cls__.stack(*data)
|
45
80
|
end
|
46
81
|
|
47
82
|
def self._generate_name(prefix, existing_names)
|
@@ -4,7 +4,11 @@ class ObjectTable::MaskedColumn < ObjectTable::Column
|
|
4
4
|
attr_accessor :indices, :parent, :padded_dims
|
5
5
|
|
6
6
|
def self.mask(parent, indices)
|
7
|
-
|
7
|
+
if parent.rank > 1
|
8
|
+
padded_dims = [nil] * (parent.rank - 1)
|
9
|
+
else
|
10
|
+
padded_dims = []
|
11
|
+
end
|
8
12
|
masked = parent.slice(*padded_dims, indices)
|
9
13
|
|
10
14
|
if masked.rank <= 0
|
@@ -28,27 +32,21 @@ class ObjectTable::MaskedColumn < ObjectTable::Column
|
|
28
32
|
alias_method :super_slice_assign, :[]=
|
29
33
|
|
30
34
|
def []=(*keys, value)
|
31
|
-
unless (value.is_a?(Array) or value.is_a?(NArray)) and value.empty?
|
35
|
+
unless parent.nil? or ((value.is_a?(Array) or value.is_a?(NArray)) and value.empty?)
|
32
36
|
parent[*padded_dims, indices[*keys]] = value
|
33
|
-
super
|
34
37
|
end
|
38
|
+
super
|
35
39
|
end
|
36
40
|
|
37
41
|
# make destructive methods affect parent
|
38
42
|
%w{ fill! indgen! indgen random! map! collect! conj! imag= mod! add! div! sbt! mul! }.each do |op|
|
39
43
|
define_method(op) do |*args, &block|
|
40
44
|
result = super(*args, &block)
|
41
|
-
parent[*padded_dims, indices] = result
|
45
|
+
parent[*padded_dims, indices] = result if parent
|
42
46
|
result
|
43
47
|
end
|
44
48
|
end
|
45
49
|
|
46
|
-
%w{ + - / * % ** to_type not abs -@ ~ }.each do |op|
|
47
|
-
define_method(op) do |*args|
|
48
|
-
ObjectTable::Column.cast super(*args)
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
50
|
def clone
|
53
51
|
ObjectTable::Column.cast(self).clone
|
54
52
|
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module ObjectTable::Printable
|
2
|
+
|
3
|
+
def self.get_printable_column(name, column)
|
4
|
+
padding = [nil] * (column.rank - 1)
|
5
|
+
rows = column.shape[-1].times.map do |i|
|
6
|
+
row = column[*padding, i]
|
7
|
+
str = row.is_a?(NArray) ? row.inspect.partition("\n")[-1].strip : row.inspect
|
8
|
+
str.split("\n")
|
9
|
+
end
|
10
|
+
|
11
|
+
name = name.to_s
|
12
|
+
[[name]] + rows + [[name]]
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.get_printable_line_numbers(numbers)
|
16
|
+
rows = numbers.map do |i|
|
17
|
+
["#{i}: "]
|
18
|
+
end
|
19
|
+
|
20
|
+
[['']] + rows + [['']]
|
21
|
+
end
|
22
|
+
|
23
|
+
def inspect(max_section = 5, col_padding = 2)
|
24
|
+
header = "#{self.class}(#{nrows}, #{ncols})\n"
|
25
|
+
|
26
|
+
return (header + "(empty table)") if ncols == 0
|
27
|
+
return (header + "(empty table with columns: #{colnames.join(", ")})") if nrows == 0
|
28
|
+
|
29
|
+
printed_columns = []
|
30
|
+
|
31
|
+
if nrows > max_section * 2
|
32
|
+
head = (0...max_section)
|
33
|
+
tail = ((nrows - max_section)...nrows)
|
34
|
+
|
35
|
+
printed_columns.push ObjectTable::Printable.get_printable_line_numbers(head.to_a + tail.to_a)
|
36
|
+
|
37
|
+
printed_columns += columns.map do |name, c|
|
38
|
+
padding = [nil] * (c.rank - 1)
|
39
|
+
c = c.slice(*padding, [head, tail])
|
40
|
+
ObjectTable::Printable.get_printable_column(name, c)
|
41
|
+
end
|
42
|
+
else
|
43
|
+
max_section = -1
|
44
|
+
printed_columns.push ObjectTable::Printable.get_printable_line_numbers(0...nrows)
|
45
|
+
printed_columns += columns.map do |name, c|
|
46
|
+
ObjectTable::Printable.get_printable_column(name, c)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
widths = printed_columns.map{|row| row.flat_map{|c| c.map(&:length)}.max + col_padding}
|
51
|
+
|
52
|
+
header + printed_columns.transpose.each_with_index.map do |row, index|
|
53
|
+
height = row.map(&:length).max
|
54
|
+
|
55
|
+
row = row.zip(widths).map do |cell, width|
|
56
|
+
cell += [" "] * (height - cell.length)
|
57
|
+
cell.map{|i| i.rjust(width)}
|
58
|
+
end
|
59
|
+
|
60
|
+
row = row.transpose.map{|i| i.join('')}.join("\n")
|
61
|
+
|
62
|
+
if index == max_section
|
63
|
+
row += "\n" + '-'*widths.reduce(:+)
|
64
|
+
end
|
65
|
+
row
|
66
|
+
end.join("\n")
|
67
|
+
|
68
|
+
rescue NoMethodError => e
|
69
|
+
raise Exception.new(e)
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
@@ -0,0 +1,19 @@
|
|
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,11 +1,8 @@
|
|
1
1
|
require 'forwardable'
|
2
|
+
require_relative 'printable'
|
2
3
|
|
3
4
|
module ObjectTable::TableMethods
|
4
|
-
|
5
|
-
# are taken from this constant, e.g. Table::View
|
6
|
-
Table = ObjectTable
|
7
|
-
|
8
|
-
|
5
|
+
include ObjectTable::Printable
|
9
6
|
extend Forwardable
|
10
7
|
|
11
8
|
attr_reader :R
|
@@ -77,23 +74,23 @@ module ObjectTable::TableMethods
|
|
77
74
|
end
|
78
75
|
|
79
76
|
if result.is_a? ObjectTable::BasicGrid
|
80
|
-
result =
|
77
|
+
result = __table_cls__.new(result)
|
81
78
|
end
|
82
79
|
result
|
83
80
|
end
|
84
81
|
|
85
82
|
def where(&block)
|
86
|
-
|
83
|
+
__view_cls__.new(self, &block)
|
87
84
|
end
|
88
85
|
|
89
86
|
def group_by(*args, &block)
|
90
|
-
ObjectTable::
|
87
|
+
ObjectTable::Grouped.new(self, *args, &block)
|
91
88
|
end
|
92
89
|
|
93
90
|
def sort_by(*keys)
|
94
91
|
sort_index = _get_sort_index(keys)
|
95
92
|
cols = ObjectTable::BasicGrid[columns.map{|k, v| [k, v[sort_index]]}]
|
96
|
-
|
93
|
+
__table_cls__.new(cols)
|
97
94
|
end
|
98
95
|
|
99
96
|
def method_missing(meth, *args, &block)
|
@@ -104,51 +101,9 @@ module ObjectTable::TableMethods
|
|
104
101
|
super or has_column?(meth)
|
105
102
|
end
|
106
103
|
|
107
|
-
def inspect(max_section = 5)
|
108
|
-
header = "#{self.class}(#{nrows}, #{ncols})\n"
|
109
|
-
printed_columns = []
|
110
|
-
|
111
|
-
if nrows > max_section * 2
|
112
|
-
head = (0...max_section)
|
113
|
-
tail = ((nrows - max_section)...nrows)
|
114
|
-
|
115
|
-
printed_columns.push [''] + (head.to_a + tail.to_a).map{|i| "#{i}: "} + ['']
|
116
|
-
printed_columns += columns.map do |name, c|
|
117
|
-
c = c.get_rows([head, tail], true)
|
118
|
-
strings = c.shape[-1].times.map do |i|
|
119
|
-
row = c.get_rows(i)
|
120
|
-
row.is_a?(NArray) ? row.inspect.partition("\n")[-1].strip : row.inspect
|
121
|
-
end
|
122
|
-
|
123
|
-
[name.to_s] + strings + [name.to_s]
|
124
|
-
end
|
125
|
-
else
|
126
|
-
max_section = -1
|
127
|
-
printed_columns.push [''] + (0...nrows).map{|i| "#{i}: "} + ['']
|
128
|
-
printed_columns += columns.map do |name, c|
|
129
|
-
[name.to_s] + c.to_a.map(&:inspect) + [name.to_s]
|
130
|
-
end
|
131
|
-
end
|
132
|
-
|
133
|
-
widths = printed_columns.map{|col| col.map(&:length).max + 2}
|
134
|
-
|
135
|
-
header + printed_columns.transpose.each_with_index.map do |row, index|
|
136
|
-
row = row.zip(widths).map do |cell, width|
|
137
|
-
cell.rjust(width)
|
138
|
-
end.join('')
|
139
|
-
|
140
|
-
if index == max_section
|
141
|
-
row += "\n" + '-'*widths.reduce(:+)
|
142
|
-
end
|
143
|
-
row
|
144
|
-
end.join("\n")
|
145
|
-
rescue NoMethodError => e
|
146
|
-
raise Exception.new(e)
|
147
|
-
end
|
148
|
-
|
149
104
|
def clone
|
150
105
|
cols = ObjectTable::BasicGrid[columns.map{|k, v| [k, v.clone]}]
|
151
|
-
|
106
|
+
__table_cls__.new(cols)
|
152
107
|
end
|
153
108
|
|
154
109
|
def _get_sort_index(columns)
|
data/lib/object_table/version.rb
CHANGED
data/lib/object_table/view.rb
CHANGED
@@ -3,7 +3,6 @@ require_relative 'view_methods'
|
|
3
3
|
require_relative 'masked_column'
|
4
4
|
|
5
5
|
class ObjectTable::View
|
6
|
-
Table = ObjectTable
|
7
6
|
include ObjectTable::ViewMethods
|
8
7
|
|
9
8
|
extend Forwardable
|
@@ -24,12 +23,12 @@ class ObjectTable::View
|
|
24
23
|
alias_method :[], :get_column
|
25
24
|
|
26
25
|
def make_view
|
27
|
-
|
26
|
+
__static_view_cls__.new @parent, indices
|
28
27
|
end
|
29
28
|
|
30
29
|
def clone
|
31
30
|
cols = ObjectTable::BasicGrid[@parent.columns.map{|k, v| [k, v[indices]]}]
|
32
|
-
|
31
|
+
__table_cls__.new(cols)
|
33
32
|
end
|
34
33
|
|
35
34
|
def inspect(*args)
|
@@ -1,9 +1,11 @@
|
|
1
1
|
require 'forwardable'
|
2
2
|
require_relative 'table_methods'
|
3
|
+
require_relative 'table_child'
|
3
4
|
|
4
5
|
module ObjectTable::ViewMethods
|
5
6
|
extend Forwardable
|
6
7
|
include ObjectTable::TableMethods
|
8
|
+
include ObjectTable::TableChild
|
7
9
|
|
8
10
|
def columns
|
9
11
|
ObjectTable::BasicGrid[@parent.columns.map{|k, v| [k, ObjectTable::MaskedColumn.mask(v, indices)]}]
|
data/lib/object_table.rb
CHANGED
@@ -5,7 +5,6 @@ require_relative "object_table/view"
|
|
5
5
|
require_relative "object_table/static_view"
|
6
6
|
require_relative "object_table/column"
|
7
7
|
require_relative "object_table/grouped"
|
8
|
-
require_relative "object_table/temp_grouped"
|
9
8
|
|
10
9
|
class ObjectTable
|
11
10
|
include TableMethods
|
@@ -32,7 +31,7 @@ class ObjectTable
|
|
32
31
|
end
|
33
32
|
|
34
33
|
def stack!(*others)
|
35
|
-
new_values = Hash.
|
34
|
+
new_values = Hash[colnames.zip(ncols.times.map{[]})]
|
36
35
|
|
37
36
|
others.each do |x|
|
38
37
|
case x
|
@@ -47,15 +46,17 @@ class ObjectTable
|
|
47
46
|
raise 'Mismatch in column names' unless (colnames | x.keys) == (colnames & x.keys)
|
48
47
|
|
49
48
|
x.each do |k, v|
|
50
|
-
|
51
|
-
new_values[k] += v
|
49
|
+
new_values[k].push NArray.to_na(v)
|
52
50
|
end
|
53
51
|
end
|
54
52
|
|
55
53
|
return self if new_values.empty?
|
54
|
+
new_rows = new_values.values.first.map{|x| x.shape[-1]}.reduce(:+)
|
55
|
+
return self unless (new_rows and new_rows != 0)
|
56
|
+
new_rows += nrows
|
56
57
|
|
57
58
|
new_values.each do |k, v|
|
58
|
-
@columns[k] =
|
59
|
+
@columns[k] = @columns[k].stack(*v)
|
59
60
|
end
|
60
61
|
self
|
61
62
|
end
|
@@ -84,4 +85,89 @@ class ObjectTable
|
|
84
85
|
self
|
85
86
|
end
|
86
87
|
|
88
|
+
def join(other, key, options={})
|
89
|
+
type = (options[:type] || 'inner')
|
90
|
+
key = [key] unless key.is_a?(Array)
|
91
|
+
|
92
|
+
lkeys = key.map{|k| get_column(k).to_a}.transpose
|
93
|
+
rkeys = key.map{|k| other[k].to_a}.transpose
|
94
|
+
|
95
|
+
rgroups = rkeys.each_with_index.group_by(&:first)
|
96
|
+
rgroups.each{|k, v| rgroups[k] = v.transpose[-1]}
|
97
|
+
|
98
|
+
rindex = rgroups.values_at(*lkeys)
|
99
|
+
lindex = lkeys.each_with_index.zip(rindex).flat_map{|(k, i), r| [i] * r.length if r}
|
100
|
+
lindex.compact!
|
101
|
+
|
102
|
+
right_cols = other.colnames - key
|
103
|
+
left_cols = colnames
|
104
|
+
|
105
|
+
if type == 'left' or type == 'outer'
|
106
|
+
lmissing = NArray.to_na(rindex.map{|x| x ? 0 : 1}).where.to_a
|
107
|
+
lindex += lmissing
|
108
|
+
rindex += [-1] * lmissing.length
|
109
|
+
end
|
110
|
+
|
111
|
+
if type == 'right' or type == 'outer'
|
112
|
+
left_cols -= key
|
113
|
+
rmissing = rgroups.values - rindex
|
114
|
+
rmissing.flatten!
|
115
|
+
lindex += [-1] * rmissing.length
|
116
|
+
rindex += rmissing
|
117
|
+
end
|
118
|
+
|
119
|
+
rindex.flatten!.compact!
|
120
|
+
|
121
|
+
lindex = NArray.to_na(lindex)
|
122
|
+
rindex = NArray.to_na(rindex)
|
123
|
+
lblank = lindex.eq(-1)
|
124
|
+
rblank = rindex.eq(-1)
|
125
|
+
|
126
|
+
left = left_cols.map do |k|
|
127
|
+
col = get_column(k)
|
128
|
+
padding = [nil] * (col.rank - 1)
|
129
|
+
col = col[*padding, lindex]
|
130
|
+
col[*padding, lblank] = [nil]
|
131
|
+
[k, col]
|
132
|
+
end
|
133
|
+
|
134
|
+
right = right_cols.map do |k|
|
135
|
+
col = other[k]
|
136
|
+
padding = [nil] * (col.rank - 1)
|
137
|
+
col = col[*padding, rindex]
|
138
|
+
col[*padding, rblank] = [nil]
|
139
|
+
[k, col]
|
140
|
+
end
|
141
|
+
|
142
|
+
keys = []
|
143
|
+
if type == 'right' or type == 'outer'
|
144
|
+
keys = key.map do |k|
|
145
|
+
col = get_column(k)
|
146
|
+
padding = [nil] * (col.rank - 1)
|
147
|
+
col = col[*padding, lindex]
|
148
|
+
col[*padding, lblank] = other[k][*padding, rmissing]
|
149
|
+
[k, col]
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
self.class.new(keys + left + right)
|
154
|
+
end
|
155
|
+
|
156
|
+
|
157
|
+
def __static_view_cls__
|
158
|
+
self.class::StaticView
|
159
|
+
end
|
160
|
+
|
161
|
+
def __view_cls__
|
162
|
+
self.class::View
|
163
|
+
end
|
164
|
+
|
165
|
+
def __group_cls__
|
166
|
+
self.class::Group
|
167
|
+
end
|
168
|
+
|
169
|
+
def __table_cls__
|
170
|
+
self.class
|
171
|
+
end
|
172
|
+
|
87
173
|
end
|