daru 0.1.0 → 0.1.1
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.
- 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
|