daru 0.1.5 → 0.3

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.
Files changed (106) hide show
  1. checksums.yaml +5 -5
  2. data/.github/ISSUE_TEMPLATE.md +18 -0
  3. data/.gitignore +1 -0
  4. data/.rubocop.yml +21 -7
  5. data/.travis.yml +10 -5
  6. data/CONTRIBUTING.md +15 -10
  7. data/History.md +124 -2
  8. data/README.md +37 -9
  9. data/ReleasePolicy.md +20 -0
  10. data/benchmarks/db_loading.rb +34 -0
  11. data/benchmarks/statistics.rb +6 -6
  12. data/benchmarks/where_clause.rb +1 -1
  13. data/benchmarks/where_vs_filter.rb +1 -1
  14. data/daru.gemspec +17 -41
  15. data/lib/daru.rb +10 -13
  16. data/lib/daru/accessors/gsl_wrapper.rb +1 -1
  17. data/lib/daru/accessors/nmatrix_wrapper.rb +2 -0
  18. data/lib/daru/category.rb +29 -15
  19. data/lib/daru/configuration.rb +34 -0
  20. data/lib/daru/core/group_by.rb +158 -77
  21. data/lib/daru/core/merge.rb +12 -3
  22. data/lib/daru/core/query.rb +20 -4
  23. data/lib/daru/dataframe.rb +692 -118
  24. data/lib/daru/date_time/index.rb +14 -11
  25. data/lib/daru/date_time/offsets.rb +9 -1
  26. data/lib/daru/extensions/which_dsl.rb +55 -0
  27. data/lib/daru/formatters/table.rb +3 -5
  28. data/lib/daru/index/categorical_index.rb +4 -4
  29. data/lib/daru/index/index.rb +131 -42
  30. data/lib/daru/index/multi_index.rb +118 -10
  31. data/lib/daru/io/csv/converters.rb +21 -0
  32. data/lib/daru/io/io.rb +105 -33
  33. data/lib/daru/io/sql_data_source.rb +10 -0
  34. data/lib/daru/iruby/templates/dataframe.html.erb +4 -51
  35. data/lib/daru/iruby/templates/dataframe_mi.html.erb +3 -56
  36. data/lib/daru/iruby/templates/dataframe_mi_tbody.html.erb +35 -0
  37. data/lib/daru/iruby/templates/dataframe_mi_thead.html.erb +21 -0
  38. data/lib/daru/iruby/templates/dataframe_tbody.html.erb +28 -0
  39. data/lib/daru/iruby/templates/dataframe_thead.html.erb +21 -0
  40. data/lib/daru/iruby/templates/vector.html.erb +3 -25
  41. data/lib/daru/iruby/templates/vector_mi.html.erb +3 -34
  42. data/lib/daru/iruby/templates/vector_mi_tbody.html.erb +26 -0
  43. data/lib/daru/iruby/templates/vector_mi_thead.html.erb +8 -0
  44. data/lib/daru/iruby/templates/vector_tbody.html.erb +17 -0
  45. data/lib/daru/iruby/templates/vector_thead.html.erb +8 -0
  46. data/lib/daru/maths/arithmetic/vector.rb +38 -2
  47. data/lib/daru/maths/statistics/dataframe.rb +28 -30
  48. data/lib/daru/maths/statistics/vector.rb +295 -41
  49. data/lib/daru/plotting/gruff/dataframe.rb +13 -15
  50. data/lib/daru/plotting/nyaplot/category.rb +1 -1
  51. data/lib/daru/plotting/nyaplot/dataframe.rb +15 -4
  52. data/lib/daru/plotting/nyaplot/vector.rb +1 -2
  53. data/lib/daru/vector.rb +308 -96
  54. data/lib/daru/version.rb +1 -1
  55. data/profile/vector_new.rb +9 -0
  56. data/spec/accessors/gsl_wrapper_spec.rb +38 -35
  57. data/spec/accessors/nmatrix_wrapper_spec.rb +25 -22
  58. data/spec/category_spec.rb +24 -20
  59. data/spec/core/group_by_spec.rb +238 -4
  60. data/spec/core/merge_spec.rb +1 -1
  61. data/spec/core/query_spec.rb +65 -50
  62. data/spec/daru_spec.rb +22 -0
  63. data/spec/dataframe_spec.rb +473 -16
  64. data/spec/date_time/date_time_index_helper_spec.rb +72 -0
  65. data/spec/date_time/index_spec.rb +34 -16
  66. data/spec/date_time/offsets_spec.rb +14 -0
  67. data/spec/extensions/rserve_spec.rb +1 -1
  68. data/spec/extensions/which_dsl_spec.rb +38 -0
  69. data/spec/fixtures/boolean_converter_test.csv +5 -0
  70. data/spec/fixtures/duplicates.csv +32 -0
  71. data/spec/fixtures/eciresults.html +394 -0
  72. data/spec/fixtures/empty_rows_test.csv +17 -0
  73. data/spec/fixtures/macau.html +3691 -0
  74. data/spec/fixtures/macd_data.csv +150 -0
  75. data/spec/fixtures/matrix_test.csv +55 -55
  76. data/spec/fixtures/moneycontrol.html +6812 -0
  77. data/spec/fixtures/string_converter_test.csv +5 -0
  78. data/spec/fixtures/test_xls.xls +0 -0
  79. data/spec/fixtures/test_xls_2.xls +0 -0
  80. data/spec/fixtures/url_test.txt~ +0 -0
  81. data/spec/fixtures/valid_markup.html +62 -0
  82. data/spec/fixtures/wiki_climate.html +1243 -0
  83. data/spec/fixtures/wiki_table_info.html +631 -0
  84. data/spec/formatters/table_formatter_spec.rb +29 -0
  85. data/spec/index/categorical_index_spec.rb +33 -33
  86. data/spec/index/index_spec.rb +160 -41
  87. data/spec/index/multi_index_spec.rb +143 -33
  88. data/spec/io/io_spec.rb +246 -2
  89. data/spec/io/sql_data_source_spec.rb +31 -41
  90. data/spec/iruby/dataframe_spec.rb +17 -19
  91. data/spec/iruby/vector_spec.rb +26 -28
  92. data/spec/maths/arithmetic/dataframe_spec.rb +1 -1
  93. data/spec/maths/arithmetic/vector_spec.rb +18 -0
  94. data/spec/maths/statistics/vector_spec.rb +153 -15
  95. data/spec/plotting/gruff/category_spec.rb +3 -3
  96. data/spec/plotting/gruff/dataframe_spec.rb +14 -4
  97. data/spec/plotting/gruff/vector_spec.rb +9 -9
  98. data/spec/plotting/nyaplot/category_spec.rb +5 -9
  99. data/spec/plotting/nyaplot/dataframe_spec.rb +95 -47
  100. data/spec/plotting/nyaplot/vector_spec.rb +5 -11
  101. data/spec/shared/vector_display_spec.rb +12 -14
  102. data/spec/spec_helper.rb +30 -7
  103. data/spec/support/matchers.rb +5 -0
  104. data/spec/vector_spec.rb +306 -72
  105. metadata +96 -55
  106. data/spec/fixtures/stock_data.csv +0 -500
@@ -112,7 +112,7 @@ describe Daru::DataFrame do
112
112
  expect(@left.join(@right, how: :outer, on: [:name])).to eq(answer)
113
113
  end
114
114
 
115
- it "performs a left outer join", focus: true do
115
+ it "performs a left outer join" do
116
116
  answer = Daru::DataFrame.new({
117
117
  :id_1 => [2,3,1,4],
118
118
  :name => ["Monkey", "Ninja", "Pirate", "Spaghetti"],
@@ -43,79 +43,79 @@ describe "Arel-like syntax" do
43
43
  @comparator = Daru::Vector.new([45,22,1214,55,32,9])
44
44
  @klass = Daru::Core::Query::BoolArray
45
45
  end
46
-
46
+
47
47
  context "#eq" do
48
48
  it "accepts scalar value" do
49
49
  expect(@vector.eq(352)).to eq(
50
50
  @klass.new([false,false,false,true,false,false]))
51
51
  end
52
-
52
+
53
53
  it "accepts vector and compares corrensponding elements" do
54
54
  expect(@vector.eq(@comparator)).to eq(
55
55
  @klass.new([false,false,true,false,true,false]))
56
56
  end
57
57
  end
58
-
58
+
59
59
  context "#not_eq" do
60
60
  it "accepts scalar value" do
61
61
  expect(@vector.not_eq(51)).to eq(
62
62
  @klass.new([true, false, true, true, true, true]))
63
63
  end
64
-
64
+
65
65
  it "accepts vector and compares corrensponding elements" do
66
66
  expect(@vector.not_eq(@comparator)).to eq(
67
67
  @klass.new([true, true, false, true, false, true]))
68
68
  end
69
69
  end
70
-
70
+
71
71
  context "#lt" do
72
72
  it "accepts scalar value" do
73
73
  expect(@vector.lt(51)).to eq(
74
74
  @klass.new([true, false, false, false, true, true]))
75
75
  end
76
-
76
+
77
77
  it "accepts vector and compares corrensponding elements" do
78
78
  expect(@vector.lt(@comparator)).to eq(
79
79
  @klass.new([true,false,false,false,false,false]))
80
80
  end
81
81
  end
82
-
82
+
83
83
  context "#lteq" do
84
84
  it "accepts scalar value" do
85
85
  expect(@vector.lteq(51)).to eq(
86
86
  @klass.new([true, true, false, false, true, true]))
87
87
  end
88
-
88
+
89
89
  it "accepts vector and compares corrensponding elements" do
90
90
  expect(@vector.lteq(@comparator)).to eq(
91
91
  @klass.new([true,false,true,false,true,false]))
92
92
  end
93
93
  end
94
-
94
+
95
95
  context "#mt" do
96
96
  it "accepts scalar value" do
97
97
  expect(@vector.mt(51)).to eq(
98
98
  @klass.new([false, false, true, true, false, false]))
99
99
  end
100
-
100
+
101
101
  it "accepts vector and compares corrensponding elements" do
102
102
  expect(@vector.mt(@comparator)).to eq(
103
103
  @klass.new([false,true,false,true,false,true]))
104
104
  end
105
105
  end
106
-
106
+
107
107
  context "#mteq" do
108
108
  it "accepts scalar value" do
109
109
  expect(@vector.mteq(51)).to eq(
110
110
  @klass.new([false, true, true, true, false, false]))
111
111
  end
112
-
112
+
113
113
  it "accepts vector and compares corrensponding elements" do
114
114
  expect(@vector.mteq(@comparator)).to eq(
115
115
  @klass.new([false,true,true,true,true,true]))
116
116
  end
117
117
  end
118
-
118
+
119
119
  context "#in" do
120
120
  it "checks if any of elements in the arg are present in the vector" do
121
121
  expect(@vector.in([23,55,1,33,32])).to eq(
@@ -123,7 +123,7 @@ describe "Arel-like syntax" do
123
123
  end
124
124
  end
125
125
  end
126
-
126
+
127
127
  describe "categorical type" do
128
128
  let(:dv) { Daru::Vector.new ['e', 'd', 'd', 'x', 'x'],
129
129
  categories: ['a', 'x', 'c', 'd', 'e'], type: :category }
@@ -134,99 +134,99 @@ describe "Arel-like syntax" do
134
134
  context "#eq" do
135
135
  context "scalar" do
136
136
  subject { dv.eq 'd' }
137
-
137
+
138
138
  it { is_expected.to be_a query_bool_class }
139
139
  its(:to_a) { is_expected.to eq [false, true, true, false, false] }
140
140
  end
141
141
 
142
142
  context "vector" do
143
143
  subject { dv.eq comp }
144
-
144
+
145
145
  it { is_expected.to be_a query_bool_class }
146
146
  its(:to_a) { is_expected.to eq [false, true, false, false, true] }
147
147
  end
148
148
  end
149
-
149
+
150
150
  context "#not_eq" do
151
151
  context "scalar" do
152
152
  subject { dv.not_eq 'd' }
153
-
153
+
154
154
  it { is_expected.to be_a query_bool_class }
155
155
  its(:to_a) { is_expected.to eq [true, false, false, true, true] }
156
156
  end
157
-
157
+
158
158
  context "vector" do
159
159
  subject { dv.not_eq comp }
160
-
160
+
161
161
  it { is_expected.to be_a query_bool_class }
162
162
  its(:to_a) { is_expected.to eq [true, false, true, true, false] }
163
163
  end
164
164
  end
165
-
165
+
166
166
  context "#lt" do
167
167
  context "scalar" do
168
168
  subject { dv.lt 'd' }
169
-
169
+
170
170
  it { is_expected.to be_a query_bool_class }
171
171
  its(:to_a) { is_expected.to eq [false, false, false, true, true] }
172
172
  end
173
-
173
+
174
174
  context "vector" do
175
175
  subject { dv.lt comp }
176
-
176
+
177
177
  it { is_expected.to be_a query_bool_class }
178
178
  its(:to_a) { is_expected.to eq [false, false, false, true, false] }
179
179
  end
180
180
  end
181
-
181
+
182
182
  context "#lteq" do
183
183
  context "scalar" do
184
184
  subject { dv.lteq 'd' }
185
-
185
+
186
186
  it { is_expected.to be_a query_bool_class }
187
187
  its(:to_a) { is_expected.to eq [false, true, true, true, true] }
188
188
  end
189
-
189
+
190
190
  context "vector" do
191
191
  subject { dv.lteq comp }
192
-
192
+
193
193
  it { is_expected.to be_a query_bool_class }
194
194
  its(:to_a) { is_expected.to eq [false, true, false, true, true] }
195
195
  end
196
196
  end
197
-
197
+
198
198
  context "#mt" do
199
199
  context "scalar" do
200
200
  subject { dv.mt 'd' }
201
-
201
+
202
202
  it { is_expected.to be_a query_bool_class }
203
203
  its(:to_a) { is_expected.to eq [true, false, false, false, false] }
204
204
  end
205
-
205
+
206
206
  context "vector" do
207
207
  subject { dv.mt comp }
208
-
208
+
209
209
  it { is_expected.to be_a query_bool_class }
210
210
  its(:to_a) { is_expected.to eq [true, false, true, false, false] }
211
211
  end
212
212
  end
213
-
213
+
214
214
  context "#mteq" do
215
215
  context "scalar" do
216
216
  subject { dv.mteq 'd' }
217
-
217
+
218
218
  it { is_expected.to be_a query_bool_class }
219
219
  its(:to_a) { is_expected.to eq [true, true, true, false, false] }
220
220
  end
221
-
221
+
222
222
  context "vector" do
223
223
  subject { dv.mteq comp }
224
-
224
+
225
225
  it { is_expected.to be_a query_bool_class }
226
226
  its(:to_a) { is_expected.to eq [true, true, true, false, true] }
227
227
  end
228
228
  end
229
-
229
+
230
230
  # context "#in" do
231
231
  # subject { dv.in ['b', 'd'] }
232
232
  # it { is_expected.to be_a query_bool_class }
@@ -240,9 +240,9 @@ describe "Arel-like syntax" do
240
240
  context Daru::DataFrame do
241
241
  before do
242
242
  @df = Daru::DataFrame.new({
243
- number: [1,2,3,4,5,6],
244
- sym: [:one, :two, :three, :four, :five, :six],
245
- names: ['sameer', 'john', 'james', 'omisha', 'priyanka', 'shravan']
243
+ number: [1,2,3,4,5,6,Float::NAN],
244
+ sym: [:one, :two, :three, :four, :five, :six, :seven],
245
+ names: ['sameer', 'john', 'james', 'omisha', 'priyanka', 'shravan',nil]
246
246
  })
247
247
  end
248
248
 
@@ -267,6 +267,11 @@ describe "Arel-like syntax" do
267
267
  ).to eq(answer)
268
268
  end
269
269
 
270
+ let(:dv) { Daru::Vector.new([1,11,32,Float::NAN,nil]) }
271
+ it "handles empty data" do
272
+ expect(dv.where(dv.lt(14))).to eq(Daru::Vector.new([1,11]))
273
+ end
274
+
270
275
  it "does not give SystemStackError" do
271
276
  v = Daru::Vector.new [1]*300_000
272
277
  expect { v.where v.eq(1) }.not_to raise_error
@@ -276,45 +281,45 @@ describe "Arel-like syntax" do
276
281
  context Daru::Vector do
277
282
  context "non-categorical type" do
278
283
  before do
279
- @vector = Daru::Vector.new([2,5,1,22,51,4])
284
+ @vector = Daru::Vector.new([2,5,1,22,51,4,nil,Float::NAN])
280
285
  end
281
-
286
+
282
287
  it "accepts a simple single statement" do
283
288
  expect(@vector.where(@vector.lt(10))).to eq(
284
289
  Daru::Vector.new([2,5,1,4], index: Daru::Index.new([0,1,2,5])))
285
290
  end
286
-
291
+
287
292
  it "accepts somewhat complex operator chaining" do
288
293
  expect(@vector.where((@vector.lt(6) | @vector.eq(51)))).to eq(
289
294
  Daru::Vector.new([2,5,1,51,4], index: Daru::Index.new([0,1,2,4,5])))
290
295
  end
291
296
  end
292
-
297
+
293
298
  context "categorical type" do
294
299
  let(:dv) { Daru::Vector.new ['a', 'c', 'x', 'x', 'c'],
295
300
  categories: ['a', 'x', 'c'], type: :category }
296
-
301
+
297
302
  context "simple single statement" do
298
303
  subject { dv.where(dv.lt('x')) }
299
-
304
+
300
305
  it { is_expected.to be_a Daru::Vector }
301
306
  its(:type) { is_expected.to eq :category }
302
307
  its(:to_a) { is_expected.to eq ['a'] }
303
308
  its(:'index.to_a') { is_expected.to eq [0] }
304
309
  end
305
-
310
+
306
311
  context "complex operator chaining" do
307
312
  subject { dv.where((dv.lt('x') | dv.eq('c'))) }
308
-
313
+
309
314
  it { is_expected.to be_a Daru::Vector }
310
315
  its(:type) { is_expected.to eq :category }
311
316
  its(:to_a) { is_expected.to eq ['a', 'c', 'c'] }
312
317
  its(:'index.to_a') { is_expected.to eq [0, 1, 4] }
313
318
  end
314
-
319
+
315
320
  context "preserve categories" do
316
321
  subject { dv.where((dv.lt('x') | dv.eq('c'))) }
317
-
322
+
318
323
  it { is_expected.to be_a Daru::Vector }
319
324
  its(:type) { is_expected.to eq :category }
320
325
  its(:to_a) { is_expected.to eq ['a', 'c', 'c'] }
@@ -329,4 +334,14 @@ describe "Arel-like syntax" do
329
334
  end
330
335
  end
331
336
  end
337
+
338
+ describe "apply_where" do
339
+ context "matches regexp with block input" do
340
+ subject { dv.apply_where(dv.match /weeks/) { |x| "#{x.split.first.to_i * 7} days" } }
341
+
342
+ let(:dv) { Daru::Vector.new ['3 days', '5 weeks', '2 weeks'] }
343
+
344
+ it { is_expected.to eq(Daru::Vector.new ['3 days', '35 days', '14 days']) }
345
+ end
346
+ end
332
347
  end
@@ -0,0 +1,22 @@
1
+ require 'spec_helper'
2
+
3
+ describe '#error' do
4
+ context 'by default' do
5
+ it { expect { Daru.error('test') }.to output("test\n").to_stderr_from_any_process }
6
+ end
7
+
8
+ context 'when set to nil' do
9
+ before { Daru.error_stream = nil }
10
+ it { expect { Daru.error('test') }.not_to output('test').to_stderr_from_any_process }
11
+ end
12
+
13
+ context 'when set to instance of custom class' do
14
+ let(:custom_stream) { double(puts: nil) }
15
+ before { Daru.error_stream = custom_stream }
16
+
17
+ it 'calls puts' do
18
+ expect { Daru.error('test') }.not_to output('test').to_stderr_from_any_process
19
+ expect(custom_stream).to have_received(:puts).with('test')
20
+ end
21
+ end
22
+ end
@@ -145,8 +145,15 @@ describe Daru::DataFrame do
145
145
  end
146
146
 
147
147
  context "#initialize" do
148
+
149
+ it "initializes an empty DataFrame with no arguments" do
150
+ df = Daru::DataFrame.new
151
+ expect(df.nrows).to eq(0)
152
+ expect(df.ncols).to eq(0)
153
+ end
154
+
148
155
  context Daru::Index do
149
- it "initializes an empty DataFrame" do
156
+ it "initializes an empty DataFrame with empty source arg" do
150
157
  df = Daru::DataFrame.new({}, order: [:a, :b])
151
158
 
152
159
  expect(df.vectors).to eq(Daru::Index.new [:a, :b])
@@ -164,6 +171,13 @@ describe Daru::DataFrame do
164
171
  expect(df.a) .to eq([1,2,3,4,5].dv(:a, df.index))
165
172
  end
166
173
 
174
+ it "initializes from a Hash and preserves default order" do
175
+ df = Daru::DataFrame.new({b: [11,12,13,14,15], a: [1,2,3,4,5]},
176
+ index: [:one, :two, :three, :four, :five])
177
+
178
+ expect(df.vectors).to eq(Daru::Index.new [:b, :a])
179
+ end
180
+
167
181
  it "initializes from a Hash of Vectors" do
168
182
  va = Daru::Vector.new([1,2,3,4,5], index: [:one, :two, :three, :four, :five])
169
183
  vb = Daru::Vector.new([11,12,13,14,15], index: [:one, :two, :three, :four, :five])
@@ -221,7 +235,7 @@ describe Daru::DataFrame do
221
235
  df = Daru::DataFrame.new({b: [11,12,13,14,15], a: [1,2,3,4,5]})
222
236
 
223
237
  expect(df.index) .to eq(Daru::Index.new [0,1,2,3,4])
224
- expect(df.vectors).to eq(Daru::Index.new [:a, :b])
238
+ expect(df.vectors).to eq(Daru::Index.new [:b, :a])
225
239
  end
226
240
 
227
241
  it "aligns indexes properly" do
@@ -329,6 +343,14 @@ describe Daru::DataFrame do
329
343
  expect(df[:a]) .to eq(Daru::Vector.new([1,2,3,4,5]))
330
344
  end
331
345
 
346
+ it "allows creation of dataframe with a default order" do
347
+ arr_of_arrs_df = Daru::DataFrame.new([[1,2,3], [4,5,6], [7,8,9]])
348
+ arr_of_vectors_df = Daru::DataFrame.new([Daru::Vector.new([1,2,3]), Daru::Vector.new([4,5,6]), Daru::Vector.new([7,8,9])])
349
+
350
+ expect(arr_of_arrs_df.vectors.to_a).to eq([0,1,2])
351
+ expect(arr_of_vectors_df.vectors.to_a).to eq([0,1,2])
352
+ end
353
+
332
354
  it "raises error for incomplete DataFrame index" do
333
355
  expect {
334
356
  df = Daru::DataFrame.new({b: [11,12,13,14,15], a: [1,2,3,4,5],
@@ -516,12 +538,31 @@ describe Daru::DataFrame do
516
538
  index: [:one, :two, :three, :four, :five]))
517
539
  end
518
540
 
541
+ it "assigns new vector with default length if given just a value" do
542
+ @df[:d] = 1.0
543
+ expect(@df[:d]).to eq(Daru::Vector.new([1.0, 1.0, 1.0, 1.0, 1.0],
544
+ index: [:one, :two, :three, :four, :five], name: :d))
545
+ end
546
+
547
+ it "updates vector with default length if given just a value" do
548
+ @df[:c] = 1.0
549
+ expect(@df[:c]).to eq(Daru::Vector.new([1.0, 1.0, 1.0, 1.0, 1.0],
550
+ index: [:one, :two, :three, :four, :five], name: :c))
551
+ end
552
+
519
553
  it "appends an Array as a Daru::Vector" do
520
554
  @df[:d] = [69,99,108,85,49]
521
555
 
522
556
  expect(@df.d.class).to eq(Daru::Vector)
523
557
  end
524
558
 
559
+ it "appends an arbitrary enumerable as a Daru::Vector" do
560
+ @df[:d] = Set.new([69,99,108,85,49])
561
+
562
+ expect(@df[:d]).to eq(Daru::Vector.new([69, 99, 108, 85, 49],
563
+ index: [:one, :two, :three, :four, :five], name: :c))
564
+ end
565
+
525
566
  it "replaces an already present vector" do
526
567
  @df[:a] = [69,99,108,85,49].dv(nil, [:one, :two, :three, :four, :five])
527
568
 
@@ -697,6 +738,47 @@ describe Daru::DataFrame do
697
738
  }
698
739
  end
699
740
 
741
+ context "#insert_vector" do
742
+ subject(:data_frame) {
743
+ Daru::DataFrame.new({b: [11,12,13,14,15], a: [1,2,3,4,5],
744
+ c: [11,22,33,44,55]}, order: [:a, :b, :c],
745
+ index: [:one, :two, :three, :four, :five])
746
+ }
747
+
748
+ it "insert a new vector at the desired slot" do
749
+ df = Daru::DataFrame.new({
750
+ a: [1,2,3,4,5],
751
+ d: [710, 720, 730, 740, 750],
752
+ b: [11, 12, 13, 14, 15],
753
+ c: [11,22,33,44,55]}, order: [:a, :d, :b, :c],
754
+ index: [:one, :two, :three, :four, :five]
755
+ )
756
+ data_frame.insert_vector 1, :d, [710, 720, 730, 740, 750]
757
+ expect(subject).to eq df
758
+ end
759
+
760
+ it "raises error for data array being too big" do
761
+ expect {
762
+ source = (1..8).to_a
763
+ data_frame.insert_vector 1, :d, source
764
+ }.to raise_error(IndexError)
765
+ end
766
+
767
+ it "raises error for invalid index value" do
768
+ expect {
769
+ source = (1..5).to_a
770
+ data_frame.insert_vector 4, :d, source
771
+ }.to raise_error(ArgumentError)
772
+ end
773
+
774
+ it "raises error for invalid source type" do
775
+ expect {
776
+ source = 14
777
+ data_frame.insert_vector 3, :d, source
778
+ }.to raise_error(ArgumentError)
779
+ end
780
+ end
781
+
700
782
  context "#row[]=" do
701
783
  context Daru::Index do
702
784
  before :each do
@@ -1546,6 +1628,22 @@ describe Daru::DataFrame do
1546
1628
  }
1547
1629
  end
1548
1630
 
1631
+ context 'with mulitiindex DF' do
1632
+ subject(:data_frame) {
1633
+ Daru::DataFrame.new({b: [11,12,13], a: [1,2,3],
1634
+ c: [11,22,33]}, order: [:a, :b, :c],
1635
+ index: Daru::MultiIndex.from_tuples([[:one, :two], [:one, :three], [:two, :four]]))
1636
+ }
1637
+
1638
+ before { data_frame.add_row [100,200,300], [:two, :five] }
1639
+
1640
+ it { is_expected.to eq(Daru::DataFrame.new({
1641
+ b: [11,12,13,200], a: [1,2,3,100],
1642
+ c: [11,22,33,300]}, order: [:a, :b, :c],
1643
+ index: Daru::MultiIndex.from_tuples([[:one, :two], [:one, :three], [:two, :four], [:two, :five]])))
1644
+ }
1645
+ end
1646
+
1549
1647
  it "allows adding rows after making empty DF by specfying only order" do
1550
1648
  df = Daru::DataFrame.new({}, order: [:a, :b, :c])
1551
1649
  df.add_row [1,2,3]
@@ -1573,6 +1671,13 @@ describe Daru::DataFrame do
1573
1671
  it 'has synonym' do
1574
1672
  expect(@data_frame.first(2)).to eq(@data_frame.head(2))
1575
1673
  end
1674
+
1675
+ it 'works on DateTime indexes' do
1676
+ idx = Daru::DateTimeIndex.new(['2017-01-01', '2017-02-01', '2017-03-01'])
1677
+ df = Daru::DataFrame.new({col1: ['a', 'b', 'c']}, index: idx)
1678
+ first = Daru::DataFrame.new({col1: ['a']}, index: Daru::DateTimeIndex.new(['2017-01-01']))
1679
+ expect(df.head(1)).to eq(first)
1680
+ end
1576
1681
  end
1577
1682
 
1578
1683
  context "#last" do
@@ -1756,6 +1861,59 @@ describe Daru::DataFrame do
1756
1861
  end
1757
1862
  end
1758
1863
 
1864
+ describe 'uniq' do
1865
+ let(:df) do
1866
+ Daru::DataFrame.from_csv 'spec/fixtures/duplicates.csv'
1867
+ end
1868
+
1869
+ context 'with no args' do
1870
+ it do
1871
+ result = df.uniq
1872
+ expect(result.shape.first).to eq 30
1873
+ end
1874
+ end
1875
+
1876
+ context 'given a vector' do
1877
+ it do
1878
+ result = df.uniq("color")
1879
+ expect(result.shape.first).to eq 2
1880
+ end
1881
+ end
1882
+
1883
+ context 'given an array of vectors' do
1884
+ it do
1885
+ result = df.uniq("color", "director_name")
1886
+ expect(result.shape.first).to eq 29
1887
+ end
1888
+ end
1889
+ end
1890
+
1891
+ context '#rolling_fillna!' do
1892
+ subject do
1893
+ Daru::DataFrame.new({
1894
+ a: [1, 2, 3, nil, Float::NAN, nil, 1, 7],
1895
+ b: [:a, :b, nil, Float::NAN, nil, 3, 5, nil],
1896
+ c: ['a', Float::NAN, 3, 4, 3, 5, nil, 7]
1897
+ })
1898
+ end
1899
+
1900
+ context 'rolling_fillna! forwards' do
1901
+ before { subject.rolling_fillna!(:forward) }
1902
+ it { expect(subject.rolling_fillna!(:forward)).to eq(subject) }
1903
+ its(:'a.to_a') { is_expected.to eq [1, 2, 3, 3, 3, 3, 1, 7] }
1904
+ its(:'b.to_a') { is_expected.to eq [:a, :b, :b, :b, :b, 3, 5, 5] }
1905
+ its(:'c.to_a') { is_expected.to eq ['a', 'a', 3, 4, 3, 5, 5, 7] }
1906
+ end
1907
+
1908
+ context 'rolling_fillna! backwards' do
1909
+ before { subject.rolling_fillna!(:backward) }
1910
+ it { expect(subject.rolling_fillna!(:backward)).to eq(subject) }
1911
+ its(:'a.to_a') { is_expected.to eq [1, 2, 3, 1, 1, 1, 1, 7] }
1912
+ its(:'b.to_a') { is_expected.to eq [:a, :b, 3, 3, 3, 3, 5, 0] }
1913
+ its(:'c.to_a') { is_expected.to eq ['a', 3, 3, 4, 3, 5, 7, 7] }
1914
+ end
1915
+ end
1916
+
1759
1917
  context "#clone" do
1760
1918
  it "returns a view of the whole dataframe" do
1761
1919
  cloned = @data_frame.clone
@@ -2603,6 +2761,26 @@ describe Daru::DataFrame do
2603
2761
  end
2604
2762
  end
2605
2763
 
2764
+ context "#rename_vectors!" do
2765
+ before do
2766
+ @df = Daru::DataFrame.new({
2767
+ a: [1,2,3,4,5],
2768
+ b: [11,22,33,44,55],
2769
+ c: %w(a b c d e)
2770
+ })
2771
+ end
2772
+
2773
+ it "returns self as modified dataframe" do
2774
+ expect(@df.rename_vectors!(:a => :alpha)).to eq(@df)
2775
+ end
2776
+
2777
+ it "re-uses rename_vectors method" do
2778
+ name_map = { :a => :alpha, :c => :gamma }
2779
+ expect(@df).to receive(:rename_vectors).with(name_map)
2780
+ @df.rename_vectors! name_map
2781
+ end
2782
+ end
2783
+
2606
2784
  context "#rename_vectors" do
2607
2785
  before do
2608
2786
  @df = Daru::DataFrame.new({
@@ -2612,6 +2790,10 @@ describe Daru::DataFrame do
2612
2790
  })
2613
2791
  end
2614
2792
 
2793
+ it "returns Daru::Index" do
2794
+ expect(@df.rename_vectors(:a => :alpha)).to be_kind_of(Daru::Index)
2795
+ end
2796
+
2615
2797
  it "renames vectors using a hash map" do
2616
2798
  @df.rename_vectors :a => :alpha, :c => :gamma
2617
2799
  expect(@df.vectors.to_a).to eq([:alpha, :b, :gamma])
@@ -2667,6 +2849,11 @@ describe Daru::DataFrame do
2667
2849
  :a => [1,2,3,4,5]
2668
2850
  }, order: [:b, 'a', :a]))
2669
2851
  end
2852
+
2853
+ it 'raises ArgumentError if argument was not an index' do
2854
+ df = Daru::DataFrame.new([])
2855
+ expect { df.reindex_vectors([]) }.to raise_error(ArgumentError)
2856
+ end
2670
2857
  end
2671
2858
 
2672
2859
  context "#to_matrix" do
@@ -3050,9 +3237,34 @@ describe Daru::DataFrame do
3050
3237
  end
3051
3238
 
3052
3239
  context "#summary" do
3053
- it "produces a summary of data frame" do
3054
- expect(@data_frame.summary.match("#{@data_frame.name}")).to_not eq(nil)
3055
- expect(@df_mi.summary.match("#{@df_mi.name}")).to_not eq(nil)
3240
+ subject { df.summary }
3241
+
3242
+ context "DataFrame" do
3243
+ let(:df) { Daru::DataFrame.new({a: [1,2,5], b: [1,2,"string"]}, order: [:a, :b], index: [:one, :two, :three], name: 'frame') }
3244
+ it { is_expected.to eq %Q{
3245
+ |= frame
3246
+ | Number of rows: 3
3247
+ | Element:[a]
3248
+ | == a
3249
+ | n :3
3250
+ | non-missing:3
3251
+ | median: 2
3252
+ | mean: 2.6667
3253
+ | std.dev.: 2.0817
3254
+ | std.err.: 1.2019
3255
+ | skew: 0.2874
3256
+ | kurtosis: -2.3333
3257
+ | Element:[b]
3258
+ | == b
3259
+ | n :3
3260
+ | non-missing:3
3261
+ | factors: 1,2,string
3262
+ | mode: 1,2,string
3263
+ | Distribution
3264
+ | 1 1 100.00%
3265
+ | 2 1 100.00%
3266
+ | string 1 100.00%
3267
+ }.unindent }
3056
3268
  end
3057
3269
  end
3058
3270
 
@@ -3101,6 +3313,15 @@ describe Daru::DataFrame do
3101
3313
  it { expect(subject['a'].to_a).to eq [1, 2, 3] }
3102
3314
  it { expect(subject[:b].to_a).to eq [4, 5, 6] }
3103
3315
  end
3316
+
3317
+ context "preserves indices for dataframes with same index" do
3318
+ let(:index) { ['one','two','three'] }
3319
+ let(:df1) { Daru::DataFrame.new({ 'a' => [1, 2, 3], 'b' => [3, 4, 5] }, index: index) }
3320
+ let(:df2) { Daru::DataFrame.new({ 'c' => [4, 5, 6], 'd' => [7, 8, 9] }, index: index) }
3321
+ subject { df1.merge df2 }
3322
+
3323
+ its(:index) { is_expected.to eq Daru::Index.new(index) }
3324
+ end
3104
3325
  end
3105
3326
 
3106
3327
  context "#vector_by_calculation" do
@@ -3115,25 +3336,41 @@ describe Daru::DataFrame do
3115
3336
  end
3116
3337
  end
3117
3338
 
3339
+ context "group_by" do
3340
+ context "on a single row DataFrame" do
3341
+ let(:df){ Daru::DataFrame.new(city: %w[Kyiv], year: [2015], value: [1]) }
3342
+ it "returns a groupby object" do
3343
+ expect(df.group_by([:city])).to be_a(Daru::Core::GroupBy)
3344
+ end
3345
+ it "has the correct index" do
3346
+ expect(df.group_by([:city]).groups).to eq({["Kyiv"]=>[0]})
3347
+ end
3348
+ end
3349
+ end
3350
+
3118
3351
  context "#vector_sum" do
3119
3352
  before do
3120
- a1 = Daru::Vector.new [1, 2, 3, 4, 5, nil]
3121
- a2 = Daru::Vector.new [10, 10, 20, 20, 20, 30]
3122
- b1 = Daru::Vector.new [nil, 1, 1, 1, 1, 2]
3123
- b2 = Daru::Vector.new [2, 2, 2, nil, 2, 3]
3353
+ a1 = Daru::Vector.new [1, 2, 3, 4, 5, nil, nil]
3354
+ a2 = Daru::Vector.new [10, 10, 20, 20, 20, 30, nil]
3355
+ b1 = Daru::Vector.new [nil, 1, 1, 1, 1, 2, nil]
3356
+ b2 = Daru::Vector.new [2, 2, 2, nil, 2, 3, nil]
3124
3357
  @df = Daru::DataFrame.new({ :a1 => a1, :a2 => a2, :b1 => b1, :b2 => b2 })
3125
3358
  end
3126
3359
 
3127
3360
  it "calculates complete vector sum" do
3128
- expect(@df.vector_sum).to eq(Daru::Vector.new [nil, 15, 26, nil, 28, nil])
3361
+ expect(@df.vector_sum).to eq(Daru::Vector.new [nil, 15, 26, nil, 28, nil, nil])
3362
+ end
3363
+
3364
+ it "ignores nils if skipnil is true" do
3365
+ expect(@df.vector_sum skipnil: true).to eq(Daru::Vector.new [13, 15, 26, 25, 28, 35, 0])
3129
3366
  end
3130
3367
 
3131
3368
  it "calculates partial vector sum" do
3132
3369
  a = @df.vector_sum([:a1, :a2])
3133
3370
  b = @df.vector_sum([:b1, :b2])
3134
3371
 
3135
- expect(a).to eq(Daru::Vector.new [11, 12, 23, 24, 25, nil])
3136
- expect(b).to eq(Daru::Vector.new [nil, 3, 3, nil, 3, 5])
3372
+ expect(a).to eq(Daru::Vector.new [11, 12, 23, 24, 25, nil, nil])
3373
+ expect(b).to eq(Daru::Vector.new [nil, 3, 3, nil, 3, 5, nil])
3137
3374
  end
3138
3375
  end
3139
3376
 
@@ -3321,7 +3558,8 @@ describe Daru::DataFrame do
3321
3558
  ev_b = Daru::Vector.new [1, 1, 0]
3322
3559
  ev_c = Daru::Vector.new [0, 1, 1]
3323
3560
  df2 = Daru::DataFrame.new({
3324
- :_id => ev_id, 'a' => ev_a, 'b' => ev_b, 'c' => ev_c })
3561
+ :_id => ev_id, 'a' => ev_a, 'b' => ev_b, 'c' => ev_c },
3562
+ order: ['a', 'b', 'c', :_id])
3325
3563
 
3326
3564
  expect(df2).to eq(df)
3327
3565
  end
@@ -3505,6 +3743,41 @@ describe Daru::DataFrame do
3505
3743
  end
3506
3744
  end
3507
3745
 
3746
+ context '#reset_index' do
3747
+ context 'when Index' do
3748
+ subject do
3749
+ Daru::DataFrame.new(
3750
+ {'vals' => [1,2,3,4,5]},
3751
+ index: Daru::Index.new(%w[a b c d e], name: 'indices')
3752
+ ).reset_index
3753
+ end
3754
+
3755
+ it { is_expected.to eq Daru::DataFrame.new(
3756
+ 'indices' => %w[a b c d e],
3757
+ 'vals' => [1,2,3,4,5]
3758
+ )}
3759
+ end
3760
+
3761
+ context 'when MultiIndex' do
3762
+ subject do
3763
+ mi = Daru::MultiIndex.from_tuples([
3764
+ [0, 'a'], [0, 'b'], [1, 'a'], [1, 'b']
3765
+ ])
3766
+ mi.name = %w[nums alphas]
3767
+ Daru::DataFrame.new(
3768
+ {'vals' => [1,2,3,4]},
3769
+ index: mi
3770
+ ).reset_index
3771
+ end
3772
+
3773
+ it { is_expected.to eq Daru::DataFrame.new(
3774
+ 'nums' => [0,0,1,1],
3775
+ 'alphas' => %w[a b a b],
3776
+ 'vals' => [1,2,3,4]
3777
+ )}
3778
+ end
3779
+ end
3780
+
3508
3781
  context "#set_index" do
3509
3782
  before(:each) do
3510
3783
  @df = Daru::DataFrame.new({
@@ -3544,6 +3817,26 @@ describe Daru::DataFrame do
3544
3817
  jholu.set_index(:a)
3545
3818
  }.to raise_error(ArgumentError)
3546
3819
  end
3820
+
3821
+ it "sets multiindex if array is given" do
3822
+ df = Daru::DataFrame.new({
3823
+ a: %w[a a b b],
3824
+ b: [1, 2, 1, 2],
3825
+ c: %w[a b c d]
3826
+ })
3827
+ df.set_index(%i[a b])
3828
+ expected =
3829
+ Daru::DataFrame.new(
3830
+ { c: %w[a b c d] },
3831
+ index: Daru::MultiIndex.from_tuples(
3832
+ [['a', 1], ['a', 2], ['b', 1], ['b', 2]]
3833
+ )
3834
+ ).tap do |df|
3835
+ df.index.name = %i[a b]
3836
+ df
3837
+ end
3838
+ expect(df).to eq(expected)
3839
+ end
3547
3840
  end
3548
3841
 
3549
3842
  context "#concat" do
@@ -3644,7 +3937,7 @@ describe Daru::DataFrame do
3644
3937
  expect(df_union.index.to_a).to eq v1 + v2
3645
3938
  end
3646
3939
  end
3647
-
3940
+
3648
3941
  context '#inspect' do
3649
3942
  subject { df.inspect }
3650
3943
 
@@ -3667,6 +3960,41 @@ describe Daru::DataFrame do
3667
3960
  }.unindent}
3668
3961
  end
3669
3962
 
3963
+ context 'if index name is set' do
3964
+ context 'single index with name' do
3965
+ let(:df) { Daru::DataFrame.new({a: [1,2,3], b: [3,4,5], c: [6,7,8]},
3966
+ name: 'test')}
3967
+ before { df.index.name = 'index_name' }
3968
+ it { should == %Q{
3969
+ |#<Daru::DataFrame: test (3x3)>
3970
+ | index_name a b c
3971
+ | 0 1 3 6
3972
+ | 1 2 4 7
3973
+ | 2 3 5 8
3974
+ }.unindent}
3975
+ end
3976
+
3977
+ context 'MultiIndex with name' do
3978
+ let(:mi) { Daru::MultiIndex.new(
3979
+ levels: [[:a,:b,:c], [:one, :two]],
3980
+ labels: [[0,0,1,1,2,2], [0,1,0,1,0,1]], name: ['s1', 's2']) }
3981
+ let(:df) { Daru::DataFrame.new({
3982
+ a: [11, 12, 13, 14, 15, 16], b: [21, 22, 23, 24, 25, 26]},
3983
+ name: 'test', index: mi)}
3984
+ it { should == %Q{
3985
+ |#<Daru::DataFrame: test (6x2)>
3986
+ | s1 s2 a b
3987
+ | a one 11 21
3988
+ | two 12 22
3989
+ | b one 13 23
3990
+ | two 14 24
3991
+ | c one 15 25
3992
+ | two 16 26
3993
+ }.unindent}
3994
+ end
3995
+
3996
+ end
3997
+
3670
3998
  context 'no name' do
3671
3999
  let(:df) { Daru::DataFrame.new({a: [1,2,3], b: [3,4,5], c: [6,7,8]})}
3672
4000
  it { should == %Q{
@@ -3709,6 +4037,21 @@ describe Daru::DataFrame do
3709
4037
  | 12 1 1 1
3710
4038
  | 13 1 1 1
3711
4039
  | 14 1 1 1
4040
+ | 15 1 1 1
4041
+ | 16 1 1 1
4042
+ | 17 1 1 1
4043
+ | 18 1 1 1
4044
+ | 19 1 1 1
4045
+ | 20 1 1 1
4046
+ | 21 1 1 1
4047
+ | 22 1 1 1
4048
+ | 23 1 1 1
4049
+ | 24 1 1 1
4050
+ | 25 1 1 1
4051
+ | 26 1 1 1
4052
+ | 27 1 1 1
4053
+ | 28 1 1 1
4054
+ | 29 1 1 1
3712
4055
  | ... ... ... ...
3713
4056
  }.unindent}
3714
4057
  end
@@ -3764,8 +4107,18 @@ describe Daru::DataFrame do
3764
4107
  end
3765
4108
 
3766
4109
  context '#to_s' do
3767
- it 'produces something, despite of how reasonable you think it is' do
3768
- expect(@data_frame.to_s).to eq @data_frame.to_html
4110
+ it 'produces a class, size description' do
4111
+ expect(@data_frame.to_s).to eq "#<Daru::DataFrame(5x3)>"
4112
+ end
4113
+
4114
+ it 'produces a class, name, size description' do
4115
+ @data_frame.name = "Test"
4116
+ expect(@data_frame.to_s).to eq "#<Daru::DataFrame: Test(5x3)>"
4117
+ end
4118
+
4119
+ it 'produces a class, name, size description when the name is a symbol' do
4120
+ @data_frame.name = :Test
4121
+ expect(@data_frame.to_s).to eq "#<Daru::DataFrame: Test(5x3)>"
3769
4122
  end
3770
4123
  end
3771
4124
 
@@ -3824,6 +4177,102 @@ describe Daru::DataFrame do
3824
4177
  end
3825
4178
  end
3826
4179
 
4180
+ context '#access_row_tuples_by_indexs' do
4181
+ let(:df) {
4182
+ Daru::DataFrame.new({col: [:a, :b, :c, :d, :e], num: [52,12,07,17,01]}) }
4183
+ let(:df_idx) {
4184
+ Daru::DataFrame.new({a: [52, 12, 07], b: [1, 2, 3]}, index: [:one, :two, :three])
4185
+ }
4186
+ let (:mi_idx) do
4187
+ Daru::MultiIndex.from_tuples [
4188
+ [:a,:one,:bar],
4189
+ [:a,:one,:baz],
4190
+ [:b,:two,:bar],
4191
+ [:a,:two,:baz],
4192
+ ]
4193
+ end
4194
+ let (:df_mi) do
4195
+ Daru::DataFrame.new({
4196
+ a: 1..4,
4197
+ b: 'a'..'d'
4198
+ }, index: mi_idx )
4199
+ end
4200
+ context 'when no index is given' do
4201
+ it 'returns empty Array' do
4202
+ expect(df.access_row_tuples_by_indexs()).to eq([])
4203
+ end
4204
+ end
4205
+ context 'when index(s) are given' do
4206
+ it 'returns Array of row tuples' do
4207
+ expect(df.access_row_tuples_by_indexs(1)).to eq([[:b, 12]])
4208
+ expect(df.access_row_tuples_by_indexs(0,3)).to eq([[:a, 52], [:d, 17]])
4209
+ end
4210
+ end
4211
+ context 'when custom index(s) are given' do
4212
+ it 'returns Array of row tuples' do
4213
+ expect(df_idx.access_row_tuples_by_indexs(:one,:three)).to eq(
4214
+ [[52, 1], [7, 3]]
4215
+ )
4216
+ end
4217
+ end
4218
+ context 'when multi index is given' do
4219
+ it 'returns Array of row tuples' do
4220
+ expect(df_mi.access_row_tuples_by_indexs(:a)).to eq(
4221
+ [[1, "a"], [2, "b"], [4, "d"]]
4222
+ )
4223
+ expect(df_mi.access_row_tuples_by_indexs(:a, :one, :baz)).to eq(
4224
+ [[2, "b"]]
4225
+ )
4226
+ end
4227
+ end
4228
+ end
4229
+
4230
+ context '#aggregate' do
4231
+ let(:cat_idx) { Daru::CategoricalIndex.new [:a, :b, :a, :a, :c] }
4232
+ let(:df) { Daru::DataFrame.new(num: [52,12,07,17,01], cat_index: cat_idx) }
4233
+ let(:df_cat_idx) {
4234
+ Daru::DataFrame.new({num: [52,12,07,17,01]}, index: cat_idx) }
4235
+
4236
+ it 'lambda function on particular column' do
4237
+ expect(df.aggregate(num_100_times: ->(df) { (df.num*100).first })).to eq(
4238
+ Daru::DataFrame.new(num_100_times: [5200, 1200, 700, 1700, 100])
4239
+ )
4240
+ end
4241
+ it 'aggregate sum on particular column' do
4242
+ expect(df_cat_idx.aggregate(num: :sum)).to eq(
4243
+ Daru::DataFrame.new({num: [76, 12, 1]}, index: [:a, :b, :c])
4244
+ )
4245
+ end
4246
+ end
4247
+
4248
+ context '#group_by_and_aggregate' do
4249
+ let(:spending_df) {
4250
+ Daru::DataFrame.rows([
4251
+ [2010, 'dev', 50, 1],
4252
+ [2010, 'dev', 150, 1],
4253
+ [2010, 'dev', 200, 1],
4254
+ [2011, 'dev', 50, 1],
4255
+ [2012, 'dev', 150, 1],
4256
+
4257
+ [2011, 'office', 300, 1],
4258
+
4259
+ [2010, 'market', 50, 1],
4260
+ [2011, 'market', 500, 1],
4261
+ [2012, 'market', 500, 1],
4262
+ [2012, 'market', 300, 1],
4263
+
4264
+ [2012, 'R&D', 10, 1],],
4265
+ order: [:year, :category, :spending, :nb_spending])
4266
+ }
4267
+
4268
+ it 'works as group_by + aggregate' do
4269
+ expect(spending_df.group_by_and_aggregate(:year, {spending: :sum})).to eq(
4270
+ spending_df.group_by(:year).aggregate(spending: :sum))
4271
+ expect(spending_df.group_by_and_aggregate([:year, :category], spending: :sum, nb_spending: :size)).to eq(
4272
+ spending_df.group_by([:year, :category]).aggregate(spending: :sum, nb_spending: :size))
4273
+ end
4274
+ end
4275
+
3827
4276
  context '#create_sql' do
3828
4277
  let(:df) { Daru::DataFrame.new({
3829
4278
  a: [1,2,3],
@@ -3839,4 +4288,12 @@ describe Daru::DataFrame do
3839
4288
  | c DATE) CHARACTER SET=UTF8;
3840
4289
  }.unindent}
3841
4290
  end
4291
+
4292
+ context "#by_single_key" do
4293
+ let(:df) { Daru::DataFrame.new(a: [1, 2, 3], b: [4, 5, 6] ) }
4294
+
4295
+ it 'raise error when vector is missing from dataframe' do
4296
+ expect { df[:c] }.to raise_error(IndexError, /Specified vector c does not exist/)
4297
+ end
4298
+ end
3842
4299
  end if mri?