daru 0.1.3.1 → 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.
Files changed (92) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/.rspec +2 -1
  4. data/.rspec_formatter.rb +33 -0
  5. data/.rubocop.yml +26 -2
  6. data/History.md +38 -0
  7. data/README.md +22 -13
  8. data/Rakefile +50 -2
  9. data/benchmarks/csv_reading.rb +22 -0
  10. data/daru.gemspec +9 -2
  11. data/lib/daru.rb +36 -4
  12. data/lib/daru/accessors/array_wrapper.rb +6 -1
  13. data/lib/daru/accessors/dataframe_by_row.rb +10 -2
  14. data/lib/daru/accessors/gsl_wrapper.rb +1 -3
  15. data/lib/daru/accessors/nmatrix_wrapper.rb +9 -0
  16. data/lib/daru/category.rb +935 -0
  17. data/lib/daru/core/group_by.rb +29 -38
  18. data/lib/daru/core/merge.rb +186 -145
  19. data/lib/daru/core/query.rb +22 -11
  20. data/lib/daru/dataframe.rb +976 -885
  21. data/lib/daru/date_time/index.rb +166 -166
  22. data/lib/daru/date_time/offsets.rb +66 -77
  23. data/lib/daru/formatters/table.rb +54 -0
  24. data/lib/daru/helpers/array.rb +40 -0
  25. data/lib/daru/index.rb +476 -73
  26. data/lib/daru/io/io.rb +66 -45
  27. data/lib/daru/io/sql_data_source.rb +33 -62
  28. data/lib/daru/iruby/helpers.rb +38 -0
  29. data/lib/daru/iruby/templates/dataframe.html.erb +52 -0
  30. data/lib/daru/iruby/templates/dataframe_mi.html.erb +58 -0
  31. data/lib/daru/iruby/templates/multi_index.html.erb +12 -0
  32. data/lib/daru/iruby/templates/vector.html.erb +27 -0
  33. data/lib/daru/iruby/templates/vector_mi.html.erb +36 -0
  34. data/lib/daru/maths/arithmetic/dataframe.rb +16 -18
  35. data/lib/daru/maths/arithmetic/vector.rb +4 -6
  36. data/lib/daru/maths/statistics/dataframe.rb +8 -15
  37. data/lib/daru/maths/statistics/vector.rb +120 -98
  38. data/lib/daru/monkeys.rb +12 -40
  39. data/lib/daru/plotting/gruff.rb +3 -0
  40. data/lib/daru/plotting/gruff/category.rb +49 -0
  41. data/lib/daru/plotting/gruff/dataframe.rb +91 -0
  42. data/lib/daru/plotting/gruff/vector.rb +57 -0
  43. data/lib/daru/plotting/nyaplot.rb +3 -0
  44. data/lib/daru/plotting/nyaplot/category.rb +34 -0
  45. data/lib/daru/plotting/nyaplot/dataframe.rb +187 -0
  46. data/lib/daru/plotting/nyaplot/vector.rb +46 -0
  47. data/lib/daru/vector.rb +694 -421
  48. data/lib/daru/version.rb +1 -1
  49. data/profile/_base.rb +23 -0
  50. data/profile/df_to_a.rb +10 -0
  51. data/profile/filter.rb +13 -0
  52. data/profile/joining.rb +13 -0
  53. data/profile/sorting.rb +12 -0
  54. data/profile/vector_each_with_index.rb +9 -0
  55. data/spec/accessors/wrappers_spec.rb +2 -4
  56. data/spec/categorical_spec.rb +1734 -0
  57. data/spec/core/group_by_spec.rb +52 -2
  58. data/spec/core/merge_spec.rb +63 -2
  59. data/spec/core/query_spec.rb +236 -80
  60. data/spec/dataframe_spec.rb +1373 -79
  61. data/spec/date_time/data_spec.rb +3 -5
  62. data/spec/date_time/index_spec.rb +154 -17
  63. data/spec/date_time/offsets_spec.rb +3 -4
  64. data/spec/fixtures/empties.dat +2 -0
  65. data/spec/fixtures/strings.dat +2 -0
  66. data/spec/formatters/table_formatter_spec.rb +99 -0
  67. data/spec/helpers_spec.rb +8 -0
  68. data/spec/index/categorical_index_spec.rb +168 -0
  69. data/spec/index/index_spec.rb +283 -0
  70. data/spec/index/multi_index_spec.rb +570 -0
  71. data/spec/io/io_spec.rb +31 -4
  72. data/spec/io/sql_data_source_spec.rb +0 -1
  73. data/spec/iruby/dataframe_spec.rb +172 -0
  74. data/spec/iruby/helpers_spec.rb +49 -0
  75. data/spec/iruby/multi_index_spec.rb +37 -0
  76. data/spec/iruby/vector_spec.rb +107 -0
  77. data/spec/math/arithmetic/dataframe_spec.rb +71 -13
  78. data/spec/math/arithmetic/vector_spec.rb +8 -10
  79. data/spec/math/statistics/dataframe_spec.rb +3 -5
  80. data/spec/math/statistics/vector_spec.rb +45 -55
  81. data/spec/monkeys_spec.rb +32 -9
  82. data/spec/plotting/dataframe_spec.rb +386 -0
  83. data/spec/plotting/vector_spec.rb +230 -0
  84. data/spec/shared/vector_display_spec.rb +215 -0
  85. data/spec/spec_helper.rb +23 -0
  86. data/spec/vector_spec.rb +905 -138
  87. metadata +143 -11
  88. data/.rubocop_todo.yml +0 -44
  89. data/lib/daru/plotting/dataframe.rb +0 -104
  90. data/lib/daru/plotting/vector.rb +0 -38
  91. data/spec/daru_spec.rb +0 -58
  92. data/spec/index_spec.rb +0 -375
@@ -24,55 +24,44 @@ module Daru
24
24
  Rational(1,86_400) => Daru::Offsets::Second
25
25
  }.freeze
26
26
 
27
+ DOW_REGEXP = Regexp.new(Daru::DAYS_OF_WEEK.keys.join('|'))
28
+ FREQUENCY_PATTERN = /^
29
+ (?<multiplier>[0-9]+)?
30
+ (
31
+ (?<offset>MONTH|YEAR|S|H|MB|ME|M|D|YB|YE) |
32
+ (?<offset>W)(-(?<weekday>#{DOW_REGEXP}))?
33
+ )$/x
34
+
27
35
  # Generates a Daru::DateOffset object for generic offsets or one of the
28
36
  # specialized classed within Daru::Offsets depending on the 'frequency'
29
37
  # string.
30
38
  def offset_from_frequency frequency
31
- frequency = 'D' if frequency.nil?
32
39
  return frequency if frequency.is_a?(Daru::DateOffset)
40
+ frequency ||= 'D'
33
41
 
34
- matched = /([0-9]*)(MONTH|YEAR|S|H|MB|ME|M|D|W|YB|YE)/.match(frequency)
35
- raise ArgumentError,
36
- "Invalid frequency string #{frequency}" if matched.nil?
37
-
38
- n = matched[1] == '' ? 1 : matched[1].to_i
39
- offset_string = matched[2]
40
- offset_klass = OFFSETS_HASH[offset_string]
42
+ matched = FREQUENCY_PATTERN.match(frequency) or
43
+ raise ArgumentError, "Invalid frequency string #{frequency}"
41
44
 
42
- raise ArgumentError,
43
- "Cannont interpret offset #{offset_string}" if offset_klass.nil?
44
-
45
- if offset_string =~ /W/
46
- day = Regexp.new(Daru::DAYS_OF_WEEK.keys.join('|')).match(frequency).to_s
47
- return offset_klass.new(n, weekday: Daru::DAYS_OF_WEEK[day])
48
- end
45
+ n = (matched[:multiplier] || 1).to_i
46
+ offset_string = matched[:offset]
47
+ offset_klass = OFFSETS_HASH[offset_string] or
48
+ raise ArgumentError, "Cannont interpret offset #{offset_string}"
49
49
 
50
- offset_klass.new(n)
51
- end
52
-
53
- def start_date start
54
- if start.is_a?(String)
55
- date_time_from(start, determine_date_precision_of(start))
50
+ if offset_string == 'W'
51
+ offset_klass.new(n, weekday: Daru::DAYS_OF_WEEK[matched[:weekday]])
56
52
  else
57
- start
53
+ offset_klass.new(n)
58
54
  end
59
55
  end
60
56
 
61
- def end_date en
62
- if en.is_a?(String)
63
- date_time_from(en, determine_date_precision_of(en))
64
- else
65
- en
66
- end
57
+ def coerce_date date
58
+ return date unless date.is_a?(String)
59
+ date_time_from(date, determine_date_precision_of(date))
67
60
  end
68
61
 
69
62
  def begin_from_offset? offset, start
70
- if offset.is_a?(Daru::Offsets::Tick) ||
71
- (offset.respond_to?(:on_offset?) && offset.on_offset?(start))
72
- true
73
- else
74
- false
75
- end
63
+ offset.is_a?(Daru::Offsets::Tick) ||
64
+ offset.respond_to?(:on_offset?) && offset.on_offset?(start)
76
65
  end
77
66
 
78
67
  def generate_data start, en, offset, periods
@@ -103,17 +92,10 @@ module Daru
103
92
  end
104
93
 
105
94
  def infer_offset data
106
- possible_freq = data[1] - data[0]
107
- inferred = true
108
- data.each_cons(2) do |d|
109
- if d[1] - d[0] != possible_freq
110
- inferred = false
111
- break
112
- end
113
- end
95
+ diffs = data.each_cons(2).map { |d1, d2| d2 - d1 }
114
96
 
115
- if inferred
116
- TIME_INTERVALS[possible_freq].new
97
+ if diffs.uniq.count == 1
98
+ TIME_INTERVALS[diffs.first].new
117
99
  else
118
100
  nil
119
101
  end
@@ -146,26 +128,17 @@ module Daru
146
128
  end
147
129
  end
148
130
 
131
+ DATE_PRECISION_REGEXP = /^(\d\d\d\d)(-\d{1,2}(-\d{1,2}( \d{1,2}(:\d{1,2}(:\d{1,2})?)?)?)?)?$/
132
+ DATE_PRECISIONS = [nil, :year, :month, :day, :hour, :min, :sec].freeze
133
+
149
134
  def determine_date_precision_of date_string
150
- case date_string
151
- when /\d\d\d\d\-\d?\d\-\d?\d \d?\d:\d?\d:\d?\d/
152
- :sec
153
- when /\d\d\d\d\-\d?\d\-\d?\d \d?\d:\d?\d/
154
- :min
155
- when /\d\d\d\d\-\d?\d\-\d?\d \d?\d/
156
- :hour
157
- when /\d\d\d\d\-\d?\d\-\d?\d/
158
- :day
159
- when /\d\d\d\d\-\d?\d/
160
- :month
161
- when /\d\d\d\d/
162
- :year
163
- else
135
+ components = date_string.scan(DATE_PRECISION_REGEXP).flatten.compact
136
+ DATE_PRECISIONS[components.count] or
164
137
  raise ArgumentError, "Unacceptable date string #{date_string}"
165
- end
166
138
  end
167
139
 
168
- def generate_bounds date_time, date_precision
140
+ def generate_bounds date_time, date_precision # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
141
+ # FIXME: about that ^ disable: I'd like to use my zverok/time_boots here, which will simplify things
169
142
  case date_precision
170
143
  when :year
171
144
  [
@@ -209,21 +182,46 @@ module Daru
209
182
  end
210
183
 
211
184
  def key_out_of_bounds? key, data
185
+ dates = data.transpose.first
186
+
212
187
  precision = determine_date_precision_of key
213
188
  date_time = date_time_from key, precision
189
+
190
+ # FIXME: I'm pretty suspicious about logic here:
191
+ # why only year & month? - zverok 2016-05-16
192
+
214
193
  case precision
215
194
  when :year
216
- date_time.year < data[0][0].year || date_time.year > data[-1][0].year
195
+ year_out_of_bounds?(date_time, dates)
217
196
  when :month
218
- (date_time.year < data[0][0].year && date_time.month < data[0][0].month) ||
219
- (date_time.year > data[-1][0].year and date_time.month > data[-1][0].month)
197
+ year_month_out_of_bounds?(date_time, dates)
220
198
  end
221
199
  end
200
+
201
+ private
202
+
203
+ def year_out_of_bounds?(date_time, dates)
204
+ date_time.year < dates.first.year || date_time.year > dates.last.year
205
+ end
206
+
207
+ def year_month_out_of_bounds?(date_time, dates)
208
+ date_time.year < dates.first.year && date_time.month < dates.first.month ||
209
+ date_time.year > dates.last.year && date_time.month > dates.last.month
210
+ end
222
211
  end
223
212
  end
224
213
 
225
214
  class DateTimeIndex < Index
226
215
  include Enumerable
216
+ Helper = DateTimeIndexHelper
217
+
218
+ def self.try_create(source)
219
+ if source && ArrayHelper.array_of?(source, DateTime)
220
+ new(source, freq: :infer)
221
+ else
222
+ nil
223
+ end
224
+ end
227
225
 
228
226
  def each(&block)
229
227
  to_a.each(&block)
@@ -255,24 +253,18 @@ module Daru
255
253
  # DateTime.new(2012,4,8), DateTime.new(2012,4,9), DateTime.new(2012,4,10),
256
254
  # DateTime.new(2012,4,11), DateTime.new(2012,4,12)], freq: :infer)
257
255
  # #=>#<DateTimeIndex:84198340 offset=D periods=8 data=[2012-04-05T00:00:00+00:00...2012-04-12T00:00:00+00:00]>
258
- def initialize *args
259
- helper = DateTimeIndexHelper
260
-
261
- data = args[0]
262
- opts = args[1] || {freq: nil}
263
-
264
- helper.possibly_convert_to_date_time data
256
+ def initialize data, opts={freq: nil}
257
+ Helper.possibly_convert_to_date_time data
265
258
 
266
259
  @offset =
267
260
  case opts[:freq]
268
- when :infer then helper.infer_offset(data)
261
+ when :infer then Helper.infer_offset(data)
269
262
  when nil then nil
270
- else helper.offset_from_frequency(opts[:freq])
263
+ else Helper.offset_from_frequency(opts[:freq])
271
264
  end
272
265
 
273
266
  @frequency = @offset ? @offset.freq_string : nil
274
- @data = data.zip(Array.new(data.size) { |i| i })
275
- @data.sort_by! { |d| d[0] } if @offset.nil?
267
+ @data = data.each_with_index.to_a.sort_by(&:first)
276
268
 
277
269
  @periods = data.size
278
270
  end
@@ -341,13 +333,11 @@ module Daru
341
333
  # :start => '2012-5-2', :periods => 50, :freq => 'ME')
342
334
  # #=> #<DateTimeIndex:83549940 offset=ME periods=50 data=[2012-05-31T00:00:00+00:00...2016-06-30T00:00:00+00:00]>
343
335
  def self.date_range opts={}
344
- helper = DateTimeIndexHelper
345
-
346
- start = helper.start_date opts[:start]
347
- en = helper.end_date opts[:end]
348
- helper.verify_start_and_end(start, en) unless en.nil?
349
- offset = helper.offset_from_frequency opts[:freq]
350
- data = helper.generate_data start, en, offset, opts[:periods]
336
+ start = Helper.coerce_date opts[:start]
337
+ en = Helper.coerce_date opts[:end]
338
+ Helper.verify_start_and_end(start, en) unless en.nil?
339
+ offset = Helper.offset_from_frequency opts[:freq]
340
+ data = Helper.generate_data start, en, offset, opts[:periods]
351
341
 
352
342
  DateTimeIndex.new(data, freq: offset)
353
343
  end
@@ -357,33 +347,41 @@ module Daru
357
347
  # @param [String, DateTime] Specify a date partially (as a String) or
358
348
  # completely to retrieve.
359
349
  def [] *key
360
- helper = DateTimeIndexHelper
361
-
362
350
  return slice(*key) if key.size != 1
363
351
  key = key[0]
364
- return key if key.is_a?(Numeric)
365
-
366
- if key.is_a?(Range)
367
- first = key.first
368
- last = key.last
369
- return slice(first, last) if
370
- first.is_a?(Fixnum) && last.is_a?(Fixnum)
371
-
372
- raise ArgumentError, "Keys #{first} and #{last} are out of bounds" if
373
- helper.key_out_of_bounds?(first, @data) && helper.key_out_of_bounds?(last, @data)
374
-
375
- slice_begin = helper.find_date_string_bounds(first)[0]
376
- slice_end = helper.find_date_string_bounds(last)[1]
377
- elsif key.is_a?(DateTime)
378
- return helper.find_index_of_date(@data, key)
352
+ case key
353
+ when Numeric
354
+ key
355
+ when DateTime
356
+ Helper.find_index_of_date(@data, key)
357
+ when Range
358
+ # FIXME: get_by_range is suspiciously close to just #slice,
359
+ # but one of specs fails when replacing it with just slice
360
+ get_by_range(key.first, key.last)
379
361
  else
380
362
  raise ArgumentError, "Key #{key} is out of bounds" if
381
- helper.key_out_of_bounds?(key, @data)
363
+ Helper.key_out_of_bounds?(key, @data)
382
364
 
383
- slice_begin, slice_end = helper.find_date_string_bounds key
365
+ slice(*Helper.find_date_string_bounds(key))
384
366
  end
367
+ end
368
+
369
+ def pos *args
370
+ # to filled
371
+ out = self[*args]
372
+ return out if out.is_a? Numeric
373
+ out.map { |date| self[date] }
374
+ end
375
+
376
+ def subset *args
377
+ self[*args]
378
+ end
385
379
 
386
- slice slice_begin, slice_end
380
+ def valid? *args
381
+ self[*args]
382
+ true
383
+ rescue IndexError
384
+ false
387
385
  end
388
386
 
389
387
  # Retrive a slice of the index by specifying first and last members of the slice.
@@ -391,51 +389,24 @@ module Daru
391
389
  # @param [String, DateTime] first Start of the slice as a string or DateTime.
392
390
  # @param [String, DateTime] last End of the slice as a string or DateTime.
393
391
  def slice first, last
394
- helper = DateTimeIndexHelper
395
-
396
- if first.is_a?(String) && last.is_a?(String)
397
- self[first..last]
398
- elsif first.is_a?(Fixnum) && last.is_a?(Fixnum)
392
+ if first.is_a?(Fixnum) && last.is_a?(Fixnum)
399
393
  DateTimeIndex.new(to_a[first..last], freq: @offset)
400
394
  else
401
- first_dt =
402
- if first.is_a?(String)
403
- helper.find_date_string_bounds(first)[0]
404
- else
405
- first
406
- end
407
-
408
- last_dt =
409
- if last.is_a?(String)
410
- helper.find_date_string_bounds(last)[1]
411
- else
412
- last
413
- end
414
-
415
- start = @data.bsearch { |d| d[0] >= first_dt }
416
- after_en = @data.bsearch { |d| d[0] > last_dt }
417
-
418
- result =
419
- if @offset
420
- en = after_en ? @data[after_en[1] - 1] : @data.last
421
- return start[1] if start == en
422
- DateTimeIndex.date_range start: start[0], end: en[0], freq: @offset
423
- else
424
- st = @data.index(start)
425
- en = after_en ? @data.index(after_en) - 1 : helper.last_date(@data)[1]
426
- return start[1] if st == en
427
- DateTimeIndex.new(@data[st..en].transpose[0])
428
- end
395
+ first = Helper.find_date_string_bounds(first)[0] if first.is_a?(String)
396
+ last = Helper.find_date_string_bounds(last)[1] if last.is_a?(String)
429
397
 
430
- result
398
+ slice_between_dates first, last
431
399
  end
432
400
  end
433
401
 
434
402
  # Return the DateTimeIndex as an Array of DateTime objects.
435
403
  # @return [Array<DateTime>] Array of containing DateTimes.
436
404
  def to_a
437
- return @data.sort_by { |d| d[1] }.transpose[0] unless @offset
438
- @data.transpose[0]
405
+ if @offset
406
+ @data
407
+ else
408
+ @data.sort_by(&:last)
409
+ end.transpose.first
439
410
  end
440
411
 
441
412
  # Size of index.
@@ -448,11 +419,9 @@ module Daru
448
419
  end
449
420
 
450
421
  def inspect
451
- string = '#<DateTimeIndex:' + object_id.to_s + ' offset=' +
452
- (@offset ? @offset.freq_string : 'nil') + ' periods=' + @periods.to_s +
453
- ' data=[' + @data.first[0].to_s + '...' + @data.last[0].to_s + ']'+ '>'
454
-
455
- string
422
+ meta = [@periods, @frequency ? "frequency=#{@frequency}" : nil].compact.join(', ')
423
+ "#<#{self.class}(#{meta}) " \
424
+ "#{@data.first[0]}...#{@data.last[0]}>"
456
425
  end
457
426
 
458
427
  # Shift all dates in the index by a positive number in the future. The dates
@@ -476,16 +445,10 @@ module Daru
476
445
  # index.shift(4)
477
446
  # #=>#<DateTimeIndex:83979630 offset=YEAR periods=10 data=[2016-01-01T00:00:00+00:00...2025-01-01T00:00:00+00:00]>
478
447
  def shift distance
479
- if distance.is_a?(Fixnum)
480
- raise IndexError, "Distance #{distance} cannot be negative" if distance < 0
481
- raise IndexError, 'To shift non-freq date time index pass an offset.' unless @offset
448
+ distance.is_a?(Fixnum) && distance < 0 and
449
+ raise IndexError, "Distance #{distance} cannot be negative"
482
450
 
483
- start = @data[0][0]
484
- distance.times { start = @offset + start }
485
- DateTimeIndex.date_range(start: start, periods: @periods, freq: @offset)
486
- else # its a Daru::Offset/DateOffset
487
- DateTimeIndex.new(to_a.map { |e| distance + e }, freq: :infer)
488
- end
451
+ _shift(distance)
489
452
  end
490
453
 
491
454
  # Shift all dates in the index to the past. The dates are shifted by the same
@@ -498,18 +461,13 @@ module Daru
498
461
  # that it was created with.
499
462
  # @return [DateTimeIndex] A new lagged DateTimeIndex object.
500
463
  def lag distance
501
- if distance.is_a?(Fixnum)
502
- raise IndexError, "Distance #{distance} cannot be negative" if distance < 0
503
- raise IndexError, 'To lag non-freq date time index pass an offset.' unless @offset
464
+ distance.is_a?(Fixnum) && distance < 0 and
465
+ raise IndexError, "Distance #{distance} cannot be negative"
504
466
 
505
- start = @data[0][0]
506
- distance.times { start = @offset - start }
507
- DateTimeIndex.date_range(start: start, periods: @periods, freq: @offset)
508
- else
509
- DateTimeIndex.new(to_a.map { |e| distance - e }, freq: :infer)
510
- end
467
+ _shift(-distance)
511
468
  end
512
469
 
470
+ # :nocov:
513
471
  def _dump(_depth)
514
472
  Marshal.dump(data: to_a, freq: @offset)
515
473
  end
@@ -519,6 +477,7 @@ module Daru
519
477
 
520
478
  Daru::DateTimeIndex.new(h[:data], freq: h[:freq])
521
479
  end
480
+ # :nocov:
522
481
 
523
482
  # @!method year
524
483
  # @return [Array<Fixnum>] Array containing year of each index.
@@ -544,20 +503,61 @@ module Daru
544
503
  # you pass a string. Recommened specifying the full date as a DateTime object.
545
504
  def include? date_time
546
505
  return false unless date_time.is_a?(String) || date_time.is_a?(DateTime)
547
- helper = DateTimeIndexHelper
506
+
548
507
  if date_time.is_a?(String)
549
- date_precision = helper.determine_date_precision_of date_time
550
- date_time = helper.date_time_from date_time, date_precision
508
+ date_precision = Helper.determine_date_precision_of date_time
509
+ date_time = Helper.date_time_from date_time, date_precision
551
510
  end
552
511
 
553
- result = @data.bsearch { |d| d[0] >= date_time }
554
- return false if result.nil?
555
- result[0] == date_time
512
+ result, = @data.bsearch { |d| d[0] >= date_time }
513
+ result && result == date_time
556
514
  end
557
515
 
558
516
  # Return true if the DateTimeIndex is empty.
559
517
  def empty?
560
518
  @data.empty?
561
519
  end
520
+
521
+ private
522
+
523
+ def get_by_range first, last
524
+ return slice(first, last) if first.is_a?(Fixnum) && last.is_a?(Fixnum)
525
+
526
+ raise ArgumentError, "Keys #{first} and #{last} are out of bounds" if
527
+ Helper.key_out_of_bounds?(first, @data) && Helper.key_out_of_bounds?(last, @data)
528
+
529
+ slice first, last
530
+ end
531
+
532
+ def slice_between_dates first, last # rubocop:disable Metrics/AbcSize
533
+ # about that ^ disable: I'm waiting for cleaner understanding
534
+ # of offsets logic. Reference: https://github.com/v0dro/daru/commit/7e1c34aec9516a9ba33037b4a1daaaaf1de0726a#diff-a95ef410a8e1f4ea3cc48d231bb880faR250
535
+ start = @data.bsearch { |d| d[0] >= first }
536
+ after_en = @data.bsearch { |d| d[0] > last }
537
+
538
+ if @offset
539
+ en = after_en ? @data[after_en[1] - 1] : @data.last
540
+ return start[1] if start == en
541
+ DateTimeIndex.date_range start: start[0], end: en[0], freq: @offset
542
+ else
543
+ st = @data.index(start)
544
+ en = after_en ? @data.index(after_en) - 1 : Helper.last_date(@data)[1]
545
+ return start[1] if st == en
546
+ DateTimeIndex.new(@data[st..en].transpose[0])
547
+ end
548
+ end
549
+
550
+ def _shift distance
551
+ if distance.is_a?(Fixnum)
552
+ raise IndexError, 'To lag non-freq date time index pass an offset.' unless @offset
553
+
554
+ start = @data[0][0]
555
+ off = distance > 0 ? @offset : -@offset
556
+ distance.abs.times { start = off + start }
557
+ DateTimeIndex.date_range(start: start, periods: @periods, freq: @offset)
558
+ else
559
+ DateTimeIndex.new(to_a.map { |e| distance + e }, freq: :infer)
560
+ end
561
+ end
562
562
  end
563
563
  end