red_amber 0.2.2 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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