daru_lite 0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (149) hide show
  1. checksums.yaml +7 -0
  2. data/.github/ISSUE_TEMPLATE.md +18 -0
  3. data/.github/workflows/ci.yml +33 -0
  4. data/.gitignore +10 -0
  5. data/.rspec +2 -0
  6. data/.rubocop.yml +27 -0
  7. data/.rubocop_todo.yml +137 -0
  8. data/CONTRIBUTING.md +47 -0
  9. data/Gemfile +2 -0
  10. data/History.md +4 -0
  11. data/LICENSE +24 -0
  12. data/README.md +218 -0
  13. data/Rakefile +69 -0
  14. data/ReleasePolicy.md +20 -0
  15. data/benchmarks/TradeoffData.csv +65 -0
  16. data/benchmarks/csv_reading.rb +22 -0
  17. data/benchmarks/dataframe_creation.rb +39 -0
  18. data/benchmarks/db_loading.rb +34 -0
  19. data/benchmarks/duplicating.rb +45 -0
  20. data/benchmarks/group_by.rb +32 -0
  21. data/benchmarks/joining.rb +52 -0
  22. data/benchmarks/row_access.rb +41 -0
  23. data/benchmarks/row_assign.rb +36 -0
  24. data/benchmarks/sorting.rb +51 -0
  25. data/benchmarks/statistics.rb +28 -0
  26. data/benchmarks/vector_access.rb +31 -0
  27. data/benchmarks/vector_assign.rb +42 -0
  28. data/benchmarks/where_clause.rb +48 -0
  29. data/benchmarks/where_vs_filter.rb +28 -0
  30. data/daru_lite.gemspec +55 -0
  31. data/images/README.md +5 -0
  32. data/images/con0.png +0 -0
  33. data/images/con1.png +0 -0
  34. data/images/init0.png +0 -0
  35. data/images/init1.png +0 -0
  36. data/images/man0.png +0 -0
  37. data/images/man1.png +0 -0
  38. data/images/man2.png +0 -0
  39. data/images/man3.png +0 -0
  40. data/images/man4.png +0 -0
  41. data/images/man5.png +0 -0
  42. data/images/man6.png +0 -0
  43. data/lib/daru_lite/accessors/array_wrapper.rb +109 -0
  44. data/lib/daru_lite/accessors/dataframe_by_row.rb +25 -0
  45. data/lib/daru_lite/accessors/mdarray_wrapper.rb +7 -0
  46. data/lib/daru_lite/category.rb +929 -0
  47. data/lib/daru_lite/configuration.rb +34 -0
  48. data/lib/daru_lite/core/group_by.rb +403 -0
  49. data/lib/daru_lite/core/merge.rb +270 -0
  50. data/lib/daru_lite/core/query.rb +109 -0
  51. data/lib/daru_lite/dataframe.rb +3080 -0
  52. data/lib/daru_lite/date_time/index.rb +569 -0
  53. data/lib/daru_lite/date_time/offsets.rb +397 -0
  54. data/lib/daru_lite/exceptions.rb +2 -0
  55. data/lib/daru_lite/extensions/which_dsl.rb +53 -0
  56. data/lib/daru_lite/formatters/table.rb +52 -0
  57. data/lib/daru_lite/helpers/array.rb +53 -0
  58. data/lib/daru_lite/index/categorical_index.rb +201 -0
  59. data/lib/daru_lite/index/index.rb +374 -0
  60. data/lib/daru_lite/index/multi_index.rb +374 -0
  61. data/lib/daru_lite/io/csv/converters.rb +21 -0
  62. data/lib/daru_lite/io/io.rb +294 -0
  63. data/lib/daru_lite/io/sql_data_source.rb +97 -0
  64. data/lib/daru_lite/iruby/helpers.rb +38 -0
  65. data/lib/daru_lite/iruby/templates/dataframe.html.erb +5 -0
  66. data/lib/daru_lite/iruby/templates/dataframe_mi.html.erb +5 -0
  67. data/lib/daru_lite/iruby/templates/dataframe_mi_tbody.html.erb +35 -0
  68. data/lib/daru_lite/iruby/templates/dataframe_mi_thead.html.erb +21 -0
  69. data/lib/daru_lite/iruby/templates/dataframe_tbody.html.erb +28 -0
  70. data/lib/daru_lite/iruby/templates/dataframe_thead.html.erb +21 -0
  71. data/lib/daru_lite/iruby/templates/multi_index.html.erb +12 -0
  72. data/lib/daru_lite/iruby/templates/vector.html.erb +5 -0
  73. data/lib/daru_lite/iruby/templates/vector_mi.html.erb +5 -0
  74. data/lib/daru_lite/iruby/templates/vector_mi_tbody.html.erb +26 -0
  75. data/lib/daru_lite/iruby/templates/vector_mi_thead.html.erb +8 -0
  76. data/lib/daru_lite/iruby/templates/vector_tbody.html.erb +17 -0
  77. data/lib/daru_lite/iruby/templates/vector_thead.html.erb +8 -0
  78. data/lib/daru_lite/maths/arithmetic/dataframe.rb +91 -0
  79. data/lib/daru_lite/maths/arithmetic/vector.rb +117 -0
  80. data/lib/daru_lite/maths/statistics/dataframe.rb +202 -0
  81. data/lib/daru_lite/maths/statistics/vector.rb +1019 -0
  82. data/lib/daru_lite/monkeys.rb +56 -0
  83. data/lib/daru_lite/vector.rb +1678 -0
  84. data/lib/daru_lite/version.rb +3 -0
  85. data/lib/daru_lite.rb +99 -0
  86. data/profile/_base.rb +23 -0
  87. data/profile/df_to_a.rb +10 -0
  88. data/profile/filter.rb +13 -0
  89. data/profile/joining.rb +13 -0
  90. data/profile/sorting.rb +12 -0
  91. data/profile/vector_each_with_index.rb +9 -0
  92. data/profile/vector_new.rb +9 -0
  93. data/spec/accessors/array_wrapper_spec.rb +3 -0
  94. data/spec/category_spec.rb +1741 -0
  95. data/spec/core/group_by_spec.rb +655 -0
  96. data/spec/core/merge_spec.rb +179 -0
  97. data/spec/core/query_spec.rb +347 -0
  98. data/spec/daru_lite_spec.rb +22 -0
  99. data/spec/dataframe_spec.rb +4330 -0
  100. data/spec/date_time/data_spec.rb +197 -0
  101. data/spec/date_time/date_time_index_helper_spec.rb +72 -0
  102. data/spec/date_time/index_spec.rb +588 -0
  103. data/spec/date_time/offsets_spec.rb +465 -0
  104. data/spec/extensions/which_dsl_spec.rb +38 -0
  105. data/spec/fixtures/bank2.dat +200 -0
  106. data/spec/fixtures/boolean_converter_test.csv +5 -0
  107. data/spec/fixtures/countries.json +7794 -0
  108. data/spec/fixtures/duplicates.csv +32 -0
  109. data/spec/fixtures/eciresults.html +394 -0
  110. data/spec/fixtures/empties.dat +2 -0
  111. data/spec/fixtures/empty_rows_test.csv +17 -0
  112. data/spec/fixtures/macau.html +3691 -0
  113. data/spec/fixtures/macd_data.csv +150 -0
  114. data/spec/fixtures/matrix_test.csv +100 -0
  115. data/spec/fixtures/moneycontrol.html +6812 -0
  116. data/spec/fixtures/music_data.tsv +2501 -0
  117. data/spec/fixtures/repeated_fields.csv +7 -0
  118. data/spec/fixtures/sales-funnel.csv +18 -0
  119. data/spec/fixtures/scientific_notation.csv +4 -0
  120. data/spec/fixtures/string_converter_test.csv +5 -0
  121. data/spec/fixtures/strings.dat +2 -0
  122. data/spec/fixtures/test_xls.xls +0 -0
  123. data/spec/fixtures/test_xls_2.xls +0 -0
  124. data/spec/fixtures/url_test.txt~ +0 -0
  125. data/spec/fixtures/valid_markup.html +62 -0
  126. data/spec/fixtures/wiki_climate.html +1243 -0
  127. data/spec/fixtures/wiki_table_info.html +631 -0
  128. data/spec/formatters/table_formatter_spec.rb +137 -0
  129. data/spec/helpers_spec.rb +8 -0
  130. data/spec/index/categorical_index_spec.rb +170 -0
  131. data/spec/index/index_spec.rb +417 -0
  132. data/spec/index/multi_index_spec.rb +680 -0
  133. data/spec/io/io_spec.rb +373 -0
  134. data/spec/io/sql_data_source_spec.rb +56 -0
  135. data/spec/iruby/dataframe_spec.rb +170 -0
  136. data/spec/iruby/helpers_spec.rb +49 -0
  137. data/spec/iruby/multi_index_spec.rb +37 -0
  138. data/spec/iruby/vector_spec.rb +105 -0
  139. data/spec/maths/arithmetic/dataframe_spec.rb +148 -0
  140. data/spec/maths/arithmetic/vector_spec.rb +165 -0
  141. data/spec/maths/statistics/dataframe_spec.rb +178 -0
  142. data/spec/maths/statistics/vector_spec.rb +756 -0
  143. data/spec/monkeys_spec.rb +42 -0
  144. data/spec/shared/vector_display_spec.rb +213 -0
  145. data/spec/spec_helper.rb +87 -0
  146. data/spec/support/database_helper.rb +30 -0
  147. data/spec/support/matchers.rb +5 -0
  148. data/spec/vector_spec.rb +2293 -0
  149. metadata +571 -0
@@ -0,0 +1,680 @@
1
+ require 'spec_helper.rb'
2
+
3
+ describe DaruLite::MultiIndex do
4
+ before(:each) do
5
+ @index_tuples = [
6
+ [:a,:one,:bar],
7
+ [:a,:one,:baz],
8
+ [:a,:two,:bar],
9
+ [:a,:two,:baz],
10
+ [:b,:one,:bar],
11
+ [:b,:two,:bar],
12
+ [:b,:two,:baz],
13
+ [:b,:one,:foo],
14
+ [:c,:one,:bar],
15
+ [:c,:one,:baz],
16
+ [:c,:two,:foo],
17
+ [:c,:two,:bar]
18
+ ]
19
+ @multi_mi = DaruLite::MultiIndex.from_tuples(@index_tuples)
20
+ end
21
+
22
+ context ".initialize" do
23
+ it "accepts labels and levels as arguments" do
24
+ mi = DaruLite::MultiIndex.new(
25
+ levels: [[:a,:b,:c], [:one, :two]],
26
+ labels: [[0,0,1,1,2,2], [0,1,0,1,0,1]])
27
+
28
+ expect(mi[:a, :two]).to eq(1)
29
+ end
30
+
31
+ it "raises error for wrong number of labels or levels" do
32
+ expect {
33
+ DaruLite::MultiIndex.new(
34
+ levels: [[:a,:a,:b,:b,:c,:c], [:one, :two]],
35
+ labels: [[0,0,1,1,2,2]])
36
+ }.to raise_error
37
+ end
38
+
39
+ context "create an MultiIndex with name" do
40
+ context 'if no name is set' do
41
+ subject { DaruLite::MultiIndex.new(
42
+ levels: [[:a,:b,:c], [:one, :two]],
43
+ labels: [[0,0,1,1,2,2], [0,1,0,1,0,1]]) }
44
+ its(:name) { is_expected.to be_nil }
45
+ end
46
+
47
+ context 'correctly return the MultiIndex name' do
48
+ subject { DaruLite::MultiIndex.new(
49
+ levels: [[:a,:b,:c], [:one, :two]],
50
+ labels: [[0,0,1,1,2,2], [0,1,0,1,0,1]], name: ['n1', 'n2']) }
51
+ its(:name) { is_expected.to eq ['n1', 'n2'] }
52
+ end
53
+
54
+ context "set new MultiIndex name" do
55
+ subject {
56
+ DaruLite::MultiIndex.new(
57
+ levels: [[:a,:b,:c], [:one, :two]],
58
+ labels: [[0,0,1,1,2,2], [0,1,0,1,0,1]], name: ['n1', 'n2']) }
59
+ before(:each) { subject.name = ['k1', 'k2'] }
60
+ its(:name) { is_expected.to eq ['k1', 'k2'] }
61
+ end
62
+
63
+ context "set new MultiIndex name having empty string" do
64
+ subject {
65
+ DaruLite::MultiIndex.new(
66
+ levels: [[:a,:b,:c], [:one, :two]],
67
+ labels: [[0,0,1,1,2,2], [0,1,0,1,0,1]], name: ['n1', 'n2']) }
68
+ before { subject.name = ['k1', ''] }
69
+ its(:name) { is_expected.to eq ['k1', ''] }
70
+ end
71
+
72
+ it "raises SizeError for wrong number of name" do
73
+ error_msg = "\'names\' and \'levels\' should be of same size. Size of the \'name\' array is 2 and size of the MultiIndex \'levels\' and \'labels\' is 3.\nIf you do not want to set name for particular level (say level \'i\') then put empty string on index \'i\' of the \'name\' Array."
74
+ expect { @multi_mi.name = ['n1', 'n2'] }.to raise_error(SizeError, error_msg)
75
+
76
+ error_msg = "'names' and 'levels' should be of same size. Size of the 'name' array is 0 and size of the MultiIndex 'levels' and 'labels' is 3.\nIf you do not want to set name for particular level (say level 'i') then put empty string on index 'i' of the 'name' Array."
77
+ expect { @multi_mi.name = [ ] }.to raise_error(SizeError, error_msg)
78
+
79
+ error_msg = "'names' and 'levels' should be of same size. Size of the 'name' array is 1 and size of the MultiIndex 'levels' and 'labels' is 3.\nIf you do not want to set name for particular level (say level 'i') then put empty string on index 'i' of the 'name' Array."
80
+ expect { @multi_mi.name = [''] }.to raise_error(SizeError, error_msg)
81
+
82
+ error_msg = "'names' and 'levels' should be of same size. Size of the 'name' array is 4 and size of the MultiIndex 'levels' and 'labels' is 3."
83
+ expect { @multi_mi.name = ['n1', 'n2', 'n3', 'n4'] }.to raise_error(SizeError, error_msg)
84
+ end
85
+ end
86
+ end
87
+
88
+ context ".from_tuples" do
89
+ it "creates 2 layer MultiIndex from tuples" do
90
+ tuples = [
91
+ [:a, :one],
92
+ [:a, :two],
93
+ [:b, :one],
94
+ [:b, :two],
95
+ [:c, :one],
96
+ [:c, :two]
97
+ ]
98
+ mi = DaruLite::MultiIndex.from_tuples(tuples)
99
+ expect(mi.levels).to eq([[:a, :b, :c], [:one,:two]])
100
+ expect(mi.labels).to eq([[0,0,1,1,2,2], [0,1,0,1,0,1]])
101
+ end
102
+
103
+ it "creates a triple layer MultiIndex from tuples" do
104
+ expect(@multi_mi.levels).to eq([[:a,:b,:c], [:one, :two],[:bar,:baz,:foo]])
105
+ expect(@multi_mi.labels).to eq([
106
+ [0,0,0,0,1,1,1,1,2,2,2,2],
107
+ [0,0,1,1,0,1,1,0,0,0,1,1],
108
+ [0,1,0,1,0,0,1,2,0,1,2,0]
109
+ ])
110
+ end
111
+ end
112
+
113
+ context '.try_from_tuples' do
114
+ it 'creates MultiIndex, if there are tuples' do
115
+ tuples = [
116
+ [:a, :one],
117
+ [:a, :two],
118
+ [:b, :one],
119
+ [:b, :two],
120
+ [:c, :one],
121
+ [:c, :two]
122
+ ]
123
+ mi = DaruLite::MultiIndex.try_from_tuples(tuples)
124
+ expect(mi).to be_a DaruLite::MultiIndex
125
+ end
126
+
127
+ it 'returns nil, if MultiIndex can not be created' do
128
+ mi = DaruLite::MultiIndex.try_from_tuples([:a, :b, :c])
129
+ expect(mi).to be_nil
130
+ end
131
+ end
132
+
133
+ context "#size" do
134
+ it "returns size of MultiIndex" do
135
+ expect(@multi_mi.size).to eq(12)
136
+ end
137
+ end
138
+
139
+ context "#[]" do
140
+ it "returns the row number when specifying the complete tuple" do
141
+ expect(@multi_mi[:a, :one, :baz]).to eq(1)
142
+ end
143
+
144
+ it "returns MultiIndex when specifying incomplete tuple" do
145
+ expect(@multi_mi[:b]).to eq(DaruLite::MultiIndex.from_tuples([
146
+ [:b,:one,:bar],
147
+ [:b,:two,:bar],
148
+ [:b,:two,:baz],
149
+ [:b,:one,:foo]
150
+ ]))
151
+ expect(@multi_mi[:b, :one]).to eq(DaruLite::MultiIndex.from_tuples([
152
+ [:b,:one,:bar],
153
+ [:b,:one,:foo]
154
+ ]))
155
+ # TODO: Return DaruLite::Index if a single layer of indexes is present.
156
+ end
157
+
158
+ it "returns MultiIndex when specifying wholly numeric ranges" do
159
+ expect(@multi_mi[3..6]).to eq(DaruLite::MultiIndex.from_tuples([
160
+ [:a,:two,:baz],
161
+ [:b,:one,:bar],
162
+ [:b,:two,:bar],
163
+ [:b,:two,:baz]
164
+ ]))
165
+ end
166
+
167
+ it "raises error when specifying invalid index" do
168
+ expect { @multi_mi[:a, :three] }.to raise_error IndexError
169
+ expect { @multi_mi[:a, :one, :xyz] }.to raise_error IndexError
170
+ expect { @multi_mi[:x] }.to raise_error IndexError
171
+ expect { @multi_mi[:x, :one] }.to raise_error IndexError
172
+ expect { @multi_mi[:x, :one, :bar] }.to raise_error IndexError
173
+ end
174
+
175
+ it "works with numerical first levels" do
176
+ mi = DaruLite::MultiIndex.from_tuples([
177
+ [2000, 'M'],
178
+ [2000, 'F'],
179
+ [2001, 'M'],
180
+ [2001, 'F']
181
+ ])
182
+
183
+ expect(mi[2000]).to eq(DaruLite::MultiIndex.from_tuples([
184
+ [2000, 'M'],
185
+ [2000, 'F']
186
+ ]))
187
+
188
+ expect(mi[2000,'M']).to eq(0)
189
+ end
190
+ end
191
+
192
+ context "#include?" do
193
+ it "checks if a completely specified tuple exists" do
194
+ expect(@multi_mi.include?([:a,:one,:bar])).to eq(true)
195
+ end
196
+
197
+ it "checks if a top layer incomplete tuple exists" do
198
+ expect(@multi_mi.include?([:a])).to eq(true)
199
+ end
200
+
201
+ it "checks if a middle layer incomplete tuple exists" do
202
+ expect(@multi_mi.include?([:a, :one])).to eq(true)
203
+ end
204
+
205
+ it "checks for non-existence of completely specified tuple" do
206
+ expect(@multi_mi.include?([:b, :two, :foo])).to eq(false)
207
+ end
208
+
209
+ it "checks for non-existence of a top layer incomplete tuple" do
210
+ expect(@multi_mi.include?([:d])).to eq(false)
211
+ end
212
+
213
+ it "checks for non-existence of a middle layer incomplete tuple" do
214
+ expect(@multi_mi.include?([:c, :three])).to eq(false)
215
+ end
216
+ end
217
+
218
+ context "#key" do
219
+ it "returns the tuple of the specified number" do
220
+ expect(@multi_mi.key(3)).to eq([:a,:two,:baz])
221
+ end
222
+
223
+ it "returns nil for non-existent pointer number" do
224
+ expect {
225
+ @multi_mi.key(100)
226
+ }.to raise_error ArgumentError
227
+ end
228
+ end
229
+
230
+ context "#to_a" do
231
+ it "returns tuples as an Array" do
232
+ expect(@multi_mi.to_a).to eq(@index_tuples)
233
+ end
234
+ end
235
+
236
+ context "#dup" do
237
+ it "completely duplicates the object" do
238
+ duplicate = @multi_mi.dup
239
+ expect(duplicate) .to eq(@multi_mi)
240
+ expect(duplicate.object_id).to_not eq(@multi_mi.object_id)
241
+ end
242
+ end
243
+
244
+ context "#inspect" do
245
+ context 'small index' do
246
+ subject {
247
+ DaruLite::MultiIndex.from_tuples [
248
+ [:a,:one,:bar],
249
+ [:a,:one,:baz],
250
+ [:a,:two,:bar],
251
+ [:a,:two,:baz],
252
+ [:b,:one,:bar],
253
+ [:b,:two,:bar],
254
+ [:b,:two,:baz],
255
+ [:b,:one,:foo],
256
+ [:c,:one,:bar],
257
+ [:c,:one,:baz],
258
+ [:c,:two,:foo],
259
+ [:c,:two,:bar]
260
+ ]
261
+ }
262
+
263
+ its(:inspect) { is_expected.to eq %Q{
264
+ |#<DaruLite::MultiIndex(12x3)>
265
+ | a one bar
266
+ | baz
267
+ | two bar
268
+ | baz
269
+ | b one bar
270
+ | two bar
271
+ | baz
272
+ | one foo
273
+ | c one bar
274
+ | baz
275
+ | two foo
276
+ | bar
277
+ }.unindent
278
+ }
279
+ end
280
+
281
+ context 'large index' do
282
+ subject {
283
+ DaruLite::MultiIndex.from_tuples(
284
+ (1..100).map { |i| %w[a b c].map { |c| [i, c] } }.flatten(1)
285
+ )
286
+ }
287
+
288
+ its(:inspect) { is_expected.to eq %Q{
289
+ |#<DaruLite::MultiIndex(300x2)>
290
+ | 1 a
291
+ | b
292
+ | c
293
+ | 2 a
294
+ | b
295
+ | c
296
+ | 3 a
297
+ | b
298
+ | c
299
+ | 4 a
300
+ | b
301
+ | c
302
+ | 5 a
303
+ | b
304
+ | c
305
+ | 6 a
306
+ | b
307
+ | c
308
+ | 7 a
309
+ | b
310
+ | ... ...
311
+ }.unindent
312
+ }
313
+ end
314
+
315
+ context 'multi index with name' do
316
+ subject {
317
+ DaruLite::MultiIndex.new(
318
+ levels: [[:a,:b,:c],[:one,:two],[:bar, :baz, :foo]],
319
+ labels: [
320
+ [0,0,0,0,1,1,1,1,2,2,2,2],
321
+ [0,0,1,1,0,1,1,0,0,0,1,1],
322
+ [0,1,0,1,0,0,1,2,0,1,2,0]], name: ['n1', 'n2', 'n3'])
323
+ }
324
+
325
+ its(:inspect) { is_expected.to start_with %Q{
326
+ |#<DaruLite::MultiIndex(12x3)>
327
+ | n1 n2 n3
328
+ }.unindent
329
+ }
330
+ end
331
+
332
+ context 'multi index with name having empty string' do
333
+ subject {
334
+ mi= DaruLite::MultiIndex.new(
335
+ levels: [[:a,:b,:c],[:one,:two],[:bar, :baz, :foo]],
336
+ labels: [
337
+ [0,0,0,0,1,1,1,1,2,2,2,2],
338
+ [0,0,1,1,0,1,1,0,0,0,1,1],
339
+ [0,1,0,1,0,0,1,2,0,1,2,0]], name: ['n1', 'n2', 'n3'])
340
+ }
341
+ before { subject.name = ['n1', '', 'n3'] }
342
+
343
+ its(:inspect) { is_expected.to start_with %Q{
344
+ |#<DaruLite::MultiIndex(12x3)>
345
+ | n1 n3
346
+ }.unindent
347
+ }
348
+ end
349
+
350
+ end
351
+
352
+ context "#==" do
353
+ it "returns false for unequal MultiIndex comparisons" do
354
+ mi1 = DaruLite::MultiIndex.from_tuples([
355
+ [:a, :one, :bar],
356
+ [:a, :two, :baz],
357
+ [:b, :one, :foo],
358
+ [:b, :two, :bar]
359
+ ])
360
+ mi2 = DaruLite::MultiIndex.from_tuples([
361
+ [:a, :two, :bar],
362
+ [:b, :one, :foo],
363
+ [:a, :one, :baz],
364
+ [:b, :two, :baz]
365
+ ])
366
+
367
+ expect(mi1 == mi2).to eq(false)
368
+ end
369
+ end
370
+
371
+ context "#values" do
372
+ it "returns an array of indices in order" do
373
+ mi = DaruLite::MultiIndex.from_tuples([
374
+ [:a, :one, :bar],
375
+ [:a, :two, :baz],
376
+ [:b, :one, :foo],
377
+ [:b, :two, :bar]
378
+ ])
379
+
380
+ expect(mi.values).to eq([0,1,2,3])
381
+ end
382
+ end
383
+
384
+ context "#|" do
385
+ before do
386
+ @mi1 = DaruLite::MultiIndex.from_tuples([
387
+ [:a, :one, :bar],
388
+ [:a, :two, :baz],
389
+ [:b, :one, :foo],
390
+ [:b, :two, :bar]
391
+ ])
392
+ @mi2 = DaruLite::MultiIndex.from_tuples([
393
+ [:a, :two, :bar],
394
+ [:b, :one, :foo],
395
+ [:a, :one, :baz],
396
+ [:b, :two, :baz]
397
+ ])
398
+ end
399
+
400
+ it "returns a union of two MultiIndex objects" do
401
+ expect(@mi1 | @mi2).to eq(DaruLite::MultiIndex.new(
402
+ levels: [[:a, :b], [:one, :two], [:bar, :baz, :foo]],
403
+ labels: [
404
+ [0, 0, 1, 1, 0, 0, 1],
405
+ [0, 1, 0, 1, 1, 0, 1],
406
+ [0, 1, 2, 0, 0, 1, 1]
407
+ ])
408
+ )
409
+ end
410
+ end
411
+
412
+ context "#&" do
413
+ before do
414
+ @mi1 = DaruLite::MultiIndex.from_tuples([
415
+ [:a, :one],
416
+ [:a, :two],
417
+ [:b, :two]
418
+ ])
419
+ @mi2 = DaruLite::MultiIndex.from_tuples([
420
+ [:a, :two],
421
+ [:b, :one],
422
+ [:b, :three]
423
+ ])
424
+ end
425
+
426
+ it "returns the intersection of two MI objects" do
427
+ expect(@mi1 & @mi2).to eq(DaruLite::MultiIndex.from_tuples([
428
+ [:a, :two],
429
+ ]))
430
+ end
431
+ end
432
+
433
+ context "#empty?" do
434
+ it "returns true if nothing present in MultiIndex" do
435
+ expect(DaruLite::MultiIndex.new(labels: [[]], levels: [[]]).empty?).to eq(true)
436
+ end
437
+ end
438
+
439
+ context "#drop_left_level" do
440
+ it "drops the leftmost level" do
441
+ expect(
442
+ DaruLite::MultiIndex.from_tuples([
443
+ [:c,:one,:bar],
444
+ [:c,:one,:baz],
445
+ [:c,:two,:foo],
446
+ [:c,:two,:bar]
447
+ ]).drop_left_level).to eq(
448
+ DaruLite::MultiIndex.from_tuples([
449
+ [:one,:bar],
450
+ [:one,:baz],
451
+ [:two,:foo],
452
+ [:two,:bar]
453
+ ])
454
+ )
455
+ end
456
+ end
457
+
458
+ context 'other forms of tuple list representation' do
459
+ let(:index) {
460
+ DaruLite::MultiIndex.from_tuples [
461
+ [:a,:one,:bar],
462
+ [:a,:one,:baz],
463
+ [:a,:two,:bar],
464
+ [:a,:two,:baz],
465
+ [:b,:one,:bar],
466
+ [:b,:two,:bar],
467
+ [:b,:two,:baz],
468
+ [:b,:one,:foo],
469
+ [:c,:one,:bar],
470
+ [:c,:one,:baz],
471
+ [:c,:two,:foo],
472
+ [:c,:two,:bar]
473
+ ]
474
+ }
475
+
476
+ context '#sparse_tuples' do
477
+ subject { index.sparse_tuples }
478
+
479
+ it { is_expected.to eq [
480
+ [:a ,:one,:bar],
481
+ [nil, nil,:baz],
482
+ [nil,:two,:bar],
483
+ [nil, nil,:baz],
484
+ [:b ,:one,:bar],
485
+ [nil,:two,:bar],
486
+ [nil, nil,:baz],
487
+ [nil,:one,:foo],
488
+ [:c ,:one,:bar],
489
+ [nil, nil,:baz],
490
+ [nil,:two,:foo],
491
+ [nil, nil,:bar]
492
+ ]}
493
+ end
494
+ end
495
+
496
+ context "#pos" do
497
+ let(:idx) do
498
+ described_class.from_tuples([
499
+ [:b,:one,:bar],
500
+ [:b,:two,:bar],
501
+ [:b,:two,:baz],
502
+ [:b,:one,:foo]
503
+ ])
504
+ end
505
+
506
+ context "single index" do
507
+ it { expect(idx.pos :b, :one, :bar).to eq 0 }
508
+ end
509
+
510
+ context "multiple indexes" do
511
+ subject { idx.pos :b, :one }
512
+
513
+ it { is_expected.to be_a Array }
514
+ its(:size) { is_expected.to eq 2 }
515
+ it { is_expected.to eq [0, 3] }
516
+ end
517
+
518
+ context "single positional index" do
519
+ it { expect(idx.pos 0).to eq 0 }
520
+ end
521
+
522
+ context "multiple positional indexes" do
523
+ subject { idx.pos 0, 1 }
524
+
525
+ it { is_expected.to be_a Array }
526
+ its(:size) { is_expected.to eq 2 }
527
+ it { is_expected.to eq [0, 1] }
528
+ end
529
+
530
+ # TODO: Add specs for IndexError
531
+ end
532
+
533
+ context "#subset" do
534
+ let(:idx) do
535
+ described_class.from_tuples([
536
+ [:b, :one, :bar],
537
+ [:b, :two, :bar],
538
+ [:b, :two, :baz],
539
+ [:b, :one, :foo]
540
+ ])
541
+ end
542
+
543
+ context "multiple indexes" do
544
+ subject { idx.subset :b, :one }
545
+
546
+ it { is_expected.to be_a described_class }
547
+ its(:size) { is_expected.to eq 2 }
548
+ its(:to_a) { is_expected.to eq [[:bar], [:foo]] }
549
+ end
550
+
551
+ context "multiple positional indexes" do
552
+ subject { idx.subset 0, 1 }
553
+
554
+ it { is_expected.to be_a described_class }
555
+ its(:size) { is_expected.to eq 2 }
556
+ its(:to_a) { is_expected.to eq [[:b, :one, :bar], [:b, :two, :bar]] }
557
+ end
558
+
559
+ # TODO: Checks for invalid indexes
560
+ end
561
+
562
+ context "at" do
563
+ let(:idx) do
564
+ described_class.from_tuples([
565
+ [:b, :one, :bar],
566
+ [:b, :two, :bar],
567
+ [:b, :two, :baz],
568
+ [:b, :one, :foo]
569
+ ])
570
+ end
571
+
572
+ context "single position" do
573
+ it { expect(idx.at 2).to eq [:b, :two, :baz] }
574
+ end
575
+
576
+ context "multiple positions" do
577
+ subject { idx.at 1, 2 }
578
+
579
+ it { is_expected.to be_a described_class }
580
+ its(:size) { is_expected.to eq 2 }
581
+ its(:to_a) { is_expected.to eq [[:b, :two, :bar],
582
+ [:b, :two, :baz]] }
583
+ end
584
+
585
+ context "range" do
586
+ subject { idx.at 1..2 }
587
+
588
+ it { is_expected.to be_a described_class }
589
+ its(:size) { is_expected.to eq 2 }
590
+ its(:to_a) { is_expected.to eq [[:b, :two, :bar],
591
+ [:b, :two, :baz]] }
592
+ end
593
+
594
+ context "range with negative integers" do
595
+ subject { idx.at 1..-2 }
596
+
597
+ it { is_expected.to be_a described_class }
598
+ its(:size) { is_expected.to eq 2 }
599
+ its(:to_a) { is_expected.to eq [[:b, :two, :bar],
600
+ [:b, :two, :baz]] }
601
+ end
602
+
603
+ context "rangle with single element" do
604
+ subject { idx.at 1..1 }
605
+
606
+ it { is_expected.to be_a described_class }
607
+ its(:size) { is_expected.to eq 1 }
608
+ its(:to_a) { is_expected.to eq [[:b, :two, :bar]] }
609
+ end
610
+
611
+ context "invalid position" do
612
+ it { expect { idx.at 4 }.to raise_error IndexError }
613
+ end
614
+
615
+ context "invalid positions" do
616
+ it { expect { idx.at 2, 4 }.to raise_error IndexError }
617
+ end
618
+ end
619
+
620
+ context "#add" do
621
+ let(:idx) do
622
+ described_class.from_tuples [
623
+ [:a, :one, :bar],
624
+ [:a, :two, :bar],
625
+ [:b, :two, :baz],
626
+ [:b, :one, :foo]
627
+ ]
628
+ end
629
+
630
+ context "single index" do
631
+ subject { idx.add :b, :two, :baz }
632
+
633
+ its(:to_a) { is_expected.to eq [
634
+ [:a, :one, :bar],
635
+ [:a, :two, :bar],
636
+ [:b, :two, :baz],
637
+ [:b, :one, :foo],
638
+ [:b, :two, :baz]] }
639
+ end
640
+ end
641
+
642
+ context "#valid?" do
643
+ let(:idx) do
644
+ described_class.from_tuples [
645
+ [:a, :one, :bar],
646
+ [:a, :two, :bar],
647
+ [:b, :two, :baz],
648
+ [:b, :one, :foo]
649
+ ]
650
+ end
651
+
652
+ context "single index" do
653
+ it { expect(idx.valid? :a, :one, :bar).to eq true }
654
+ it { expect(idx.valid? :b, :two, :three).to eq false }
655
+ end
656
+
657
+ context "multiple indexes" do
658
+ it { expect(idx.valid? :a, :one).to eq true }
659
+ it { expect(idx.valid? :a, :three).to eq false }
660
+ end
661
+ end
662
+
663
+ context '#to_df' do
664
+ let(:idx) do
665
+ described_class.from_tuples([
666
+ %w[a one bar],
667
+ %w[a two bar],
668
+ %w[b two baz],
669
+ %w[b one foo]
670
+ ]).tap { |idx| idx.name = %w[col1 col2 col3] }
671
+ end
672
+
673
+ subject { idx.to_df }
674
+ it { is_expected.to eq DaruLite::DataFrame.new(
675
+ 'col1' => %w[a a b b],
676
+ 'col2' => %w[one two two one],
677
+ 'col3' => %w[bar bar baz foo]
678
+ )}
679
+ end
680
+ end