red_amber 0.1.3 → 0.1.4

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.
data/doc/Vector.md ADDED
@@ -0,0 +1,195 @@
1
+ # Vector
2
+
3
+ Class `RedAmber::Vector` represents a series of data in the DataFrame.
4
+
5
+ ## Constructor
6
+
7
+ ### Create from a column in a DataFrame
8
+
9
+ ```ruby
10
+ df = RedAmber::DataFrame.new(x: [1, 2, 3])
11
+ df[:x]
12
+ # =>
13
+ #<RedAmber::Vector(:uint8, size=3):0x000000000000f4ec>
14
+ [1, 2, 3]
15
+ ```
16
+
17
+ ### New from an Array
18
+
19
+ ```ruby
20
+ vector = RedAmber::Vector.new([1, 2, 3])
21
+ # =>
22
+ #<RedAmber::Vector(:uint8, size=3):0x000000000000f514>
23
+ [1, 2, 3]
24
+ ```
25
+
26
+ ## Properties
27
+
28
+ ### `to_s`
29
+
30
+ ### `values`, `to_a`, `entries`
31
+
32
+ ### `size`, `length`, `n_rows`, `nrow`
33
+
34
+ ### `type`
35
+
36
+ ### `data_type`
37
+
38
+ ### [ ] `each` (not impremented yet)
39
+
40
+ ### [ ] `chunked?` (not impremented yet)
41
+
42
+ ### [ ] `n_chunks` (not impremented yet)
43
+
44
+ ### [ ] `each_chunk` (not impremented yet)
45
+
46
+ ### `tally`
47
+
48
+ ### `n_nils`, `n_nans`
49
+
50
+ - `n_nulls` is an alias of `n_nils`
51
+
52
+ ### `inspect(limit: 80)`
53
+
54
+ - `limit` sets size limit to display long array.
55
+
56
+ ```ruby
57
+ vector = RedAmber::Vector.new((1..50).to_a)
58
+ # =>
59
+ #<RedAmber::Vector(:uint8, size=50):0x000000000000f528>
60
+ [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, ... ]
61
+ ```
62
+
63
+ ## Functions
64
+
65
+ ### Unary aggregations: `vector.func => scalar`
66
+
67
+ ![unary aggregation](doc/image/../../image/vector/unary_aggregation_w_option.png)
68
+
69
+ | Method |Boolean|Numeric|String|Options|Remarks|
70
+ | ----------- | --- | --- | --- | --- | --- |
71
+ | ✓ `all` | ✓ | | | ✓ ScalarAggregate| |
72
+ | ✓ `any` | ✓ | | | ✓ ScalarAggregate| |
73
+ | ✓ `approximate_median`| |✓| | ✓ ScalarAggregate| alias `median`|
74
+ | ✓ `count` | ✓ | ✓ | ✓ | ✓ Count | |
75
+ | ✓ `count_distinct`| ✓ | ✓ | ✓ | ✓ Count |alias `count_uniq`|
76
+ |[ ]`index` | [ ] | [ ] | [ ] |[ ] Index | |
77
+ | ✓ `max` | ✓ | ✓ | ✓ | ✓ ScalarAggregate| |
78
+ | ✓ `mean` | ✓ | ✓ | | ✓ ScalarAggregate| |
79
+ | ✓ `min` | ✓ | ✓ | ✓ | ✓ ScalarAggregate| |
80
+ | ✓ `min_max` | ✓ | ✓ | ✓ | ✓ ScalarAggregate| |
81
+ |[ ]`mode` | | [ ] | |[ ] Mode | |
82
+ | ✓ `product` | ✓ | ✓ | | ✓ ScalarAggregate| |
83
+ |[ ]`quantile`| | [ ] | |[ ] Quantile| |
84
+ | ✓ `sd ` | | ✓ | | |ddof: 1 at `stddev`|
85
+ | ✓ `stddev` | | ✓ | | ✓ Variance|ddof: 0 by default|
86
+ | ✓ `sum` | ✓ | ✓ | | ✓ ScalarAggregate| |
87
+ |[ ]`tdigest` | | [ ] | |[ ] TDigest | |
88
+ | ✓ `var `| | ✓ | | |ddof: 1 at `variance`<br>alias `unbiased_variance`|
89
+ | ✓ `variance`| | ✓ | | ✓ Variance|ddof: 0 by default|
90
+
91
+
92
+ Options can be used as follows.
93
+ See the [document of C++ function](https://arrow.apache.org/docs/cpp/compute.html) for detail.
94
+
95
+ ```ruby
96
+ double = RedAmber::Vector.new([1, 0/0.0, -1/0.0, 1/0.0, nil, ""])
97
+ #=>
98
+ #<RedAmber::Vector(:double, size=6):0x000000000000f910>
99
+ [1.0, NaN, -Infinity, Infinity, nil, 0.0]
100
+
101
+ double.count #=> 5
102
+ double.count(opts: {mode: :only_valid}) #=> 5, default
103
+ double.count(opts: {mode: :only_null}) #=> 1
104
+ double.count(opts: {mode: :all}) #=> 6
105
+
106
+ boolean = RedAmber::Vector.new([true, true, nil])
107
+ #=>
108
+ #<RedAmber::Vector(:boolean, size=3):0x000000000000f924>
109
+ [true, true, nil]
110
+
111
+ boolean.all #=> true
112
+ boolean.all(opts: {skip_nulls: true}) #=> true
113
+ boolean.all(opts: {skip_nulls: false}) #=> false
114
+ ```
115
+
116
+ ### Unary element-wise: `vector.func => vector`
117
+
118
+ ![unary element-wise](doc/image/../../image/vector/unary_element_wise.png)
119
+
120
+ | Method |Boolean|Numeric|String|Options|Remarks|
121
+ | ------------ | --- | --- | --- | --- | ----- |
122
+ | ✓ `-@` | | ✓ | | |as `-vector`|
123
+ | ✓ `negate` | | ✓ | | |`-@` |
124
+ | ✓ `abs` | | ✓ | | | |
125
+ |[ ]`acos` | | [ ] | | | |
126
+ |[ ]`asin` | | [ ] | | | |
127
+ | ✓ `atan` | | ✓ | | | |
128
+ | ✓ `bit_wise_not`| | (✓) | | |integer only|
129
+ |[ ]`ceil` | | ✓ | | | |
130
+ | ✓ `cos` | | ✓ | | | |
131
+ |[ ]`floor` | | ✓ | | | |
132
+ | ✓ `invert` | ✓ | | | |`!`, alias `not`|
133
+ |[ ]`ln` | | [ ] | | | |
134
+ |[ ]`log10` | | [ ] | | | |
135
+ |[ ]`log1p` | | [ ] | | | |
136
+ |[ ]`log2` | | [ ] | | | |
137
+ |[ ]`round` | | [ ] | |[ ] Round| |
138
+ |[ ]`round_to_multiple`| | [ ] | |[ ] RoundToMultiple| |
139
+ | ✓ `sign` | | ✓ | | | |
140
+ | ✓ `sin` | | ✓ | | | |
141
+ | ✓ `tan` | | ✓ | | | |
142
+ |[ ]`trunc` | | ✓ | | | |
143
+
144
+ ### Binary element-wise: `vector.func(vector) => vector`
145
+
146
+ ![binary element-wise](doc/image/../../image/vector/binary_element_wise.png)
147
+
148
+ | Method |Boolean|Numeric|String|Options|Remarks|
149
+ | ----------------- | --- | --- | --- | --- | ----- |
150
+ | ✓ `add` | | ✓ | | | `+` |
151
+ | ✓ `atan2` | | ✓ | | | |
152
+ | ✓ `and_kleene` | ✓ | | | | `&` |
153
+ | ✓ `and_org ` | ✓ | | | |`and` in Red Arrow|
154
+ | ✓ `and_not` | ✓ | | | | |
155
+ | ✓ `and_not_kleene`| ✓ | | | | |
156
+ | ✓ `bit_wise_and` | | (✓) | | |integer only|
157
+ | ✓ `bit_wise_or` | | (✓) | | |integer only|
158
+ | ✓ `bit_wise_xor` | | (✓) | | |integer only|
159
+ | ✓ `divide` | | ✓ | | | `/` |
160
+ | ✓ `equal` | ✓ | ✓ | ✓ | |`==`, alias `eq`|
161
+ | ✓ `greater` | ✓ | ✓ | ✓ | |`>`, alias `gt`|
162
+ | ✓ `greater_equal` | ✓ | ✓ | ✓ | |`>=`, alias `ge`|
163
+ | ✓ `is_finite` | | ✓ | | | |
164
+ | ✓ `is_inf` | | ✓ | | | |
165
+ | ✓ `is_na` | ✓ | ✓ | ✓ | | |
166
+ | ✓ `is_nan` | | ✓ | | | |
167
+ |[ ]`is_nil` | ✓ | ✓ | ✓ |[ ] Null|alias `is_null`|
168
+ | ✓ `is_valid` | ✓ | ✓ | ✓ | | |
169
+ | ✓ `less` | ✓ | ✓ | ✓ | |`<`, alias `lt`|
170
+ | ✓ `less_equal` | ✓ | ✓ | ✓ | |`<=`, alias `le`|
171
+ |[ ]`logb` | | [ ] | | | |
172
+ |[ ]`mod` | | [ ] | | | `%` |
173
+ | ✓ `multiply` | | ✓ | | | `*` |
174
+ | ✓ `not_equal` | ✓ | ✓ | ✓ | |`!=`, alias `ne`|
175
+ | ✓ `or_kleene` | ✓ | | | | `\|` |
176
+ | ✓ `or_org` | ✓ | | | |`or` in Red Arrow|
177
+ | ✓ `power` | | ✓ | | | `**` |
178
+ | ✓ `subtract` | | ✓ | | | `-` |
179
+ | ✓ `shift_left` | | (✓) | | |`<<`, integer only|
180
+ | ✓ `shift_right` | | (✓) | | |`>>`, integer only|
181
+ | ✓ `xor` | ✓ | | | | `^` |
182
+
183
+ (Not impremented functions)
184
+ ### [ ] sort, sort_index
185
+ ### [ ] argmin, argmax
186
+ ### [ ] (array functions)
187
+ ### [ ] (strings functions)
188
+ ### [ ] (temporal functions)
189
+ ### [ ] (conditional functions)
190
+ ### [ ] (index functions)
191
+ ### [ ] (other functions)
192
+
193
+ ## Coerce (not impremented)
194
+
195
+ ## Updating (not impremented)
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
data/doc/image/tdr.png ADDED
Binary file
Binary file
Binary file
data/doc/tdr.md ADDED
@@ -0,0 +1,53 @@
1
+ # TDR (Transposed DataFrame Representation)
2
+
3
+ ([Japanese version](tdr_ja.md) of this document is available)
4
+
5
+ TDR is a presentation style of 2D data. It shows columnar vector values in *row Vector* and observations in *column* just like a **transposed** table.
6
+
7
+ ![TDR Image](image/tdr.png)
8
+
9
+ Row-oriented data table (1) and columnar data table (2) have different data allocation in memory within a context of Arrow Columnar Format. But they have the same data placement (in rows and columns) in our brain.
10
+
11
+ TDR (3) is a logical concept of data placement to transpose rows and columns in a columnar table (2).
12
+
13
+ ![TDR and Table Image](image/tdr_and_table.png)
14
+
15
+ TDR is not an implementation in software but a logical image in our mind.
16
+
17
+ TDR is consistent with the 'transposed' tidy data concept. The only thing we should do is not to use the positional words 'row' and 'column'.
18
+
19
+ ![tidy data in TDR](image/tidy_data_in_TDR.png)
20
+
21
+ TDR is one of a simple way to create DataFrame object in many libraries. For example, we can initalize Arrow::Table in Red Arrow like the right below and get table as left.
22
+
23
+ ![Arrow Table New](image/arrow_table_new.png)
24
+
25
+ We are using TDR style code naturally. For other example:
26
+ - Ruby: Daru::DataFrame, Rover::DataFrame accept same arguments.
27
+ - Python: similar style in Pandas for pd.DataFrame(data_in_dict)
28
+ - R: similar style in tidyr for tibble(x = 1:3, y = c("A", "B", "C"))
29
+
30
+ There are other ways to initialize data frame, but they are not intuitive.
31
+
32
+ ## Table and TDR API
33
+
34
+ The API based on TDR is draft and RedAmber is a small experiment to test the TDR concept. The following is a comparison of Table and TDR (draft).
35
+
36
+ | |Basic Table|Transposed DataFrame|Comment for TDR|
37
+ |-----------|---------|------------|---|
38
+ |name in TDR|`Table`|`TDR`|**T**ransposed **D**ataFrame **R**epresentation|
39
+ |variable |located in a column|a key and a `Vector` in lateral|select by key|
40
+ |observation|located in a row|intersection in a vertical axis|select by index|
41
+ |number of rows|n_rows etc. |`size` |`n_row` is available as an alias|
42
+ |number of columns|n_columns etc. |`n_keys` |`n_col` is available as an alias|
43
+ |shape |[n_rows, n_columns] |`[size, n_keys]` |same order as Table|
44
+ |merge/join left| left_join(a,b)<br>merge(a, b, how='left')|`a.join(b)` |naturally join from bottom|
45
+ |merge/join right| right_join(a,b))<br>merge(a, b, how='right')|`b.join(a)` |naturally join from bottom|
46
+
47
+ ## Operation example with TDR API
48
+
49
+ [Operation example with TDR API](TDR_operation.pdf) (draft)
50
+
51
+ ## Q and A for TDR
52
+
53
+ (Not prepared yet)
data/doc/tdr_ja.md ADDED
@@ -0,0 +1,53 @@
1
+ # TDR (Transposed DataFrame Representation)
2
+
3
+ ([英語版](tdr.md) もあります)
4
+
5
+ TDR は、2次元のデータの表現方法につけた名前です。TDR では下の図のように同じ型のデータに key というラベルをつけて横に並べ、それらを縦に積み重ねてデータを表現します。
6
+
7
+ ![TDR Image](image/tdr.png)
8
+
9
+ Arrow Columnar Format では、csv のような従来の行指向データ(1)に対して、列方向に連続したデータ(2)を取り扱います。この行、列という言葉は私たちの脳内イメージを規定していて、データフレームの構造といえば(1)または(2)のような形を思い浮かべることでしょう。しかし、本質は連続したデータの配置にあるので、我々の頭の中では(3)のように行と列を入れ替えて考えてもいいはずです。
10
+
11
+ ![TDR and Table Image](image/tdr_and_table.png)
12
+
13
+ 大事なことは、TDR は頭の中の論理的なイメージであって、実装上のアーキテクチャではないということです。
14
+
15
+ TDR は、整然データ(tidy data)の考え方とも矛盾しません。TDR における整然データは行と列を入れ替えた形で全く同じデータを表しています。一つだけ気をつけることは、混乱を避けるため、位置や方向に関するワードである行(row)や列(column)を避けるべきであるということです。
16
+
17
+ ![tidy data in TDR](image/tidy_data_in_TDR.png)
18
+
19
+ TDR は、現時点でも2次元データを楽に初期化できる記法で、ごく自然に使われています。例えば、Red Arrow ではArrow::Table を初期化する際に下の図の右のように書けます。
20
+
21
+ ![Arrow Table New](image/arrow_table_new.png)
22
+
23
+ これはごく自然な書き方ですが、この形は TDR の形と一致しています。その他の例として:
24
+ - Ruby: Daru::DataFrame, Rover::DataFrame でも上と同じように書けます。
25
+ - Python: Pandas で pd.DataFrame(data_in_dict) のように dict を使う場合が同じです。
26
+ - R: tidyr で tibble(x = 1:3, y = c("A", "B", "C")) のように書けます。
27
+
28
+ それぞれのライブラリーで、データフレームを初期化するやり方はこれだけではありませんが、他の方法は少し回りくどいような印象があります。
29
+
30
+ TDR で考えた方がちょっぴりうまくいくというのは単なる仮説ですが、その理由は「この惑星では横書きでコードを書く」からではないかと私は考えています。
31
+
32
+ ## Table and TDR API
33
+
34
+ TDR に基づいた API はまだ暫定板の段階であり、RedAmber は TDR の実験の場であると考えています。下記の表に TDR と行x列形式の Table のAPIの比較を示します(暫定版)。
35
+
36
+ | |従来の Table|Transposed DataFrame|TDRに対するコメント|
37
+ |-----------|---------|------------|---|
38
+ |TDRでの呼称|`Table`|`TDR`|**T**ransposed **D**ataFrame **R**epresentationの略|
39
+ |変数 |列に配置|`variables`<br>key と `Vector` として横方向に配置|key で選択|
40
+ |観測 |行に配置|`observations`<br>縦方向に切った一つ一つは`slice`|index や `slice` メソッドで選択|
41
+ |行の数|nrow, n_rows など |`size` |`n_row` をエイリアスとして設定|
42
+ |列の数|ncol, n_columns など |`n_keys` |`n_col` をエイリアスとして設定|
43
+ |shape |[nrow, ncol] |`[size, n_keys]` |行, 列の順番は同じ|
44
+ |merge/join left| left_join(a,b)<br>merge(a, b, how='left')|`a.join(b)` |自然に下にくっつける|
45
+ |merge/join right| right_join(a,b))<br>merge(a, b, how='right')|`b.join(a)` |自然に下にくっつける|
46
+
47
+ ## Operation example with TDR API
48
+
49
+ [TDR の操作例](TDR_operation.pdf) (暫定版)
50
+
51
+ ## Q and A for TDR
52
+
53
+ (作成中)
@@ -5,8 +5,11 @@ module RedAmber
5
5
  # @table : holds Arrow::Table object
6
6
  class DataFrame
7
7
  # mix-in
8
+ include DataFrameDisplayable
9
+ include DataFrameHelper
8
10
  include DataFrameSelectable
9
- include DataFrameOutput
11
+ include DataFrameObservationOperation
12
+ include DataFrameVariableOperation
10
13
 
11
14
  def initialize(*args)
12
15
  # DataFrame.new, DataFrame.new([]), DataFrame.new({}), DataFrame.new(nil)
@@ -44,43 +47,42 @@ module RedAmber
44
47
  end
45
48
 
46
49
  # Properties ===
47
- def n_rows
50
+ def size
48
51
  @table.n_rows
49
52
  end
50
- alias_method :nrow, :n_rows
51
- alias_method :size, :n_rows
52
- alias_method :length, :n_rows
53
+ alias_method :n_rows, :size
54
+ alias_method :n_obs, :size
53
55
 
54
- def n_columns
56
+ def n_keys
55
57
  @table.n_columns
56
58
  end
57
- alias_method :ncol, :n_columns
58
- alias_method :width, :n_columns
59
+ alias_method :n_cols, :n_keys
60
+ alias_method :n_vars, :n_keys
59
61
 
60
62
  def shape
61
- [n_rows, n_columns]
63
+ [size, n_keys]
62
64
  end
63
65
 
64
- def column_names
66
+ def keys
65
67
  @table.columns.map { |column| column.name.to_sym }
66
68
  end
67
- alias_method :keys, :column_names
68
- alias_method :header, :column_names
69
+ alias_method :column_names, :keys
70
+ alias_method :var_names, :keys
69
71
 
70
72
  def key?(key)
71
- column_names.include?(key.to_sym)
73
+ keys.include?(key.to_sym)
72
74
  end
73
75
  alias_method :has_key?, :key?
74
76
 
75
77
  def key_index(key)
76
- column_names.find_index(key.to_sym)
78
+ keys.find_index(key.to_sym)
77
79
  end
78
80
  alias_method :find_index, :key_index
79
81
  alias_method :index, :key_index
80
82
 
81
83
  def types
82
84
  @table.columns.map do |column|
83
- column.data_type.to_s.to_sym
85
+ column.data.value_type.nick.to_sym
84
86
  end
85
87
  end
86
88
 
@@ -96,6 +98,11 @@ module RedAmber
96
98
  end
97
99
  end
98
100
 
101
+ def indexes
102
+ 0...size
103
+ end
104
+ alias_method :indices, :indexes
105
+
99
106
  def to_h
100
107
  @table.columns.each_with_object({}) do |column, result|
101
108
  result[column.name.to_sym] = column.entries
@@ -4,7 +4,7 @@ require 'stringio'
4
4
 
5
5
  module RedAmber
6
6
  # mix-ins for the class DataFrame
7
- module DataFrameOutput
7
+ module DataFrameDisplayable
8
8
  def to_s
9
9
  @table.to_s
10
10
  end
@@ -13,19 +13,37 @@ module RedAmber
13
13
 
14
14
  # def summary() end
15
15
 
16
- def inspect_raw
17
- format "#<#{self.class}:0x%016x>\n#{self}", object_id
16
+ def inspect
17
+ "#<#{shape_str(with_id: true)}>\n#{dataframe_info(3)}"
18
18
  end
19
19
 
20
- # - tally_level: max level to use tally mode
21
- # - max_element: max element to show values in each row
22
- # - TODO: Is it better to change name other than `inspect` ?
23
- # - TODO: Fall back to inspect_raw when treating large dataset
24
- # - TODO: Refactor code to smaller methods
25
- def inspect(tally_level: 5, max_element: 5)
26
- return '#<RedAmber::DataFrame (empty)>' if empty?
20
+ # - limit: max num of Vectors to show
21
+ # - tally: max level to use tally mode
22
+ # - elements: max element to show values in each vector
23
+ def tdr(limit = 10, tally: 5, elements: 5)
24
+ puts tdr_str(limit, tally: tally, elements: elements)
25
+ end
26
+
27
+ def tdr_str(limit = 10, tally: 5, elements: 5)
28
+ "#{shape_str}\n#{dataframe_info(limit, tally_level: tally, max_element: elements)}"
29
+ end
30
+
31
+ private # =====
32
+
33
+ def pl(num)
34
+ num > 1 ? 's' : ''
35
+ end
36
+
37
+ def shape_str(with_id: false)
38
+ shape_info = empty? ? '(empty)' : "#{size} x #{n_keys} Vector#{pl(n_keys)}"
39
+ id = with_id ? format(', 0x%016x', object_id) : ''
40
+ "#{self.class} : #{shape_info}#{id}"
41
+ end
27
42
 
28
- stringio = StringIO.new # output string buffer
43
+ def dataframe_info(limit, tally_level: 5, max_element: 5)
44
+ return '' if empty?
45
+
46
+ limit = n_keys if [:all, -1].include? limit
29
47
 
30
48
  tallys = vectors.map(&:tally)
31
49
  levels = tallys.map(&:size)
@@ -34,48 +52,37 @@ module RedAmber
34
52
  headers = { idx: '#', key: 'key', type: 'type', levels: 'level', data: 'data_preview' }
35
53
  header_format = make_header_format(levels, headers, quoted_keys)
36
54
 
37
- # 1st row: show shape of the dataframe
38
- vs = "Vector#{pl(ncol)}"
39
- stringio.puts \
40
- "#{self.class} : #{nrow} x #{ncol} #{vs}"
41
-
42
- # 2nd row: show var counts by type
43
- stringio.puts "#{vs} : #{var_type_count(type_groups).join(', ')}"
44
-
45
- # 3rd row: print header of rows
46
- stringio.printf header_format, *headers.values
55
+ sio = StringIO.new # output string buffer
56
+ sio.puts "Vector#{pl(n_keys)} : #{var_type_count(type_groups).join(', ')}"
57
+ sio.printf header_format, *headers.values
47
58
 
48
- # 4th row ~: show details for each column (vector)
49
59
  vectors.each.with_index do |vector, i|
60
+ if i >= limit
61
+ sio << " ... #{n_keys - i} more Vector#{pl(n_keys - i)} ...\n"
62
+ break
63
+ end
50
64
  key = quoted_keys[i]
51
65
  type = types[i]
52
66
  type_group = type_groups[i]
53
67
  data_tally = tallys[i]
54
-
55
68
  a = case type_group
56
69
  when :numeric, :string, :boolean
57
- if data_tally.size <= tally_level && data_tally.size != nrow
70
+ if data_tally.size <= tally_level && data_tally.size != size
58
71
  [data_tally.to_s]
59
72
  else
60
- [shorthand(vector, nrow, max_element)].concat na_string(vector)
73
+ [shorthand(vector, size, max_element)].concat na_string(vector)
61
74
  end
62
75
  else
63
- shorthand(vector, nrow, max_element)
76
+ shorthand(vector, size, max_element)
64
77
  end
65
- stringio.printf header_format, i + 1, key, type, data_tally.size, a.join(', ')
78
+ sio.printf header_format, i + 1, key, type, data_tally.size, a.join(', ')
66
79
  end
67
- stringio.string
68
- end
69
-
70
- private # =====
71
-
72
- def pl(num)
73
- num > 1 ? 's' : ''
80
+ sio.string
74
81
  end
75
82
 
76
83
  def make_header_format(levels, headers, quoted_keys)
77
84
  # find longest word to adjust column width
78
- w_idx = ncol.to_s.size
85
+ w_idx = n_keys.to_s.size
79
86
  w_key = [quoted_keys.map(&:size).max, headers[:key].size].max
80
87
  w_type = [types.map(&:size).max, headers[:type].size].max
81
88
  w_row = [levels.map { |l| l.to_s.size }.max, headers[:levels].size].max
@@ -103,10 +110,10 @@ module RedAmber
103
110
  a
104
111
  end
105
112
 
106
- def shorthand(vector, nrow, max_element)
113
+ def shorthand(vector, size, max_element)
107
114
  a = vector.to_a.take(max_element)
108
115
  a.map! { |e| e.nil? ? 'nil' : e.inspect }
109
- a << '... ' if nrow > max_element
116
+ a << '... ' if size > max_element
110
117
  "[#{a.join(', ')}]"
111
118
  end
112
119
 
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RedAmber
4
+ # mix-in for the class DataFrame
5
+ module DataFrameHelper
6
+ private
7
+
8
+ def expand_range(args)
9
+ args.each_with_object([]) do |e, a|
10
+ e.is_a?(Range) ? a.concat(normalized_array(e)) : a.append(e)
11
+ end
12
+ end
13
+
14
+ def normalized_array(range)
15
+ both_end = [range.begin, range.end]
16
+ both_end[1] -= 1 if range.exclude_end? && range.end.is_a?(Integer)
17
+
18
+ if both_end.any?(Integer) || both_end.all?(&:nil?)
19
+ if both_end.any? { |e| e&.>=(size) || e&.<(-size) }
20
+ raise DataFrameArgumentError, "Index out of range: #{range} for 0..#{size - 1}"
21
+ end
22
+
23
+ (0...size).to_a[range]
24
+ else
25
+ range.to_a
26
+ end
27
+ end
28
+
29
+ def out_of_range?(indeces)
30
+ indeces.max >= size || indeces.min < -size
31
+ end
32
+
33
+ def integers?(enum)
34
+ enum.all?(Integer)
35
+ end
36
+
37
+ def sym_or_str?(enum)
38
+ enum.all? { |e| e.is_a?(Symbol) || e.is_a?(String) }
39
+ end
40
+
41
+ def booleans?(enum)
42
+ enum.all? { |e| e.is_a?(TrueClass) || e.is_a?(FalseClass) || e.is_a?(NilClass) }
43
+ end
44
+
45
+ def create_dataframe_from_vector(key, vector)
46
+ DataFrame.new(key => vector.data)
47
+ end
48
+
49
+ def select_obs_by_boolean(array)
50
+ DataFrame.new(@table.filter(array))
51
+ end
52
+
53
+ def select_obs_by_indeces(indeces)
54
+ out_of_range?(indeces) && raise(DataFrameArgumentError, "Invalid index: #{indeces} for 0..#{size - 1}")
55
+
56
+ a = indeces.map { |i| @table.slice(i).to_a }
57
+ DataFrame.new(@table.schema, a)
58
+ end
59
+
60
+ def keys_by_booleans(booleans)
61
+ keys.select.with_index { |_, i| booleans[i] }
62
+ end
63
+ end
64
+ end