daru 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.build.sh +6 -6
- data/.gitignore +2 -0
- data/CONTRIBUTING.md +7 -3
- data/History.md +36 -0
- data/README.md +21 -13
- data/Rakefile +16 -1
- data/benchmarks/TradeoffData.csv +65 -0
- data/benchmarks/dataframe_creation.rb +39 -0
- data/benchmarks/group_by.rb +32 -0
- data/benchmarks/row_access.rb +41 -0
- data/benchmarks/row_assign.rb +36 -0
- data/benchmarks/sorting.rb +44 -0
- data/benchmarks/vector_access.rb +31 -0
- data/benchmarks/vector_assign.rb +42 -0
- data/benchmarks/where_clause.rb +48 -0
- data/benchmarks/where_vs_filter.rb +28 -0
- data/daru.gemspec +29 -5
- data/lib/daru.rb +30 -1
- data/lib/daru/accessors/array_wrapper.rb +2 -2
- data/lib/daru/accessors/nmatrix_wrapper.rb +6 -6
- data/lib/daru/core/group_by.rb +112 -31
- data/lib/daru/core/merge.rb +170 -0
- data/lib/daru/core/query.rb +95 -0
- data/lib/daru/dataframe.rb +335 -223
- data/lib/daru/date_time/index.rb +550 -0
- data/lib/daru/date_time/offsets.rb +397 -0
- data/lib/daru/index.rb +266 -54
- data/lib/daru/io/io.rb +1 -2
- data/lib/daru/maths/arithmetic/dataframe.rb +2 -2
- data/lib/daru/maths/arithmetic/vector.rb +2 -2
- data/lib/daru/maths/statistics/dataframe.rb +58 -8
- data/lib/daru/maths/statistics/vector.rb +229 -0
- data/lib/daru/vector.rb +230 -80
- data/lib/daru/version.rb +1 -1
- data/spec/core/group_by_spec.rb +16 -16
- data/spec/core/merge_spec.rb +52 -0
- data/spec/core/query_spec.rb +171 -0
- data/spec/dataframe_spec.rb +278 -280
- data/spec/date_time/data_spec.rb +199 -0
- data/spec/date_time/index_spec.rb +433 -0
- data/spec/date_time/offsets_spec.rb +371 -0
- data/spec/fixtures/stock_data.csv +500 -0
- data/spec/index_spec.rb +317 -11
- data/spec/io/io_spec.rb +18 -17
- data/spec/math/arithmetic/dataframe_spec.rb +3 -3
- data/spec/math/statistics/dataframe_spec.rb +39 -1
- data/spec/math/statistics/vector_spec.rb +163 -1
- data/spec/monkeys_spec.rb +4 -0
- data/spec/spec_helper.rb +3 -0
- data/spec/vector_spec.rb +125 -60
- metadata +71 -14
- data/lib/daru/accessors/dataframe_by_vector.rb +0 -17
- data/lib/daru/multi_index.rb +0 -216
- 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
|
data/lib/daru/index.rb
CHANGED
@@ -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
|
-
|
28
|
-
|
29
|
-
|
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
|
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
|
-
|
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
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
77
|
+
case
|
78
|
+
when loc.is_a?(Range)
|
79
|
+
first = loc.first
|
80
|
+
last = loc.last
|
73
81
|
|
74
|
-
|
75
|
-
when
|
76
|
-
Daru::Index.new key.map { |k|
|
82
|
+
slice first, last
|
83
|
+
when key.size > 1
|
84
|
+
Daru::Index.new key.map { |k| self[k] }
|
77
85
|
else
|
78
|
-
@relation_hash[
|
86
|
+
v = @relation_hash[loc]
|
87
|
+
return loc if v.nil?
|
88
|
+
v
|
79
89
|
end
|
80
90
|
end
|
81
91
|
|
82
|
-
def
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
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
|
-
|
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
|
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
|