daru 0.1.3.1 → 0.1.4

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