daru 0.1.5 → 0.1.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (81) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +20 -7
  3. data/CONTRIBUTING.md +1 -1
  4. data/History.md +48 -1
  5. data/README.md +3 -3
  6. data/benchmarks/statistics.rb +6 -6
  7. data/benchmarks/where_clause.rb +1 -1
  8. data/benchmarks/where_vs_filter.rb +1 -1
  9. data/daru.gemspec +3 -2
  10. data/lib/daru.rb +14 -6
  11. data/lib/daru/accessors/gsl_wrapper.rb +1 -1
  12. data/lib/daru/accessors/nmatrix_wrapper.rb +2 -0
  13. data/lib/daru/category.rb +1 -1
  14. data/lib/daru/core/group_by.rb +32 -15
  15. data/lib/daru/core/query.rb +4 -4
  16. data/lib/daru/dataframe.rb +196 -48
  17. data/lib/daru/date_time/index.rb +7 -5
  18. data/lib/daru/formatters/table.rb +1 -0
  19. data/lib/daru/index/index.rb +121 -33
  20. data/lib/daru/index/multi_index.rb +83 -3
  21. data/lib/daru/io/csv/converters.rb +18 -0
  22. data/lib/daru/io/io.rb +80 -11
  23. data/lib/daru/io/sql_data_source.rb +10 -0
  24. data/lib/daru/iruby/templates/dataframe.html.erb +3 -50
  25. data/lib/daru/iruby/templates/dataframe_mi.html.erb +3 -56
  26. data/lib/daru/iruby/templates/dataframe_mi_tbody.html.erb +35 -0
  27. data/lib/daru/iruby/templates/dataframe_mi_thead.html.erb +21 -0
  28. data/lib/daru/iruby/templates/dataframe_tbody.html.erb +28 -0
  29. data/lib/daru/iruby/templates/dataframe_thead.html.erb +21 -0
  30. data/lib/daru/iruby/templates/vector.html.erb +3 -25
  31. data/lib/daru/iruby/templates/vector_mi.html.erb +3 -34
  32. data/lib/daru/iruby/templates/vector_mi_tbody.html.erb +26 -0
  33. data/lib/daru/iruby/templates/vector_mi_thead.html.erb +8 -0
  34. data/lib/daru/iruby/templates/vector_tbody.html.erb +17 -0
  35. data/lib/daru/iruby/templates/vector_thead.html.erb +8 -0
  36. data/lib/daru/maths/statistics/dataframe.rb +9 -11
  37. data/lib/daru/maths/statistics/vector.rb +139 -32
  38. data/lib/daru/plotting/gruff/dataframe.rb +13 -15
  39. data/lib/daru/plotting/nyaplot/category.rb +1 -1
  40. data/lib/daru/plotting/nyaplot/dataframe.rb +4 -4
  41. data/lib/daru/plotting/nyaplot/vector.rb +1 -2
  42. data/lib/daru/vector.rb +169 -80
  43. data/lib/daru/version.rb +1 -1
  44. data/spec/category_spec.rb +19 -19
  45. data/spec/core/group_by_spec.rb +47 -0
  46. data/spec/core/query_spec.rb +55 -50
  47. data/spec/daru_spec.rb +22 -0
  48. data/spec/dataframe_spec.rb +118 -6
  49. data/spec/date_time/index_spec.rb +34 -16
  50. data/spec/extensions/rserve_spec.rb +1 -1
  51. data/spec/fixtures/boolean_converter_test.csv +5 -0
  52. data/spec/fixtures/eciresults.html +394 -0
  53. data/spec/fixtures/empty_rows_test.csv +17 -0
  54. data/spec/fixtures/macau.html +3691 -0
  55. data/spec/fixtures/macd_data.csv +150 -0
  56. data/spec/fixtures/moneycontrol.html +6812 -0
  57. data/spec/fixtures/url_test.txt~ +0 -0
  58. data/spec/fixtures/valid_markup.html +62 -0
  59. data/spec/fixtures/wiki_climate.html +1243 -0
  60. data/spec/fixtures/wiki_table_info.html +631 -0
  61. data/spec/formatters/table_formatter_spec.rb +29 -0
  62. data/spec/index/categorical_index_spec.rb +33 -33
  63. data/spec/index/index_spec.rb +134 -41
  64. data/spec/index/multi_index_spec.rb +115 -31
  65. data/spec/io/io_spec.rb +201 -0
  66. data/spec/io/sql_data_source_spec.rb +31 -41
  67. data/spec/iruby/dataframe_spec.rb +17 -19
  68. data/spec/iruby/vector_spec.rb +26 -28
  69. data/spec/maths/statistics/vector_spec.rb +136 -14
  70. data/spec/plotting/gruff/category_spec.rb +3 -3
  71. data/spec/plotting/gruff/dataframe_spec.rb +14 -4
  72. data/spec/plotting/gruff/vector_spec.rb +9 -9
  73. data/spec/plotting/nyaplot/category_spec.rb +5 -9
  74. data/spec/plotting/nyaplot/dataframe_spec.rb +72 -47
  75. data/spec/plotting/nyaplot/vector_spec.rb +5 -11
  76. data/spec/shared/vector_display_spec.rb +12 -14
  77. data/spec/spec_helper.rb +21 -0
  78. data/spec/support/matchers.rb +5 -0
  79. data/spec/vector_spec.rb +222 -72
  80. metadata +68 -23
  81. data/spec/fixtures/stock_data.csv +0 -500
@@ -5,62 +5,52 @@ require 'active_record'
5
5
 
6
6
  RSpec.describe Daru::IO::SqlDataSource do
7
7
  include_context 'with accounts table in sqlite3 database'
8
- let(:dbi_handle) do
9
- DBI.connect("DBI:SQLite3:#{db_name}")
8
+
9
+ let(:query) do
10
+ 'select * from accounts'
10
11
  end
11
12
 
12
- let(:active_record_connection) do
13
+ let(:source) do
13
14
  ActiveRecord::Base.establish_connection("sqlite3:#{db_name}")
14
15
  ActiveRecord::Base.connection
15
16
  end
16
17
 
17
- let(:query) do
18
- 'select * from accounts'
19
- end
20
-
21
18
  describe '.make_dataframe' do
22
- context 'with DBI::DatabaseHandle' do
23
- it 'returns a dataframe' do
24
- result = Daru::IO::SqlDataSource.make_dataframe(dbi_handle, query)
25
- expect(result).to be_a(Daru::DataFrame)
26
- expect(result.nrows).to eq(2)
27
- expect(result.row[0][:id]).to eq(1)
28
- expect(result.row[0][:name]).to eq('Homer')
29
- end
19
+ subject(:df) { Daru::IO::SqlDataSource.make_dataframe(source, query) }
30
20
 
31
- context 'with an object not a string as a query' do
32
- it 'raises ArgumentError' do
33
- expect {
34
- Daru::IO::SqlDataSource.make_dataframe(dbi_handle, Object.new)
35
- }.to raise_error(ArgumentError)
36
- end
37
- end
21
+ context 'with DBI::DatabaseHandle' do
22
+ let(:source) { DBI.connect("DBI:SQLite3:#{db_name}") }
23
+ it { is_expected.to be_a(Daru::DataFrame) }
24
+ it { expect(df.row[0]).to have_attributes(id: 1, age: 20) }
25
+ its(:nrows) { is_expected.to eq 2 }
38
26
  end
39
27
 
40
28
  context 'with ActiveRecord::Connection' do
41
- it 'returns a dataframe' do
42
- result = Daru::IO::SqlDataSource.make_dataframe(active_record_connection, query)
43
- expect(result).to be_a(Daru::DataFrame)
44
- expect(result.nrows).to eq(2)
45
- expect(result.row[0][:id]).to eq(1)
46
- expect(result.row[0][:name]).to eq('Homer')
47
- end
29
+ it { is_expected.to be_a(Daru::DataFrame) }
30
+ it { expect(df.row[0]).to have_attributes(id: 1, age: 20) }
31
+ its(:nrows) { is_expected.to eq 2 }
32
+ end
48
33
 
49
- context 'with an object not a string as a query' do
50
- it 'raises ArgumentError' do
51
- expect {
52
- Daru::IO::SqlDataSource.make_dataframe(active_record_connection, Object.new)
53
- }.to raise_error(ArgumentError)
54
- end
55
- end
34
+ context 'with path to sqlite3 file' do
35
+ let(:source) { db_name }
36
+ it { is_expected.to be_a(Daru::DataFrame) }
37
+ it { expect(df.row[0]).to have_attributes(id: 1, age: 20) }
38
+ its(:nrows) { is_expected.to eq 2 }
39
+ end
40
+
41
+ context 'with an object not a string as a query' do
42
+ let(:query) { Object.new }
43
+ it { expect { df }.to raise_error(ArgumentError) }
56
44
  end
57
45
 
58
46
  context 'with an object not a database connection' do
59
- it 'raises ArgumentError' do
60
- expect {
61
- Daru::IO::SqlDataSource.make_dataframe(Object.new, query)
62
- }.to raise_error(ArgumentError)
63
- end
47
+ let(:source) { Object.new }
48
+ it { expect { df }.to raise_error(ArgumentError) }
49
+ end
50
+
51
+ context 'with path to unsupported db file' do
52
+ let(:source) { 'spec/fixtures/bank2.dat' }
53
+ it { expect { df }.to raise_error(ArgumentError) }
64
54
  end
65
55
  end
66
56
  end
@@ -1,7 +1,7 @@
1
1
  describe Daru::DataFrame, '#to_html' do
2
2
  let(:doc) { Nokogiri::HTML(df.to_html) }
3
3
  subject(:table) { doc.at('table') }
4
- let(:header) { table.at('tr:first-child > th:first-child') }
4
+ let(:header) { doc.at('b')}
5
5
  let(:name) { 'test' }
6
6
 
7
7
  let(:splitted_row) { row.inner_html.scan(/<t[dh].+?<\/t[dh]>/) }
@@ -13,18 +13,17 @@ describe Daru::DataFrame, '#to_html' do
13
13
  subject { header }
14
14
 
15
15
  it { is_expected.not_to be_nil }
16
- its(['colspan']) { is_expected.to eq (df.ncols + 1).to_s }
17
- its(:text) { is_expected.to eq "Daru::DataFrame: test (3x3)" }
16
+ its(:text) { is_expected.to eq " Daru::DataFrame: test (3x3) " }
18
17
 
19
18
  context 'without name' do
20
19
  let(:name) { nil }
21
20
 
22
- its(:text) { is_expected.to eq "Daru::DataFrame(3x3)" }
21
+ its(:text) { is_expected.to eq " Daru::DataFrame(3x3) " }
23
22
  end
24
23
  end
25
24
 
26
25
  describe 'column headers' do
27
- subject(:columns) { table.search('tr:nth-child(2) th').map(&:text) }
26
+ subject(:columns) { table.search('tr:nth-child(1) th').map(&:text) }
28
27
  its(:size) { is_expected.to eq df.ncols + 1 }
29
28
  it { is_expected.to eq ['', 'a', 'b', 'c'] }
30
29
  end
@@ -34,7 +33,7 @@ describe Daru::DataFrame, '#to_html' do
34
33
 
35
34
  subject { splitted_row }
36
35
  describe 'first row' do
37
- let(:row) { table.search('tr:nth-child(2)') }
36
+ let(:row) { table.search('thead > tr:nth-child(1)') }
38
37
 
39
38
  it { is_expected.to eq [
40
39
  '<th rowspan="2"></th>',
@@ -44,7 +43,7 @@ describe Daru::DataFrame, '#to_html' do
44
43
  end
45
44
 
46
45
  describe 'next row' do
47
- let(:row) { table.search('tr:nth-child(3)') }
46
+ let(:row) { table.search('thead > tr:nth-child(2)') }
48
47
 
49
48
  it { is_expected.to eq [
50
49
  '<th colspan="1">foo</th>',
@@ -62,7 +61,7 @@ describe Daru::DataFrame, '#to_html' do
62
61
 
63
62
  describe 'values' do
64
63
  subject(:values) {
65
- table.search('tr')[2..-1]
64
+ table.search('tr')[1..-1]
66
65
  .map { |tr| tr.search('td')[1..-1].map(&:text) }
67
66
  }
68
67
  its(:count) { is_expected.to eq df.nrows }
@@ -76,21 +75,21 @@ describe Daru::DataFrame, '#to_html' do
76
75
  describe 'header' do
77
76
  subject { header }
78
77
 
79
- its(:text) { is_expected.to eq "Daru::DataFrame: test (300x3)" }
78
+ its(:text) { is_expected.to eq " Daru::DataFrame: test (300x3) " }
80
79
  end
81
80
 
82
- it 'has only 30 rows (+ 2 header rows, + 2 finishing rows)' do
83
- expect(table.search('tr').size).to eq 34
81
+ it 'has only 30 rows (+ 1 header rows, + 2 finishing rows)' do
82
+ expect(table.search('tr').size).to eq 33
84
83
  end
85
84
 
86
85
  describe '"skipped" row' do
87
- subject(:row) { table.search('tr:nth-child(33) td').map(&:text) }
86
+ subject(:row) { table.search('tr:nth-child(31) td').map(&:text) }
88
87
  its(:count) { is_expected.to eq df.ncols + 1 }
89
88
  it { is_expected.to all eq '...' }
90
89
  end
91
90
 
92
91
  describe 'last row' do
93
- subject(:row) { table.search('tr:nth-child(34) td').map(&:text) }
92
+ subject(:row) { table.search('tr:nth-child(32) td').map(&:text) }
94
93
  its(:count) { is_expected.to eq df.ncols + 1 }
95
94
  it { is_expected.to eq ['299', *df.row[-1].map(&:to_s)] }
96
95
  end
@@ -119,12 +118,11 @@ describe Daru::DataFrame, '#to_html' do
119
118
  subject { header }
120
119
 
121
120
  it { is_expected.not_to be_nil }
122
- its(['colspan']) { is_expected.to eq (df.ncols + df.index.width).to_s }
123
- its(:text) { is_expected.to eq "Daru::DataFrame: test (7x2)" }
121
+ its(:text) { is_expected.to eq " Daru::DataFrame: test (7x2) " }
124
122
  end
125
123
 
126
124
  describe 'column headers' do
127
- let(:row) { table.search('tr:nth-child(2)') }
125
+ let(:row) { table.search('thead > tr:nth-child(1)') }
128
126
  subject { splitted_row }
129
127
 
130
128
  it { is_expected.to eq [
@@ -139,7 +137,7 @@ describe Daru::DataFrame, '#to_html' do
139
137
 
140
138
  subject { splitted_row }
141
139
  describe 'first row' do
142
- let(:row) { table.search('tr:nth-child(2)') }
140
+ let(:row) { table.search('thead > tr:nth-child(1)') }
143
141
 
144
142
  it { is_expected.to eq [
145
143
  '<th colspan="2" rowspan="2"></th>',
@@ -148,7 +146,7 @@ describe Daru::DataFrame, '#to_html' do
148
146
  end
149
147
 
150
148
  describe 'next row' do
151
- let(:row) { table.search('tr:nth-child(3)') }
149
+ let(:row) { table.search('thead > tr:nth-child(2)') }
152
150
 
153
151
  it { is_expected.to eq [
154
152
  '<th colspan="1">foo</th>',
@@ -158,7 +156,7 @@ describe Daru::DataFrame, '#to_html' do
158
156
  end
159
157
 
160
158
  describe 'first row' do
161
- let(:row) { table.search('tr:nth-child(3)') }
159
+ let(:row) { table.search('tbody > tr:nth-child(1)') }
162
160
  subject { splitted_row }
163
161
 
164
162
  it { is_expected.to eq [
@@ -2,65 +2,64 @@ describe Daru::Vector, '#to_html' do
2
2
  [nil, :category].each do |type|
3
3
  let(:doc) { Nokogiri::HTML(vector.to_html) }
4
4
  subject(:table) { doc.at('table') }
5
- let(:header) { table.at('tr:first-child > th:first-child') }
6
-
5
+ let(:header) { doc.at('b') }
6
+
7
7
  context 'simple' do
8
8
  let(:vector) { Daru::Vector.new [1,nil,3],
9
9
  index: [:a, :b, :c], name: 'test', type: type }
10
10
  it { is_expected.not_to be_nil }
11
-
11
+
12
12
  describe 'header' do
13
13
  subject { header }
14
14
  it { is_expected.not_to be_nil }
15
- its(['colspan']) { is_expected.to eq '2' }
16
- its(:text) { is_expected.to eq "Daru::Vector(3)"\
17
- "#{":category" if type == :category}" }
15
+ its(:text) { is_expected.to eq " Daru::Vector(3)"\
16
+ "#{":category" if type == :category} " }
18
17
  end
19
-
18
+
20
19
  describe 'name' do
21
- subject(:name) { table.at('tr:nth-child(2) > th:nth-child(2)') }
20
+ subject(:name) { table.at('tr:nth-child(1) > th:nth-child(2)') }
22
21
  it { is_expected.not_to be_nil }
23
22
  its(:text) { is_expected.to eq 'test' }
24
-
23
+
25
24
  context 'withought name' do
26
25
  let(:vector) { Daru::Vector.new [1,nil,3], index: [:a, :b, :c], type: type }
27
-
26
+
28
27
  it { is_expected.to be_nil }
29
28
  end
30
29
  end
31
-
30
+
32
31
  describe 'index' do
33
32
  subject(:indexes) { table.search('tr > td:first-child').map(&:text) }
34
33
  its(:count) { is_expected.to eq vector.size }
35
34
  it { is_expected.to eq vector.index.to_a.map(&:to_s) }
36
35
  end
37
-
36
+
38
37
  describe 'values' do
39
38
  subject(:indexes) { table.search('tr > td:last-child').map(&:text) }
40
39
  its(:count) { is_expected.to eq vector.size }
41
40
  it { is_expected.to eq vector.to_a.map(&:to_s) }
42
41
  end
43
42
  end
44
-
43
+
45
44
  context 'large vector' do
46
45
  subject(:vector) { Daru::Vector.new [1,2,3] * 100, name: 'test', type: type }
47
- it 'has only 30 rows (+ 2 header rows, + 2 finishing rows)' do
48
- expect(table.search('tr').size).to eq 34
46
+ it 'has only 30 rows (+ 1 header rows, + 2 finishing rows)' do
47
+ expect(table.search('tr').size).to eq 33
49
48
  end
50
-
49
+
51
50
  describe '"skipped" row' do
52
- subject(:row) { table.search('tr:nth-child(33) td').map(&:text) }
51
+ subject(:row) { table.search('tr:nth-child(31) td').map(&:text) }
53
52
  its(:count) { is_expected.to eq 2 }
54
53
  it { is_expected.to eq ['...', '...'] }
55
54
  end
56
-
55
+
57
56
  describe 'last row' do
58
- subject(:row) { table.search('tr:nth-child(34) td').map(&:text) }
57
+ subject(:row) { table.search('tr:nth-child(32) td').map(&:text) }
59
58
  its(:count) { is_expected.to eq 2 }
60
59
  it { is_expected.to eq ['299', '3'] }
61
60
  end
62
61
  end
63
-
62
+
64
63
  context 'multi-index' do
65
64
  subject(:vector) {
66
65
  Daru::Vector.new(
@@ -78,23 +77,22 @@ describe Daru::Vector, '#to_html' do
78
77
  ]),
79
78
  )
80
79
  }
81
-
80
+
82
81
  describe 'header' do
83
82
  subject { header }
84
83
  it { is_expected.not_to be_nil }
85
- its(['colspan']) { is_expected.to eq '3' }
86
- its(:text) { is_expected.to eq "Daru::Vector(7)"\
87
- "#{":category" if type == :category}" }
84
+ its(:text) { is_expected.to eq " Daru::Vector(7)"\
85
+ "#{":category" if type == :category} " }
88
86
  end
89
-
87
+
90
88
  describe 'name row' do
91
- subject(:row) { table.at('tr:nth-child(2)').search('th') }
89
+ subject(:row) { table.at('tr:nth-child(1)').search('th') }
92
90
  its(:count) { should == 2 }
93
91
  it { expect(row.first['colspan']).to eq '2' }
94
92
  end
95
-
93
+
96
94
  describe 'first data row' do
97
- let(:row) { table.at('tr:nth-child(3)') }
95
+ let(:row) { table.at('tbody > tr:first-child') }
98
96
  subject { row.inner_html.scan(/<t[dh].+?<\/t[dh]>/) }
99
97
  it { is_expected.to eq [
100
98
  '<th rowspan="3">foo</th>',
@@ -12,6 +12,116 @@ describe Daru::Vector do
12
12
  end
13
13
  end
14
14
 
15
+ let(:dv) { dv = Daru::Vector.new (["Tyrion", "Daenerys", "Jon Starkgaryen"]), index: Daru::Index.new([:t, :d, :j]) }
16
+
17
+ context "#max" do
18
+ it "returns max value" do
19
+ expect(dv.max).to eq("Tyrion")
20
+ end
21
+ it "returns N max values" do
22
+ expect(dv.max(2)).to eq(["Tyrion","Jon Starkgaryen"])
23
+ end
24
+ it "returns max value, sorted by comparitive block input" do
25
+ expect(dv.max { |a,b| a.size <=> b.size }).to eq("Jon Starkgaryen")
26
+ end
27
+ it "returns max value, sorted by object block input" do
28
+ expect(dv.max { |x| x.size }).to eq("Jon Starkgaryen")
29
+ end
30
+ it "returns N max values, sorted by comparitive block input" do
31
+ expect(dv.max(2) {|a,b| a.size <=> b.size}).to eq(["Jon Starkgaryen","Daenerys"])
32
+ end
33
+ it "returns N max values, sorted by object block input" do
34
+ expect(dv.max(2) {|x| x.size }).to eq(["Jon Starkgaryen","Daenerys"])
35
+ end
36
+ end
37
+
38
+ context "#index_of_max" do
39
+ it "returns index_of_max value" do
40
+ expect(dv.index_of_max).to eq(:t)
41
+ end
42
+ it "returns N index_of_max values" do
43
+ expect(dv.index_of_max(2)).to eq([:t, :j])
44
+ end
45
+ it "returns index_of_max value, sorted by comparitive block input" do
46
+ expect(dv.index_of_max { |a,b| a.size <=> b.size }).to eq(:j)
47
+ end
48
+ it "returns index_of_max value, sorted by object block input" do
49
+ expect(dv.index_of_max { |x| x.size }).to eq(:j)
50
+ end
51
+ it "returns N index_of_max values, sorted by comparitive block input" do
52
+ expect(dv.index_of_max(2) {|a,b| a.size <=> b.size}).to eq([:j, :d])
53
+ end
54
+ it "returns N index_of_max values, sorted by object block input" do
55
+ expect(dv.index_of_max(2) {|x| x.size }).to eq([:j, :d])
56
+ end
57
+ end
58
+
59
+ context "#min" do
60
+ it "returns min value" do
61
+ expect(dv.min).to eq("Daenerys")
62
+ end
63
+ it "returns N min values" do
64
+ expect(dv.min(2)).to eq(["Daenerys","Jon Starkgaryen"])
65
+ end
66
+ it "returns min value, sorted by comparitive block input" do
67
+ expect(dv.min { |a,b| a.size <=> b.size }).to eq("Tyrion")
68
+ end
69
+ it "returns min value, sorted by object block input" do
70
+ expect(dv.min { |x| x.size }).to eq("Tyrion")
71
+ end
72
+ it "returns N min values, sorted by comparitive block input" do
73
+ expect(dv.min(2) {|a,b| a.size <=> b.size}).to eq(["Tyrion","Daenerys"])
74
+ end
75
+ it "returns N min values, sorted by object block input" do
76
+ expect(dv.min(2) {|x| x.size }).to eq(["Tyrion","Daenerys"])
77
+ end
78
+ end
79
+
80
+ context "#index_of_min" do
81
+ it "returns index of min value" do
82
+ expect(dv.index_of_min).to eq(:d)
83
+ end
84
+ it "returns N index of min values" do
85
+ expect(dv.index_of_min(2)).to eq([:d, :j])
86
+ end
87
+ it "returns index of min value, sorted by comparitive block input" do
88
+ expect(dv.index_of_min { |a,b| a.size <=> b.size }).to eq(:t)
89
+ end
90
+ it "returns index of min value, sorted by object block input" do
91
+ expect(dv.index_of_min { |x| x.size }).to eq(:t)
92
+ end
93
+ it "returns N index of min values, sorted by comparitive block input" do
94
+ expect(dv.index_of_min(2) {|a,b| a.size <=> b.size}).to eq([:t, :d])
95
+ end
96
+ it "returns N index of min values, sorted by object block input" do
97
+ expect(dv.index_of_min(2) {|x| x.size }).to eq([:t, :d])
98
+ end
99
+ end
100
+
101
+ context "#max_by" do
102
+ it "tests alias of max_by to max" do
103
+ expect(dv.method(:max_by)).to eq(dv.method(:max))
104
+ end
105
+ end
106
+
107
+ context "#min_by" do
108
+ it "tests alias of min_by to min" do
109
+ expect(dv.method(:min_by)).to eq(dv.method(:min))
110
+ end
111
+ end
112
+
113
+ context "#index_of_max_by" do
114
+ it "tests alias of index_of_max_by to index_of_max" do
115
+ expect(dv.method(:index_of_max_by)).to eq(dv.method(:index_of_max))
116
+ end
117
+ end
118
+
119
+ context "#index_of_min_by" do
120
+ it "tests alias of index_of_min_by to index_of_min" do
121
+ expect(dv.method(:index_of_min_by)).to eq(dv.method(:index_of_min))
122
+ end
123
+ end
124
+
15
125
  context "#sum_of_squares" do
16
126
  it "calcs sum of squares, omits nil values" do
17
127
  v = Daru::Vector.new [1,2,3,4,5,6], dtype: dtype
@@ -583,26 +693,38 @@ describe Daru::Vector do
583
693
  end
584
694
  end
585
695
 
586
- context "#macd" do
587
- it "calculates moving average convergence divergence" do
588
- # MACD uses a lot more data than the other ones, so we need a bigger vector
589
- data = Daru::Vector.new(
590
- File.readlines("spec/fixtures/stock_data.csv").map(&:to_f))
696
+ RSpec.shared_examples 'correct macd' do |*args|
697
+ let(:source) { Daru::DataFrame.from_csv('spec/fixtures/macd_data.csv') }
591
698
 
592
- macd, signal = data.macd
699
+ # skip initial records during compare as ema is sensitive to
700
+ # period used.
701
+ # http://ta-lib.org/d_api/ta_setunstableperiod.html
702
+ let(:stability_offset) { 90 }
703
+ let(:delta) { 0.001 }
704
+ let(:desc) { args.empty? ? '12_26_9' : args.join('_') }
593
705
 
594
- # check the MACD
595
- expect(macd[-1]).to be_within(1e-6).of(3.12e-4)
596
- expect(macd[-10]).to be_within(1e-4).of(-1.07e-2)
597
- expect(macd[-20]).to be_within(1e-5).of(-5.65e-3)
706
+ subject { source['price'].macd(*args) }
598
707
 
599
- # check the signal
600
- expect(signal[-1]).to be_within(1e-5).of(-0.00628)
601
- expect(signal[-10]).to be_within(1e-5).of(-0.00971)
602
- expect(signal[-20]).to be_within(1e-5).of(-0.00338)
708
+ %w[ macd macdsig macdhist ].each_with_index do |field, i|
709
+ it do
710
+ act = subject[i][stability_offset..-1]
711
+ exp = source["#{field}_#{desc}"][stability_offset..-1]
712
+ expect(act).to be_all_within(delta).of(exp)
713
+ end
603
714
  end
604
715
  end
605
716
 
717
+ describe '#macd' do
718
+ context 'by default' do
719
+ it_should_behave_like 'correct macd'
720
+ end
721
+
722
+ context 'custom values for fast, slow, signal' do
723
+ it_should_behave_like 'correct macd', 6, 13, 4
724
+ end
725
+
726
+ end
727
+
606
728
  context "#cumsum" do
607
729
  it "calculates cumulative sum" do
608
730
  vector = Daru::Vector.new([1,2,3,4,5,6,7,8,9,10])