daru 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/.build.sh +6 -6
  3. data/.gitignore +2 -0
  4. data/CONTRIBUTING.md +7 -3
  5. data/History.md +36 -0
  6. data/README.md +21 -13
  7. data/Rakefile +16 -1
  8. data/benchmarks/TradeoffData.csv +65 -0
  9. data/benchmarks/dataframe_creation.rb +39 -0
  10. data/benchmarks/group_by.rb +32 -0
  11. data/benchmarks/row_access.rb +41 -0
  12. data/benchmarks/row_assign.rb +36 -0
  13. data/benchmarks/sorting.rb +44 -0
  14. data/benchmarks/vector_access.rb +31 -0
  15. data/benchmarks/vector_assign.rb +42 -0
  16. data/benchmarks/where_clause.rb +48 -0
  17. data/benchmarks/where_vs_filter.rb +28 -0
  18. data/daru.gemspec +29 -5
  19. data/lib/daru.rb +30 -1
  20. data/lib/daru/accessors/array_wrapper.rb +2 -2
  21. data/lib/daru/accessors/nmatrix_wrapper.rb +6 -6
  22. data/lib/daru/core/group_by.rb +112 -31
  23. data/lib/daru/core/merge.rb +170 -0
  24. data/lib/daru/core/query.rb +95 -0
  25. data/lib/daru/dataframe.rb +335 -223
  26. data/lib/daru/date_time/index.rb +550 -0
  27. data/lib/daru/date_time/offsets.rb +397 -0
  28. data/lib/daru/index.rb +266 -54
  29. data/lib/daru/io/io.rb +1 -2
  30. data/lib/daru/maths/arithmetic/dataframe.rb +2 -2
  31. data/lib/daru/maths/arithmetic/vector.rb +2 -2
  32. data/lib/daru/maths/statistics/dataframe.rb +58 -8
  33. data/lib/daru/maths/statistics/vector.rb +229 -0
  34. data/lib/daru/vector.rb +230 -80
  35. data/lib/daru/version.rb +1 -1
  36. data/spec/core/group_by_spec.rb +16 -16
  37. data/spec/core/merge_spec.rb +52 -0
  38. data/spec/core/query_spec.rb +171 -0
  39. data/spec/dataframe_spec.rb +278 -280
  40. data/spec/date_time/data_spec.rb +199 -0
  41. data/spec/date_time/index_spec.rb +433 -0
  42. data/spec/date_time/offsets_spec.rb +371 -0
  43. data/spec/fixtures/stock_data.csv +500 -0
  44. data/spec/index_spec.rb +317 -11
  45. data/spec/io/io_spec.rb +18 -17
  46. data/spec/math/arithmetic/dataframe_spec.rb +3 -3
  47. data/spec/math/statistics/dataframe_spec.rb +39 -1
  48. data/spec/math/statistics/vector_spec.rb +163 -1
  49. data/spec/monkeys_spec.rb +4 -0
  50. data/spec/spec_helper.rb +3 -0
  51. data/spec/vector_spec.rb +125 -60
  52. metadata +71 -14
  53. data/lib/daru/accessors/dataframe_by_vector.rb +0 -17
  54. data/lib/daru/multi_index.rb +0 -216
  55. data/spec/multi_index_spec.rb +0 -216
@@ -0,0 +1,397 @@
1
+ module Daru
2
+ # Generic class for generating date offsets.
3
+ class DateOffset
4
+ # A Daru::DateOffset object is created by a passing certain options
5
+ # to the constructor, which determine the kind of offset the object
6
+ # will support.
7
+ #
8
+ # You can pass one of the following options followed by their number
9
+ # to the DateOffset constructor:
10
+ #
11
+ # * :secs - Create a seconds offset
12
+ # * :mins - Create a minutes offset
13
+ # * :hours - Create an hours offset
14
+ # * :days - Create a days offset
15
+ # * :weeks - Create a weeks offset
16
+ # * :months - Create a months offset
17
+ # * :years - Create a years offset
18
+ #
19
+ # Additionaly, passing the `:n` option will apply the offset that many times.
20
+ #
21
+ # @example Usage of DateOffset
22
+ # # Create an offset of 3 weeks.
23
+ # offset = Daru::DateOffset.new(weeks: 3)
24
+ # offset + DateTime.new(2012,5,3)
25
+ # #=> #<DateTime: 2012-05-24T00:00:00+00:00 ((2456072j,0s,0n),+0s,2299161j)>
26
+ #
27
+ # # Create an offset of 5 hours
28
+ # offset = Daru::DateOffset.new(hours: 5)
29
+ # offset + DateTime.new(2015,3,3,23,5,1)
30
+ # #=> #<DateTime: 2015-03-04T04:05:01+00:00 ((2457086j,14701s,0n),+0s,2299161j)>
31
+ #
32
+ # # Create an offset of 2 minutes, applied 5 times
33
+ # offset = Daru::DateOffset.new(mins: 2, n: 5)
34
+ # offset + DateTime.new(2011,5,3,3,5)
35
+ # #=> #<DateTime: 2011-05-03T03:15:00+00:00 ((2455685j,11700s,0n),+0s,2299161j)>
36
+ def initialize opts={}
37
+ n = opts[:n] || 1
38
+
39
+ @offset =
40
+ case
41
+ when opts[:secs]
42
+ Offsets::Second.new(n*opts[:secs])
43
+ when opts[:mins]
44
+ Offsets::Minute.new(n*opts[:mins])
45
+ when opts[:hours]
46
+ Offsets::Hour.new(n*opts[:hours])
47
+ when opts[:days]
48
+ Offsets::Day.new(n*opts[:days])
49
+ when opts[:weeks]
50
+ Offsets::Day.new(7*n*opts[:weeks])
51
+ when opts[:months]
52
+ Offsets::Month.new(n*opts[:months])
53
+ when opts[:years]
54
+ Offsets::Year.new(n*opts[:years])
55
+ end
56
+ end
57
+
58
+ # Offset a DateTime forward.
59
+ #
60
+ # @param date_time [DateTime] A DateTime object which is to offset.
61
+ def + date_time
62
+ @offset + date_time
63
+ end
64
+
65
+ # Offset a DateTime backward.
66
+ #
67
+ # @param date_time [DateTime] A DateTime object which is to offset.
68
+ def - date_time
69
+ @offset - date_time
70
+ end
71
+ end
72
+
73
+ module Offsets
74
+ # Private superclass for Offsets with equal inter-frequencies.
75
+ # @abstract
76
+ # @private
77
+ class Tick < DateOffset
78
+ # Initialize one of the subclasses of Tick with the number of the times
79
+ # the offset should be applied, which is the supplied as the argument.
80
+ #
81
+ # @param n [Integer] The number of times an offset should be applied.
82
+ def initialize n=1
83
+ @n = n
84
+ end
85
+
86
+ def + date_time
87
+ date_time + @n*multiplier
88
+ end
89
+
90
+ def - date_time
91
+ date_time - @n*multiplier
92
+ end
93
+ end
94
+
95
+ # Create a seconds offset
96
+ #
97
+ # @param n [Integer] The number of times an offset should be applied.
98
+ # @example Create a Seconds offset
99
+ # offset = Daru::Offsets::Second.new(5)
100
+ # offset + DateTime.new(2012,5,1,4,3)
101
+ # #=> #<DateTime: 2012-05-01T04:03:05+00:00 ((2456049j,14585s,0n),+0s,2299161j)>
102
+ class Second < Tick
103
+ def multiplier
104
+ 1.1574074074074073e-05
105
+ end
106
+
107
+ def freq_string
108
+ (@n == 1 ? '' : @n.to_s) + 'S'
109
+ end
110
+ end
111
+
112
+ # Create a minutes offset
113
+ #
114
+ # @param n [Integer] The number of times an offset should be applied.
115
+ # @example Create a Minutes offset
116
+ # offset = Daru::Offsets::Minute.new(8)
117
+ # offset + DateTime.new(2012,5,1,4,3)
118
+ # #=> #<DateTime: 2012-05-01T04:11:00+00:00 ((2456049j,15060s,0n),+0s,2299161j)>
119
+ class Minute < Tick
120
+ def multiplier
121
+ 0.0006944444444444445
122
+ end
123
+
124
+ def freq_string
125
+ (@n == 1 ? '' : @n.to_s) + 'M'
126
+ end
127
+ end
128
+
129
+ # Create an hours offset
130
+ #
131
+ # @param n [Integer] The number of times an offset should be applied.
132
+ # @example Create a Hour offset
133
+ # offset = Daru::Offsets::Hour.new(8)
134
+ # offset + DateTime.new(2012,5,1,4,3)
135
+ # #=> #<DateTime: 2012-05-01T12:03:00+00:00 ((2456049j,43380s,0n),+0s,2299161j)>
136
+ class Hour < Tick
137
+ def multiplier
138
+ 0.041666666666666664
139
+ end
140
+
141
+ def freq_string
142
+ (@n == 1 ? '' : @n.to_s) + 'H'
143
+ end
144
+ end
145
+
146
+ # Create an days offset
147
+ #
148
+ # @param n [Integer] The number of times an offset should be applied.
149
+ # @example Create a Day offset
150
+ # offset = Daru::Offsets::Day.new(2)
151
+ # offset + DateTime.new(2012,5,1,4,3)
152
+ # #=> #<DateTime: 2012-05-03T04:03:00+00:00 ((2456051j,14580s,0n),+0s,2299161j)>
153
+ class Day < Tick
154
+ def multiplier
155
+ 1.0
156
+ end
157
+
158
+ def freq_string
159
+ (@n == 1 ? '' : @n.to_s) + 'D'
160
+ end
161
+ end
162
+
163
+ # Create an months offset
164
+ #
165
+ # @param n [Integer] The number of times an offset should be applied.
166
+ # @example Create a Month offset
167
+ # offset = Daru::Offsets::Month.new(5)
168
+ # offset + DateTime.new(2012,5,1,4,3)
169
+ # #=> #<DateTime: 2012-10-01T04:03:00+00:00 ((2456202j,14580s,0n),+0s,2299161j)>
170
+ class Month < Tick
171
+ def freq_string
172
+ (@n == 1 ? '' : @n.to_s) + 'MONTH'
173
+ end
174
+
175
+ def + date_time
176
+ date_time >> @n
177
+ end
178
+
179
+ def - date_time
180
+ date_time << @n
181
+ end
182
+ end
183
+
184
+ # Create a years offset
185
+ #
186
+ # @param n [Integer] The number of times an offset should be applied.
187
+ # @example Create a Year offset
188
+ # offset = Daru::Offsets::Year.new(2)
189
+ # offset + DateTime.new(2012,5,1,4,3)
190
+ # #=> #<DateTime: 2014-05-01T04:03:00+00:00 ((2456779j,14580s,0n),+0s,2299161j)>
191
+ class Year < Tick
192
+ def freq_string
193
+ (@n == 1 ? '' : @n.to_s) + 'YEAR'
194
+ end
195
+
196
+ def + date_time
197
+ date_time >> @n*12
198
+ end
199
+
200
+ def - date_time
201
+ date_time << @n*12
202
+ end
203
+ end
204
+
205
+ class Week < DateOffset
206
+ def initialize *args
207
+ @n = !args[0].is_a?(Hash)? args[0] : 1
208
+ opts = args[-1]
209
+ @weekday = opts[:weekday] || 0
210
+ end
211
+
212
+ def + date_time
213
+ wday = date_time.wday
214
+ distance = (@weekday - wday).abs
215
+ if @weekday > wday
216
+ date_time + distance + 7*(@n-1)
217
+ else
218
+ date_time + (7-distance) + 7*(@n -1)
219
+ end
220
+ end
221
+
222
+ def - date_time
223
+ wday = date_time.wday
224
+ distance = (@weekday - wday).abs
225
+ if @weekday >= wday
226
+ date_time - ((7 - distance) + 7*(@n -1))
227
+ else
228
+ date_time - (distance + 7*(@n-1))
229
+ end
230
+ end
231
+
232
+ def on_offset? date_time
233
+ date_time.wday == @weekday
234
+ end
235
+
236
+ def freq_string
237
+ (@n == 1 ? '' : @n.to_s) + 'W' + '-' + Daru::DAYS_OF_WEEK.key(@weekday)
238
+ end
239
+ end
240
+
241
+ # Create a month begin offset
242
+ #
243
+ # @param n [Integer] The number of times an offset should be applied.
244
+ # @example Create a MonthBegin offset
245
+ # offset = Daru::Offsets::MonthBegin.new(2)
246
+ # offset + DateTime.new(2012,5,5)
247
+ # #=> #<DateTime: 2012-07-01T00:00:00+00:00 ((2456110j,0s,0n),+0s,2299161j)>
248
+ class MonthBegin < DateOffset
249
+ def initialize n=1
250
+ @n = n
251
+ end
252
+
253
+ def freq_string
254
+ (@n == 1 ? '' : @n.to_s) + "MB"
255
+ end
256
+
257
+ def + date_time
258
+ @n.times do
259
+ days_in_month = Daru::MONTH_DAYS[date_time.month]
260
+ days_in_month += 1 if date_time.leap? and date_time.month == 2
261
+ date_time = date_time + (days_in_month - date_time.day + 1)
262
+ end
263
+
264
+ date_time
265
+ end
266
+
267
+ def - date_time
268
+ @n.times do
269
+ date_time = date_time << 1 if on_offset?(date_time)
270
+ date_time = DateTime.new(date_time.year, date_time.month, 1,
271
+ date_time.hour, date_time.min, date_time.sec)
272
+ end
273
+
274
+ date_time
275
+ end
276
+
277
+ def on_offset? date_time
278
+ date_time.day == 1
279
+ end
280
+ end
281
+
282
+ # Create a month end offset
283
+ #
284
+ # @param n [Integer] The number of times an offset should be applied.
285
+ # @example Create a MonthEnd offset
286
+ # offset = Daru::Offsets::MonthEnd.new
287
+ # offset + DateTime.new(2012,5,5)
288
+ # #=> #<DateTime: 2012-05-31T00:00:00+00:00 ((2456079j,0s,0n),+0s,2299161j)>
289
+ class MonthEnd < DateOffset
290
+ def initialize n=1
291
+ @n = n
292
+ end
293
+
294
+ def freq_string
295
+ (@n == 1 ? '' : @n.to_s) + 'ME'
296
+ end
297
+
298
+ def + date_time
299
+ @n.times do
300
+ date_time = date_time >> 1 if on_offset?(date_time)
301
+ days_in_month = Daru::MONTH_DAYS[date_time.month]
302
+ days_in_month += 1 if date_time.leap? and date_time.month == 2
303
+
304
+ date_time = date_time + (days_in_month - date_time.day)
305
+ end
306
+
307
+ date_time
308
+ end
309
+
310
+ def - date_time
311
+ @n.times do
312
+ date_time = date_time << 1
313
+ days_in_month = Daru::MONTH_DAYS[date_time.month]
314
+ days_in_month += 1 if date_time.leap? and date_time.month == 2
315
+
316
+ date_time = date_time + (days_in_month - date_time.day)
317
+ end
318
+
319
+ date_time
320
+ end
321
+
322
+ def on_offset? date_time
323
+ (date_time + 1).day == 1
324
+ end
325
+ end
326
+
327
+ # Create a year begin offset
328
+ #
329
+ # @param n [Integer] The number of times an offset should be applied.
330
+ # @example Create a YearBegin offset
331
+ # offset = Daru::Offsets::YearBegin.new(3)
332
+ # offset + DateTime.new(2012,5,5)
333
+ # #=> #<DateTime: 2015-01-01T00:00:00+00:00 ((2457024j,0s,0n),+0s,2299161j)>
334
+ class YearBegin < DateOffset
335
+ def initialize n=1
336
+ @n = n
337
+ end
338
+
339
+ def freq_string
340
+ (@n == 1 ? '' : @n.to_s) + 'YB'
341
+ end
342
+
343
+ def + date_time
344
+ DateTime.new(date_time.year + @n, 1, 1,
345
+ date_time.hour,date_time.min, date_time.sec)
346
+ end
347
+
348
+ def - date_time
349
+ if on_offset?(date_time)
350
+ DateTime.new(date_time.year - @n, 1, 1,
351
+ date_time.hour,date_time.min, date_time.sec)
352
+ else
353
+ DateTime.new(date_time.year - (@n-1), 1, 1)
354
+ end
355
+ end
356
+
357
+ def on_offset? date_time
358
+ date_time.month == 1 and date_time.day == 1
359
+ end
360
+ end
361
+
362
+ # Create a year end offset
363
+ #
364
+ # @param n [Integer] The number of times an offset should be applied.
365
+ # @example Create a YearEnd offset
366
+ # offset = Daru::Offsets::YearEnd.new
367
+ # offset + DateTime.new(2012,5,5)
368
+ # #=> #<DateTime: 2012-12-31T00:00:00+00:00 ((2456293j,0s,0n),+0s,2299161j)>
369
+ class YearEnd < DateOffset
370
+ def initialize n=1
371
+ @n = n
372
+ end
373
+
374
+ def freq_string
375
+ (@n == 1 ? '' : @n.to_s) + 'YE'
376
+ end
377
+
378
+ def + date_time
379
+ if on_offset?(date_time)
380
+ DateTime.new(date_time.year + @n, 12, 31,
381
+ date_time.hour, date_time.min, date_time.sec)
382
+ else
383
+ DateTime.new(date_time.year + (@n-1), 12, 31,
384
+ date_time.hour, date_time.min, date_time.sec)
385
+ end
386
+ end
387
+
388
+ def - date_time
389
+ DateTime.new(date_time.year - 1, 12, 31)
390
+ end
391
+
392
+ def on_offset? date_time
393
+ date_time.month == 12 and date_time.day == 31
394
+ end
395
+ end
396
+ end
397
+ end
@@ -1,6 +1,42 @@
1
1
  module Daru
2
2
  class Index
3
3
  include Enumerable
4
+ # It so happens that over riding the .new method in a super class also
5
+ # tampers with the default .new method for class that inherit from the
6
+ # super class (Index in this case). Thus we first alias the original
7
+ # new method (from Object) to __new__ when the Index class is evaluated,
8
+ # and then we use an inherited hook such that the old new method (from
9
+ # Object) is once again the default .new for the subclass.
10
+ # Refer http://blog.sidu.in/2007/12/rubys-new-as-factory.html
11
+ class << self
12
+ alias :__new__ :new
13
+
14
+ def inherited subclass
15
+ class << subclass
16
+ alias :new :__new__
17
+ end
18
+ end
19
+ end
20
+
21
+ # We over-ride the .new method so that any sort of Index can be generated
22
+ # from Daru::Index based on the types of arguments supplied.
23
+ def self.new *args, &block
24
+ source = args[0]
25
+
26
+ idx =
27
+ if source and source[0].is_a?(Array)
28
+ Daru::MultiIndex.from_tuples source
29
+ elsif source and source.is_a?(Array) and !source.empty? and
30
+ source.all? { |e| e.is_a?(DateTime) }
31
+ Daru::DateTimeIndex.new(source, freq: :infer)
32
+ else
33
+ i = self.allocate
34
+ i.send :initialize, *args, &block
35
+ i
36
+ end
37
+
38
+ idx
39
+ end
4
40
 
5
41
  def each(&block)
6
42
  @relation_hash.each_key(&block)
@@ -11,84 +47,73 @@ module Daru
11
47
  to_a.map(&block)
12
48
  end
13
49
 
14
- attr_reader :relation_hash
15
-
16
- attr_reader :size
17
-
18
- attr_reader :index_class
19
-
20
- def initialize index, values=nil
21
- @relation_hash = {}
50
+ attr_reader :relation_hash, :size
22
51
 
52
+ def initialize index
23
53
  index = 0 if index.nil?
24
54
  index = Array.new(index) { |i| i} if index.is_a? Integer
25
55
  index = index.to_a if index.is_a? Daru::Index
26
56
 
27
- if values.nil?
28
- index.each_with_index do |n, idx|
29
- n = n.to_sym unless n.is_a?(Integer)
30
-
31
- @relation_hash[n] = idx
32
- end
33
- else
34
- raise IndexError, "Size of values : #{values.size} and index : #{index.size} do not match" if
35
- index.size != values.size
36
-
37
- values.each_with_index do |value,i|
38
- @relation_hash[index[i]] = value
39
- end
57
+ @relation_hash = {}
58
+ index.each_with_index do |n, idx|
59
+ @relation_hash[n] = idx
40
60
  end
41
61
 
42
62
  @relation_hash.freeze
63
+ @keys = @relation_hash.keys
43
64
  @size = @relation_hash.size
44
-
45
- if index[0].is_a?(Integer)
46
- @index_class = Integer
47
- else
48
- @index_class = Symbol
49
- end
50
65
  end
51
66
 
52
67
  def ==(other)
53
- return false if other.size != @size
68
+ return false if self.class != other.class or other.size != @size
54
69
 
55
- @relation_hash.keys == other.to_a and @relation_hash.values == other.relation_hash.values
70
+ @relation_hash.keys == other.to_a and
71
+ @relation_hash.values == other.relation_hash.values
56
72
  end
57
73
 
58
- def [](key)
59
- case key
60
- when Range
61
- if key.first.is_a?(Integer) and key.last.is_a?(Integer)
62
- first = key.first
63
- last = key.last
64
- else
65
- first = @relation_hash[key.first]
66
- last = @relation_hash[key.last]
67
- end
74
+ def [](*key)
75
+ loc = key[0]
68
76
 
69
- indexes = []
70
- (first..last).each do |idx|
71
- indexes << @relation_hash.key(idx)
72
- end
77
+ case
78
+ when loc.is_a?(Range)
79
+ first = loc.first
80
+ last = loc.last
73
81
 
74
- Daru::Index.new indexes, (first..last).to_a
75
- when Array # works only with numeric indices
76
- Daru::Index.new key.map { |k| @relation_hash.key(k) }, key
82
+ slice first, last
83
+ when key.size > 1
84
+ Daru::Index.new key.map { |k| self[k] }
77
85
  else
78
- @relation_hash[key]
86
+ v = @relation_hash[loc]
87
+ return loc if v.nil?
88
+ v
79
89
  end
80
90
  end
81
91
 
82
- def +(other)
83
- if other.respond_to? :relation_hash #another index object
84
- (@relation_hash.keys + other.relation_hash.keys).uniq.to_index
85
- elsif other.is_a?(Symbol) or other.is_a?(Integer)
86
- (@relation_hash.keys << other).uniq.to_index
92
+ def slice *args
93
+ start = args[0]
94
+ en = args[1]
95
+ indexes = []
96
+
97
+ if start.is_a?(Integer) and en.is_a?(Integer)
98
+ Index.new @keys[start..en]
87
99
  else
88
- (@relation_hash.keys + other).uniq.to_index
100
+ start_idx = @relation_hash[start]
101
+ en_idx = @relation_hash[en]
102
+
103
+ Index.new @keys[start_idx..en_idx]
89
104
  end
90
105
  end
91
106
 
107
+ # Produce new index from the set union of two indexes.
108
+ def |(other)
109
+ Index.new(to_a | other.to_a)
110
+ end
111
+
112
+ # Produce a new index from the set intersection of two indexes
113
+ def & other
114
+
115
+ end
116
+
92
117
  def to_a
93
118
  @relation_hash.keys
94
119
  end
@@ -116,7 +141,194 @@ module Daru
116
141
  def self._load data
117
142
  h = Marshal.load data
118
143
 
119
- Daru::Index.new(h[:relation_hash].keys, h[:relation_hash].values)
144
+ Daru::Index.new(h[:relation_hash].keys)
145
+ end
146
+ end # class Index
147
+
148
+ class MultiIndex < Index
149
+ include Enumerable
150
+
151
+ def each(&block)
152
+ to_a.each(&block)
153
+ end
154
+
155
+ def map(&block)
156
+ to_a.map(&block)
157
+ end
158
+
159
+ attr_reader :labels
160
+
161
+ def levels
162
+ @levels.map { |e| e.keys }
163
+ end
164
+
165
+ def initialize opts={}
166
+ labels = opts[:labels]
167
+ levels = opts[:levels]
168
+
169
+ raise ArgumentError,
170
+ "Must specify both labels and levels" unless labels and levels
171
+ raise ArgumentError,
172
+ "Labels and levels should be same size" if labels.size != levels.size
173
+ raise ArgumentError,
174
+ "Incorrect labels and levels" if incorrect_fields?(labels, levels)
175
+
176
+ @labels = labels
177
+ @levels = levels.map { |e| Hash[e.map.with_index.to_a]}
178
+ end
179
+
180
+ def incorrect_fields? labels, levels
181
+ max_level = levels[0].size
182
+
183
+ correct = labels.all? { |e| e.size == max_level }
184
+ correct = levels.all? { |e| e.uniq.size == e.size }
185
+
186
+ !correct
187
+ end
188
+
189
+ private :incorrect_fields?
190
+
191
+ def self.from_arrays arrays
192
+ levels = arrays.map { |e| e.uniq.sort_by { |a| a.to_s } }
193
+ labels = []
194
+
195
+ arrays.each_with_index do |arry, level_index|
196
+ label = []
197
+ level = levels[level_index]
198
+ arry.each do |lvl|
199
+ label << level.index(lvl)
200
+ end
201
+
202
+ labels << label
203
+ end
204
+
205
+ MultiIndex.new labels: labels, levels: levels
206
+ end
207
+
208
+ def self.from_tuples tuples
209
+ from_arrays tuples.transpose
210
+ end
211
+
212
+ def [] *key
213
+ key.flatten!
214
+ case
215
+ when key[0].is_a?(Range) then retrieve_from_range(key[0])
216
+ when (key[0].is_a?(Integer) and key.size == 1) then try_retrieve_from_integer(key[0])
217
+ else retrieve_from_tuples(key)
218
+ end
219
+ end
220
+
221
+ def try_retrieve_from_integer int
222
+ return retrieve_from_tuples([int]) if @levels[0].has_key?(int)
223
+ int
224
+ end
225
+
226
+ def retrieve_from_range range
227
+ MultiIndex.from_tuples(range.map { |index| key(index) })
228
+ end
229
+
230
+ def retrieve_from_tuples key
231
+ chosen = []
232
+
233
+ key.each_with_index do |k, depth|
234
+ level_index = @levels[depth][k]
235
+ label = @labels[depth]
236
+ chosen = find_all_indexes label, level_index, chosen
237
+ end
238
+
239
+ return chosen[0] if chosen.size == 1
240
+ return multi_index_from_multiple_selections(chosen)
241
+ end
242
+
243
+ def multi_index_from_multiple_selections chosen
244
+ MultiIndex.from_tuples(chosen.map { |e| key(e) })
245
+ end
246
+
247
+ def find_all_indexes label, level_index, chosen
248
+ if chosen.empty?
249
+ label.each_with_index do |lbl, i|
250
+ if lbl == level_index then chosen << i end
251
+ end
252
+ else
253
+ chosen.keep_if { |c| label[c] == level_index }
254
+ end
255
+
256
+ chosen
257
+ end
258
+
259
+ private :find_all_indexes, :multi_index_from_multiple_selections,
260
+ :retrieve_from_range, :retrieve_from_tuples
261
+
262
+ def key index
263
+ raise ArgumentError,
264
+ "Key #{index} is too large" if index >= @labels[0].size
265
+
266
+ level_indexes =
267
+ @labels.inject([]) do |memo, label|
268
+ memo << label[index]
269
+ memo
270
+ end
271
+
272
+ tuple = []
273
+ level_indexes.each_with_index do |level_index, i|
274
+ tuple << @levels[i].keys[level_index]
275
+ end
276
+
277
+ tuple
278
+ end
279
+
280
+ def dup
281
+ MultiIndex.new levels: levels.dup, labels: labels
282
+ end
283
+
284
+ def drop_left_level by=1
285
+ MultiIndex.from_arrays to_a.transpose[by..-1]
286
+ end
287
+
288
+ def | other
289
+ MultiIndex.from_tuples(to_a | other.to_a)
290
+ end
291
+
292
+ def & other
293
+ MultiIndex.from_tuples(to_a & other.to_a)
294
+ end
295
+
296
+ def empty?
297
+ @labels.flatten.empty? and @levels.all? { |l| l.empty? }
298
+ end
299
+
300
+ def include? tuple
301
+ tuple.flatten!
302
+ tuple.each_with_index do |tup, i|
303
+ return false unless @levels[i][tup]
304
+ end
305
+ true
306
+ end
307
+
308
+ def size
309
+ @labels[0].size
310
+ end
311
+
312
+ def width
313
+ @levels.size
314
+ end
315
+
316
+ def == other
317
+ self.class == other.class and
318
+ labels == other.labels and
319
+ levels == other.levels
320
+ end
321
+
322
+ def to_a
323
+ (0...size).map { |e| key(e) }
324
+ end
325
+
326
+ def values
327
+ Array.new(size) { |i| i }
328
+ end
329
+
330
+ def inspect
331
+ "Daru::MultiIndex:#{self.object_id} (levels: #{levels}\nlabels: #{labels})"
120
332
  end
121
333
  end
122
334
  end