red_amber 0.2.2 → 0.3.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 (41) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +114 -39
  3. data/CHANGELOG.md +203 -31
  4. data/Gemfile +5 -2
  5. data/README.md +62 -29
  6. data/benchmark/basic.yml +86 -0
  7. data/benchmark/combine.yml +62 -0
  8. data/benchmark/dataframe.yml +62 -0
  9. data/benchmark/drop_nil.yml +15 -3
  10. data/benchmark/group.yml +39 -0
  11. data/benchmark/reshape.yml +31 -0
  12. data/benchmark/{csv_load_penguins.yml → rover/csv_load_penguins.yml} +3 -3
  13. data/benchmark/rover/flights.yml +23 -0
  14. data/benchmark/rover/penguins.yml +23 -0
  15. data/benchmark/rover/planes.yml +23 -0
  16. data/benchmark/rover/weather.yml +23 -0
  17. data/benchmark/vector.yml +60 -0
  18. data/doc/DataFrame.md +335 -53
  19. data/doc/Vector.md +91 -0
  20. data/doc/image/dataframe/join.png +0 -0
  21. data/doc/image/dataframe/set_and_bind.png +0 -0
  22. data/doc/image/dataframe_model.png +0 -0
  23. data/lib/red_amber/data_frame.rb +167 -51
  24. data/lib/red_amber/data_frame_combinable.rb +486 -0
  25. data/lib/red_amber/data_frame_displayable.rb +6 -4
  26. data/lib/red_amber/data_frame_indexable.rb +2 -2
  27. data/lib/red_amber/data_frame_loadsave.rb +4 -1
  28. data/lib/red_amber/data_frame_reshaping.rb +35 -10
  29. data/lib/red_amber/data_frame_selectable.rb +221 -116
  30. data/lib/red_amber/data_frame_variable_operation.rb +146 -82
  31. data/lib/red_amber/group.rb +108 -18
  32. data/lib/red_amber/helper.rb +53 -43
  33. data/lib/red_amber/refinements.rb +199 -0
  34. data/lib/red_amber/vector.rb +56 -46
  35. data/lib/red_amber/vector_functions.rb +23 -83
  36. data/lib/red_amber/vector_selectable.rb +116 -69
  37. data/lib/red_amber/vector_updatable.rb +189 -65
  38. data/lib/red_amber/version.rb +1 -1
  39. data/lib/red_amber.rb +3 -0
  40. data/red_amber.gemspec +4 -3
  41. metadata +24 -10
@@ -3,71 +3,145 @@
3
3
  module RedAmber
4
4
  # mix-ins for the class DataFrame
5
5
  module DataFrameVariableOperation
6
- # pick up some variables to create sub DataFrame
6
+ # Array is refined
7
+ using RefineArray
8
+
9
+ # Pick up variables (columns) to create a new DataFrame
10
+ #
11
+ # @note DataFrame#pick creates a DataFrame with single key.
12
+ # DataFrame#[] creates a Vector if single key is specified.
13
+ #
14
+ # @overload pick(keys)
15
+ # Pick variables by Symbols or Strings.
16
+ #
17
+ # @param keys [Symbol, String, <Symbol, String>]
18
+ # key name(s) of variables to pick.
19
+ # @return [DataFrame]
20
+ # Picked DataFrame.
21
+ #
22
+ # @overload pick(booleans)
23
+ # Pick variables by booleans.
24
+ #
25
+ # @param booleans [<true, false, nil>]
26
+ # boolean array to pick variables at true.
27
+ # @return [DataFrame]
28
+ # Picked DataFrame.
29
+ #
30
+ # @overload pick(indices)
31
+ # Pick variables by column indices.
32
+ #
33
+ # @param indices [Integer, Float, Range<Integer>, Vector, Arrow::Array]
34
+ # numeric array to pick variables by column index.
35
+ # @return [DataFrame]
36
+ # Picked DataFrame.
37
+ #
7
38
  def pick(*args, &block)
8
- picker = args
9
39
  if block
10
- raise DataFrameArgumentError, 'Must not specify both arguments and block.' unless args.empty?
40
+ unless args.empty?
41
+ raise DataFrameArgumentError, 'Must not specify both arguments and block.'
42
+ end
11
43
 
12
- picker = [instance_eval(&block)]
44
+ args = [instance_eval(&block)]
13
45
  end
14
- picker.flatten!
15
- return DataFrame.new if picker.empty? || picker == [nil]
16
-
17
- key_vector = Vector.new(keys)
18
- vec = parse_to_vector(picker, vsize: n_keys)
19
-
20
- ary =
21
- if vec.boolean?
22
- key_vector.filter(*vec).to_a
23
- elsif vec.numeric?
24
- key_vector.take(*vec).to_a
25
- elsif vec.string? || vec.dictionary?
26
- picker
27
- else
28
- raise DataFrameArgumentError, "Invalid argument #{args}"
29
- end
30
46
 
31
- # DataFrame#[] creates a Vector with single key is specified.
32
- # DataFrame#pick creates a DataFrame with single key.
33
- DataFrame.new(@table[ary])
47
+ case args
48
+ in [] | [nil]
49
+ return DataFrame.new
50
+ in [*] if args.symbols?
51
+ return DataFrame.create(@table.select_columns(*args))
52
+ in [*] if args.booleans?
53
+ picker = keys.select_by_booleans(args)
54
+ return DataFrame.create(@table.select_columns(*picker))
55
+ in [(Vector | Arrow::Array | Arrow::ChunkedArray) => a]
56
+ picker = a.to_a
57
+ else
58
+ picker = parse_args(args, n_keys)
59
+ end
60
+
61
+ return DataFrame.new if picker.compact.empty?
62
+
63
+ if picker.booleans?
64
+ picker = keys.select_by_booleans(picker)
65
+ return DataFrame.create(@table.select_columns(*picker))
66
+ end
67
+ picker.compact!
68
+ raise DataFrameArgumentError, "some keys are duplicated: #{args}" if picker.uniq!
69
+
70
+ DataFrame.create(@table.select_columns(*picker))
34
71
  end
35
72
 
36
- # drop some variables to create remainer sub DataFrame
73
+ # Drop some variables (columns) to create a remainer DataFrame
74
+ #
75
+ # @note DataFrame#drop creates a DataFrame even if it is a single column.
76
+ #
77
+ # @overload drop(keys)
78
+ # Drop variables by Symbols or Strings.
79
+ #
80
+ # @param keys [Symbol, String, <Symbol, String>]
81
+ # key name(s) of variables to drop.
82
+ # @return [DataFrame]
83
+ # Remainer DataFrame.
84
+ #
85
+ # @overload drop(booleans)
86
+ # Drop variables by booleans.
87
+ #
88
+ # @param booleans [<true, false, nil>]
89
+ # boolean array of variables to drop at true.
90
+ # @return [DataFrame]
91
+ # Remainer DataFrame.
92
+ #
93
+ # @overload drop(indices)
94
+ # Pick variables by column indices.
95
+ #
96
+ # @param indices [Integer, Float, Range<Integer>, Vector, Arrow::Array]
97
+ # numeric array of variables to drop by column index.
98
+ # @return [DataFrame]
99
+ # Remainer DataFrame.
100
+ #
37
101
  def drop(*args, &block)
38
- dropper = args
39
102
  if block
40
- raise DataFrameArgumentError, 'Must not specify both arguments and block.' unless args.empty?
103
+ unless args.empty?
104
+ raise DataFrameArgumentError, 'Must not specify both arguments and block.'
105
+ end
41
106
 
42
- dropper = [instance_eval(&block)]
107
+ args = [instance_eval(&block)]
43
108
  end
44
- dropper.flatten!
45
-
46
- key_vector = Vector.new(keys)
47
- vec = parse_to_vector(dropper, vsize: n_keys)
48
-
49
- ary =
50
- if vec.boolean?
51
- key_vector.filter(*vec.primitive_invert).each.map(&:to_sym) # Array
52
- elsif vec.numeric?
53
- keys - key_vector.take(*vec).each.map(&:to_sym) # Array
54
- elsif vec.string? || vec.dictionary?
55
- keys - dropper
109
+ return self if args.empty? || empty?
110
+
111
+ picker =
112
+ if args.symbols?
113
+ keys - args
114
+ elsif args.booleans?
115
+ keys.reject_by_booleans(args)
116
+ elsif args.integers?
117
+ keys.reject_by_indices(args)
56
118
  else
57
- raise DataFrameArgumentError, "Invalid argument #{args}"
119
+ dropper = parse_args(args, n_keys)
120
+ if dropper.booleans?
121
+ keys.reject_by_booleans(dropper)
122
+ elsif dropper.symbols?
123
+ keys - dropper
124
+ else
125
+ dropper.compact!
126
+ unless dropper.integers?
127
+ raise DataFrameArgumentError, "Invalid argument #{args}"
128
+ end
129
+
130
+ keys.reject_by_indices(dropper)
131
+ end
58
132
  end
59
133
 
60
- return DataFrame.new if ary.empty?
134
+ return DataFrame.new if picker.empty?
61
135
 
62
- # DataFrame#[] creates a Vector with single key is specified.
63
- # DataFrame#drop creates a DataFrame with single key.
64
- DataFrame.new(@table[ary])
136
+ DataFrame.create(@table.select_columns(*picker))
65
137
  end
66
138
 
67
139
  # rename variables to create a new DataFrame
68
140
  def rename(*renamer, &block)
69
141
  if block
70
- raise DataFrameArgumentError, 'Must not specify both arguments and a block' unless renamer.empty?
142
+ unless renamer.empty?
143
+ raise DataFrameArgumentError, 'Must not specify both arguments and a block'
144
+ end
71
145
 
72
146
  renamer = [instance_eval(&block)]
73
147
  end
@@ -90,35 +164,23 @@ module RedAmber
90
164
 
91
165
  # assign variables to create a new DataFrame
92
166
  def assign(*assigner, &block)
93
- appender, fields, arrays = assign_update(*assigner, &block)
94
- return self if appender.is_a?(DataFrame)
95
-
96
- append_to_fields_and_arrays(appender, fields, arrays, append_to_left: false) unless appender.empty?
97
-
98
- DataFrame.new(Arrow::Table.new(Arrow::Schema.new(fields), arrays))
167
+ assign_update(*assigner, append_to_left: false, &block)
99
168
  end
100
169
 
101
170
  def assign_left(*assigner, &block)
102
- appender, fields, arrays = assign_update(*assigner, &block)
103
- return self if appender.is_a?(DataFrame)
104
-
105
- append_to_fields_and_arrays(appender, fields, arrays, append_to_left: true) unless appender.empty?
106
-
107
- DataFrame.new(Arrow::Table.new(Arrow::Schema.new(fields), arrays))
171
+ assign_update(*assigner, append_to_left: true, &block)
108
172
  end
109
173
 
110
174
  private
111
175
 
112
- def assign_update(*assigner, &block)
176
+ def assign_update(*assigner, append_to_left: false, &block)
113
177
  if block
114
178
  assigner_from_block = instance_eval(&block)
115
179
  assigner =
116
- if assigner.empty?
117
- # block only
180
+ case assigner_from_block
181
+ in _ if assigner.empty? # block only
118
182
  [assigner_from_block]
119
- # If Ruby >= 3.0, one line pattern match can be used
120
- # assigner_from_block in [Array, *]
121
- elsif multiple_assigner?(assigner_from_block)
183
+ in [Vector, *] | [Array, *] | [Arrow::Array, *]
122
184
  assigner.zip(assigner_from_block)
123
185
  else
124
186
  assigner.zip([assigner_from_block])
@@ -128,10 +190,10 @@ module RedAmber
128
190
  case assigner
129
191
  in [] | [nil] | [{}] | [[]]
130
192
  return self
131
- in [Hash => key_array_pairs]
132
- # noop
133
193
  in [(Symbol | String) => key, (Vector | Array | Arrow::Array) => array]
134
194
  key_array_pairs = { key => array }
195
+ in [Hash => key_array_pairs]
196
+ # noop
135
197
  in [Array => array_in_array]
136
198
  key_array_pairs = try_convert_to_hash(array_in_array)
137
199
  in [Array, *] => array_in_array1
@@ -151,20 +213,27 @@ module RedAmber
151
213
  appender[key] = array
152
214
  end
153
215
  end
154
- [appender, *update_fields_and_arrays(updater)]
216
+ fields, arrays = *update_fields_and_arrays(updater)
217
+ return self if appender.is_a?(DataFrame)
218
+
219
+ unless appender.empty?
220
+ append_to_fields_and_arrays(appender, fields, arrays, append_to_left)
221
+ end
222
+
223
+ DataFrame.create(Arrow::Table.new(Arrow::Schema.new(fields), arrays))
155
224
  end
156
225
 
157
226
  def try_convert_to_hash(array)
158
227
  array.to_h
159
228
  rescue TypeError
160
229
  [array].to_h
161
- rescue TypeError # rubocop:disable Lint/DuplicateRescueException
162
- raise DataFrameArgumentError, "Invalid argument in Array #{array}"
163
230
  end
164
231
 
165
232
  def rename_by_hash(key_pairs)
166
233
  not_existing_keys = key_pairs.keys - keys
167
- raise DataFrameArgumentError, "Not existing: #{not_existing_keys}" unless not_existing_keys.empty?
234
+ unless not_existing_keys.empty?
235
+ raise DataFrameArgumentError, "Not existing: #{not_existing_keys}"
236
+ end
168
237
 
169
238
  fields =
170
239
  keys.map do |key|
@@ -175,7 +244,7 @@ module RedAmber
175
244
  @table.schema[key]
176
245
  end
177
246
  end
178
- DataFrame.new(Arrow::Table.new(Arrow::Schema.new(fields), @table.columns))
247
+ DataFrame.create(Arrow::Table.new(Arrow::Schema.new(fields), @table.columns))
179
248
  end
180
249
 
181
250
  def update_fields_and_arrays(updater)
@@ -185,7 +254,9 @@ module RedAmber
185
254
  data = updater[key]
186
255
  next unless data
187
256
 
188
- raise DataFrameArgumentError, "Data size mismatch (#{data.size} != #{size})" if data.nil? || data.size != size
257
+ if data.size != size
258
+ raise DataFrameArgumentError, "Data size mismatch (#{data.size} != #{size})"
259
+ end
189
260
 
190
261
  a = Arrow::Array.new(data.is_a?(Vector) ? data.to_a : data)
191
262
  fields[i] = Arrow::Field.new(key, a.value_data_type)
@@ -194,10 +265,12 @@ module RedAmber
194
265
  [fields, arrays]
195
266
  end
196
267
 
197
- def append_to_fields_and_arrays(appender, fields, arrays, append_to_left: false)
268
+ def append_to_fields_and_arrays(appender, fields, arrays, append_to_left)
198
269
  enum = append_to_left ? appender.reverse_each : appender.each
199
270
  enum.each do |key, data|
200
- raise DataFrameArgumentError, "Data size mismatch (#{data.size} != #{size})" if data.size != size
271
+ if data.size != size
272
+ raise DataFrameArgumentError, "Data size mismatch (#{data.size} != #{size})"
273
+ end
201
274
 
202
275
  a = Arrow::Array.new(data.is_a?(Vector) ? data.to_a : data)
203
276
 
@@ -210,14 +283,5 @@ module RedAmber
210
283
  end
211
284
  end
212
285
  end
213
-
214
- def multiple_assigner?(assigner)
215
- case assigner
216
- in [Vector, *] | [Array, *] | [Arrow::Array, *]
217
- true
218
- else
219
- false
220
- end
221
- end
222
286
  end
223
287
  end
@@ -3,35 +3,88 @@
3
3
  module RedAmber
4
4
  # group class
5
5
  class Group
6
+ include Enumerable # This feature is experimental
7
+
8
+ using RefineArrowTable
9
+
6
10
  # Creates a new Group object.
7
11
  #
8
12
  # @param dataframe [DataFrame] dataframe to be grouped.
9
13
  # @param group_keys [Array<>] keys for grouping.
10
14
  def initialize(dataframe, *group_keys)
11
15
  @dataframe = dataframe
12
- @table = @dataframe.table
13
16
  @group_keys = group_keys.flatten
14
17
 
15
- raise GroupArgumentError, 'group_keys is empty.' if @group_keys.empty?
18
+ raise GroupArgumentError, 'group_keys are empty.' if @group_keys.empty?
16
19
 
17
20
  d = @group_keys - @dataframe.keys
18
21
  raise GroupArgumentError, "#{d} is not a key of\n #{@dataframe}." unless d.empty?
19
22
 
20
- @group = @table.group(*@group_keys)
23
+ @group = @dataframe.table.group(*@group_keys)
21
24
  end
22
25
 
26
+ attr_reader :dataframe, :group_keys
27
+
23
28
  functions = %i[count sum product mean min max stddev variance]
24
29
  functions.each do |function|
25
30
  define_method(function) do |*summary_keys|
26
- by(function, summary_keys)
31
+ summary_keys = Array(summary_keys).flatten
32
+ d = summary_keys - @dataframe.keys
33
+ unless summary_keys.empty? || d.empty?
34
+ raise GroupArgumentError, "#{d} is not a key of\n #{@dataframe}."
35
+ end
36
+
37
+ table = @group.aggregate(*build_aggregation_keys("hash_#{function}",
38
+ summary_keys))
39
+ g = @group_keys.map(&:to_s)
40
+ DataFrame.new(table[g + (table.keys - g)])
27
41
  end
28
42
  end
29
43
 
30
- def inspect
31
- tallys = @dataframe.pick(@group_keys).vectors.map.with_object({}) do |v, h|
32
- h[v.key] = v.tally
44
+ alias_method :__count, :count
45
+ private :__count
46
+
47
+ def count(*summary_keys)
48
+ df = __count(summary_keys)
49
+ # if counts are the same (and do not include NaN or nil), aggregate count columns.
50
+ if df.pick(@group_keys.size..).to_h.values.uniq.size == 1
51
+ df.pick(0..@group_keys.size).rename { [keys[-1], :count] }
52
+ else
53
+ df
33
54
  end
34
- "#<#{self.class}:#{format('0x%016x', object_id)}\n#{tallys}>"
55
+ end
56
+
57
+ def filters
58
+ @filters ||= begin
59
+ first, *others = @group_keys.map do |key|
60
+ vector = @dataframe[key]
61
+ vector.uniq.each.map { |u| u.nil? ? vector.is_nil : vector == u }
62
+ end
63
+
64
+ if others.empty?
65
+ first.select(&:any?)
66
+ else
67
+ first.product(*others).map { |a| a.reduce(&:&) }.select(&:any?)
68
+ end
69
+ end
70
+ end
71
+
72
+ def each
73
+ filters
74
+ return enum_for(:each) unless block_given?
75
+
76
+ @filters.each do |filter|
77
+ yield @dataframe[filter]
78
+ end
79
+ @filters.size
80
+ end
81
+
82
+ def group_count
83
+ DataFrame.create(add_columns_to_table(base_table, [:group_count], [group_counts]))
84
+ end
85
+
86
+ def inspect
87
+ "#<#{self.class} : #{format('0x%016x', object_id)}>\n#{group_count}"
35
88
  end
36
89
 
37
90
  def summarize(&block)
@@ -46,20 +99,57 @@ module RedAmber
46
99
  end
47
100
  end
48
101
 
102
+ # experimental
103
+ def agg_sum(*summary_keys)
104
+ call_aggregating_function(:sum, summary_keys, _options = nil)
105
+ end
106
+
49
107
  private
50
108
 
51
- def by(func, summary_keys)
52
- summary_keys = Array(summary_keys).flatten
53
- d = summary_keys - @dataframe.keys
54
- raise GroupArgumentError, "#{d} is not a key of\n #{@dataframe}." unless summary_keys.empty? || d.empty?
109
+ def build_aggregation_keys(function_name, summary_keys)
110
+ if summary_keys.empty?
111
+ [function_name]
112
+ else
113
+ summary_keys.map { |key| "#{function_name}(#{key})" }
114
+ end
115
+ end
55
116
 
56
- df = RedAmber::DataFrame.new(@group.send(func, *summary_keys))
57
- df = df.pick(@group_keys, df.keys - @group_keys)
58
- # if counts are the same (and do not include NaN or nil), aggregate count columns.
59
- if func == :count && df.pick(@group_keys.size..).to_h.values.uniq.size == 1
60
- df = df.pick(0..@group_keys.size).rename { [keys[-1], :count] }
117
+ # @note `@group_counts.sum == @dataframe.size``
118
+ def group_counts
119
+ @group_counts ||= filters.map(&:sum)
120
+ end
121
+
122
+ def base_table
123
+ @base_table ||= begin
124
+ indexes = filters.map { |filter| filter.index(true) }
125
+ @dataframe.table[@group_keys].take(indexes)
126
+ end
127
+ end
128
+
129
+ def add_columns_to_table(table, keys, data_arrays)
130
+ fields = table.schema.fields
131
+ arrays = table.columns.map(&:data)
132
+
133
+ keys.zip(data_arrays).each do |key, array|
134
+ data = Arrow::ChunkedArray.new([array])
135
+ fields << Arrow::Field.new(key, data.value_data_type)
136
+ arrays << data
137
+ end
138
+
139
+ Arrow::Table.new(Arrow::Schema.new(fields), arrays)
140
+ end
141
+
142
+ # Call Vector aggregating function and return an array of arrays:
143
+ # [keys, data_arrays]
144
+ # (Experimental feature)
145
+ def call_aggregating_function(func, summary_keys, _options)
146
+ summary_keys.each.with_object([[], []]) do |key, (keys, arrays)|
147
+ vector = @dataframe[key]
148
+ arrays << filters.map { |filter| vector.filter(filter).send(func) }
149
+ keys << "#{func}(#{key})".to_sym
150
+ rescue Arrow::Error::NotImplemented
151
+ # next
61
152
  end
62
- df
63
153
  end
64
154
  end
65
155
  end
@@ -5,58 +5,68 @@ module RedAmber
5
5
  module Helper
6
6
  private
7
7
 
8
+ # If num is larger than 1 return 's' to be plural.
9
+ #
10
+ # @param num [Numeric] some number.
11
+ # @return ['s', ''] return 's' if num is larger than 1.
12
+ # Otherwise return ''.
8
13
  def pl(num)
9
14
  num > 1 ? 's' : ''
10
15
  end
11
16
 
12
- def out_of_range?(indeces)
13
- indeces.max >= size || indeces.min < -size
14
- end
15
-
16
- def integers?(enum)
17
- enum.all?(Integer)
18
- end
19
-
20
- def booleans?(enum)
21
- enum.all? { |e| e.is_a?(TrueClass) || e.is_a?(FalseClass) || e.is_a?(NilClass) }
22
- end
23
-
24
- def create_dataframe_from_vector(key, vector)
25
- DataFrame.new(key => vector.data)
26
- end
27
-
28
- def parse_to_vector(args, vsize: size)
29
- a = args.reduce([]) do |accum, elem|
30
- accum.concat(normalize_element(elem, vsize: vsize))
17
+ # Parse the argments in an Array
18
+ # and returns a parsed Array.
19
+ #
20
+ # @param args
21
+ # [<Integer, Symbol, true, false, nil, Array, Range, Enumerator, String, Float>]
22
+ # arguments.
23
+ # @param array_size [Integer] size of target Array to use in a endless Range.
24
+ # @return [<Integer, Symbol, true, false, nil>] parsed flat Array.
25
+ # @note This method is recursively called to parse.
26
+ def parse_args(args, array_size)
27
+ args.flat_map do |elem|
28
+ case elem
29
+ when Integer, Symbol, NilClass, TrueClass, FalseClass
30
+ elem
31
+ when Array
32
+ parse_args(elem, array_size)
33
+ when Range
34
+ parse_range(elem, array_size)
35
+ when Enumerator
36
+ parse_args(Array(elem), array_size)
37
+ when String
38
+ elem.to_sym
39
+ when Float
40
+ elem.floor.to_i
41
+ else
42
+ Array(elem)
43
+ end
31
44
  end
32
- Vector.new(a)
33
45
  end
34
46
 
35
- def normalize_element(elem, vsize: size)
36
- case elem
37
- when NilClass
38
- [nil]
39
- when Range
40
- bg = elem.begin
41
- en = elem.end
42
- if [bg, en].any?(Integer)
43
- bg += vsize if bg&.negative?
44
- en += vsize if en&.negative?
45
- en -= 1 if en.is_a?(Integer) && elem.exclude_end?
46
- if bg&.negative? || (en && en >= vsize)
47
- raise DataFrameArgumentError, "Index out of range: #{elem} for 0..#{vsize - 1}"
48
- end
49
-
50
- Array(0...vsize)[elem]
51
- elsif bg.nil? && en.nil?
52
- Array(0...vsize)
53
- else
54
- Array[elem]
47
+ # Parse a Range to an Array
48
+ #
49
+ # @param range [Range] Range to parse.
50
+ # @param array_size [Integer] size of target Array to use in a endless Range.
51
+ # @return [Array<Integer, Symbol, String>] parsed Array.
52
+ def parse_range(range, array_size)
53
+ bg = range.begin
54
+ en = range.end
55
+ if [bg, en].any?(Integer)
56
+ bg += array_size if bg&.negative?
57
+ en += array_size if en&.negative?
58
+ en -= 1 if en.is_a?(Integer) && range.exclude_end?
59
+ if bg&.negative? || (en && en >= array_size)
60
+ raise IndexError, "Index out of range: #{range} for 0..#{array_size - 1}"
55
61
  end
56
- when Enumerator
57
- elem.to_a
62
+
63
+ Array(0...array_size)[range]
64
+ elsif bg.nil?
65
+ raise DataFrameArgumentError, "Cannot use beginless Range: #{range}"
66
+ elsif en.nil?
67
+ raise DataFrameArgumentError, "Cannot use endless Range: #{range}"
58
68
  else
59
- Array[elem]
69
+ Array(range)
60
70
  end
61
71
  end
62
72
  end