daru 0.1.5 → 0.1.6

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 (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])