bio-statsample-timeseries 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/test/helper.rb ADDED
@@ -0,0 +1,81 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ require 'minitest/unit'
11
+ require 'shoulda'
12
+ require 'shoulda-context'
13
+ require 'mocha'
14
+
15
+ require 'bio-statsample-timeseries'
16
+
17
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
18
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
19
+ require 'bio-statsample-timeseries'
20
+ module MiniTest
21
+ class Unit
22
+ class TestCase
23
+ include Shoulda::Context::Assertions
24
+ include Shoulda::Context::InstanceMethods
25
+ extend Shoulda::Context::ClassMethods
26
+ def self.should_with_gsl(name,&block)
27
+ should(name) do
28
+ if Statsample.has_gsl?
29
+ instance_eval(&block)
30
+ else
31
+ skip("Requires GSL")
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+
38
+ module Assertions
39
+ def assert_similar_vector(exp, obs, delta=1e-10,msg=nil)
40
+ msg||="Different vectors #{exp} - #{obs}"
41
+ assert_equal(exp.size, obs.size)
42
+ exp.data_with_nils.each_with_index {|v,i|
43
+ assert_in_delta(v,obs[i],delta)
44
+ }
45
+ end
46
+ def assert_equal_vector(exp,obs,delta=1e-10,msg=nil)
47
+ assert_equal(exp.size, obs.size, "Different size.#{msg}")
48
+ exp.size.times {|i|
49
+ assert_in_delta(exp[i],obs[i],delta, "Different element #{i}. \nExpected:\n#{exp}\nObserved:\n#{obs}.#{msg}")
50
+ }
51
+ end
52
+ def assert_equal_matrix(exp,obs,delta=1e-10,msg=nil)
53
+ assert_equal(exp.row_size, obs.row_size, "Different row size.#{msg}")
54
+ assert_equal(exp.column_size, obs.column_size, "Different column size.#{msg}")
55
+ exp.row_size.times {|i|
56
+ exp.column_size.times {|j|
57
+ assert_in_delta(exp[i,j],obs[i,j], delta, "Different element #{i},#{j}\nExpected:\n#{exp}\nObserved:\n#{obs}.#{msg}")
58
+ }
59
+ }
60
+ end
61
+ alias :assert_raise :assert_raises unless method_defined? :assert_raise
62
+ alias :assert_not_equal :refute_equal unless method_defined? :assert_not_equal
63
+ alias :assert_not_same :refute_same unless method_defined? :assert_not_same
64
+ unless method_defined? :assert_nothing_raised
65
+ def assert_nothing_raised(msg=nil)
66
+ msg||="Nothing should be raised, but raised %s"
67
+ begin
68
+ yield
69
+ not_raised=true
70
+ rescue Exception => e
71
+ not_raised=false
72
+ msg=sprintf(msg,e)
73
+ end
74
+ assert(not_raised,msg)
75
+ end
76
+ end
77
+ end
78
+ end
79
+
80
+ MiniTest::Unit.autorun
81
+
@@ -0,0 +1,176 @@
1
+ require(File.expand_path(File.dirname(__FILE__)+'/helper.rb'))
2
+
3
+ class StatsampleArimaSimulatorsTest < MiniTest::Unit::TestCase
4
+ def generate_acf(simulation)
5
+ ts = simulation.to_ts
6
+ ts.acf
7
+ end
8
+
9
+ def generate_pacf(simulation)
10
+ ts = simulation.to_ts
11
+ ts.pacf
12
+ end
13
+ context("AR(1) simulations") do
14
+ include Statsample::ARIMA
15
+
16
+ setup do
17
+ @series = ARIMA.new
18
+ @ar_1_positive = @series.ar_sim(1500, [0.9], 2)
19
+ @ar_1_negative = @series.ar_sim(1500, [-0.9], 2)
20
+
21
+ #generating acf
22
+ @positive_acf = generate_acf(@ar_1_positive)
23
+ @negative_acf = generate_acf(@ar_1_negative)
24
+
25
+ #generating pacf
26
+ @positive_pacf = generate_pacf(@ar_1_positive)
27
+ @negative_pacf = generate_pacf(@ar_1_negative)
28
+ end
29
+
30
+
31
+ should "have exponential decay of acf on positive side with phi > 0" do
32
+ @acf = @positive_acf
33
+ assert_equal @acf[0], 1.0
34
+ assert_operator @acf[1], :>=, 0.7
35
+ assert_operator @acf[@acf.size - 1], :<=, 0.2
36
+ #visualization:
37
+ #https://dl.dropboxusercontent.com/u/102071534/sciruby/AR%281%29_positive_phi_acf.png
38
+ #https://dl.dropboxusercontent.com/u/102071534/sciruby/AR%281%29_positive_phi_acf_line.png
39
+ end
40
+
41
+ should "have series with alternating sign on acf starting on negative side with phi < 0" do
42
+ @acf = @negative_acf
43
+ assert_equal @acf[0], 1.0
44
+ #testing for alternating series
45
+ assert_operator @acf[1], :<, 0
46
+ assert_operator @acf[2], :>, 0
47
+ assert_operator @acf[3], :<, 0
48
+ assert_operator @acf[4], :>, 0
49
+ #visualization:
50
+ #https://dl.dropboxusercontent.com/u/102071534/sciruby/AR%281%29_negative_phi_acf.png
51
+ #https://dl.dropboxusercontent.com/u/102071534/sciruby/AR%281%29_negative_phi_acf_line.png
52
+ end
53
+
54
+ should "have positive spike on pacf at lag 1 for phi > 0" do
55
+ @pacf = @positive_pacf
56
+ assert_operator @pacf[1], :>=, 0.7
57
+ assert_operator @pacf[2], :<=, 0.2
58
+ assert_operator @pacf[3], :<=, 0.14
59
+ #visualization:
60
+ #https://dl.dropboxusercontent.com/u/102071534/sciruby/AR%281%29_postive_phi_pacf.png
61
+ #https://dl.dropboxusercontent.com/u/102071534/sciruby/AR%281%29_postive_phi_pacf_line.png
62
+ end
63
+
64
+ should "have negative spike on pacf at lag 1 for phi < 0" do
65
+ @pacf = @negative_pacf
66
+ assert_operator @pacf[1], :<=, 0
67
+ assert_operator @pacf[1], :<=, -0.5
68
+ assert_operator @pacf[2], :>=, -0.5
69
+ #visualizaton:
70
+ #https://dl.dropboxusercontent.com/u/102071534/sciruby/AR%281%29_negative_phi_pacf.png
71
+ #[hided @pacf[0] = 1 to convey accurate picture]
72
+ end
73
+ end
74
+
75
+ context("AR(p) simulations") do
76
+ include Statsample::ARIMA
77
+
78
+ setup do
79
+ @series = ARIMA.new
80
+ @ar_p_positive = @series.ar_sim(1500, [0.3, 0.5], 2)
81
+ @ar_p_negative = @series.ar_sim(1500, [-0.3, -0.5], 2)
82
+ end
83
+
84
+
85
+ should "have damped sine wave starting on positive side on acf" do
86
+ @ar = @ar_p_positive
87
+ @acf = generate_acf(@ar)
88
+ assert_operator @acf[0], :>=, @acf[1]
89
+ assert_operator @acf[1], :>=, 0.0
90
+ #caution: sine curve can split on cartesian plane,
91
+ #visualization:
92
+ #https://dl.dropboxusercontent.com/u/102071534/sciruby/AR(p)_positive_phi_sine_wave.png
93
+ end
94
+
95
+ should "have damped sine wave starting on negative side on acf" do
96
+ @ar = @ar_p_negative
97
+ @acf = generate_acf(@ar)
98
+ assert_operator @acf[0], :>=, @acf[1]
99
+ assert_operator @acf[1], :<=, 0.0
100
+ assert_operator @acf[1], :>=, @acf[2]
101
+ #caution: sine curve can split on cartesian plane,
102
+ #visualization:
103
+ #https://dl.dropboxusercontent.com/u/102071534/sciruby/AR%28p%29_negative_phi_acf_sine_wave.png
104
+ end
105
+
106
+ should "have spikes from 1 to p for pacf" do
107
+ #here p = 2
108
+ @ar = @ar_p_positive
109
+ @pacf = generate_pacf(@ar)
110
+ assert_equal @pacf[0], 1.0
111
+ assert_operator @pacf[1], :>, @pacf[3]
112
+ assert_operator @pacf[1], :>, @pacf[4]
113
+ assert_operator @pacf[1], :>, @pacf[5]
114
+ assert_operator @pacf[2], :>, @pacf[3]
115
+ assert_operator @pacf[2], :>, @pacf[4]
116
+ #visualization:
117
+ #https://dl.dropboxusercontent.com/u/102071534/sciruby/AR(p)_positive_phi_pacf_spikes.png
118
+ end
119
+ end
120
+
121
+
122
+ context("MA(1) simulations") do
123
+ include Statsample::ARIMA
124
+ setup do
125
+ @series = ARIMA.new
126
+ @ma_positive = @series.ar_sim(1500, [0.5], 2)
127
+ @ma_negative = @series.ar_sim(1500, [-0.5], 2)
128
+ end
129
+
130
+ should "have one positive spike at lag 1 on acf at positive theta" do
131
+ @acf = generate_acf(@ma_positive)
132
+ assert_equal @acf[0], 1.0
133
+ assert_operator @acf[1], :>=, 0 #test if positive
134
+ #test if spike
135
+ assert_operator @acf[2], :>=, 0.1
136
+ assert_operator @acf[3], :<=, 0.2
137
+ assert_operator @acf[4], :<=, 0.2
138
+ #visualization:
139
+ #https://dl.dropboxusercontent.com/u/102071534/sciruby/MA%281%29_postive_acf.png
140
+ end
141
+
142
+ should "have one negative spike at lag 1 on acf at negative theta" do
143
+ @acf = generate_acf(@ma_negative)
144
+ assert_operator @acf[1], :<, 0
145
+ assert_operator @acf[2], :>=, @acf[1]
146
+ assert_operator @acf[3], :>=, @acf[1]
147
+ #visualization:
148
+ #https://dl.dropboxusercontent.com/u/102071534/sciruby/MA%281%29_negative_acf.png
149
+ #positive_vs_negative:
150
+ #https://dl.dropboxusercontent.com/u/102071534/sciruby/MA%281%29_acf_positive_vs_negative.png
151
+ end
152
+
153
+ end
154
+
155
+ context("MA(q) simulations") do
156
+ include Statsample::ARIMA
157
+ setup do
158
+ @series = ARIMA.new
159
+ @ma_positive = @series.ar_sim(1500, [0.5, 0.3, 0.2], 2)
160
+ @ma_negative = @series.ar_sim(1500, [-0.5], 2)
161
+ end
162
+
163
+ should "have q positive spikes at lag 1 to q on acf at positive thetas" do
164
+ @acf = generate_acf(@ma_positive)
165
+ assert_operator @acf[1], :>=, @acf[2]
166
+ assert_operator @acf[2], :>=, @acf[3]
167
+ assert_operator @acf[3], :>=, @acf[4]
168
+ #Visualization: http://jsfiddle.net/YeK2c/
169
+ end
170
+
171
+ should "have damped sine wave on pacf at positive thetas" do
172
+ #visualization: http://jsfiddle.net/7keHK/2/
173
+ end
174
+ end
175
+ end
176
+
data/test/test_pacf.rb ADDED
@@ -0,0 +1,52 @@
1
+ require(File.expand_path(File.dirname(__FILE__)+'/helper.rb'))
2
+ class StatsampleTimeSeriesPacfTestCase < MiniTest::Unit::TestCase
3
+ context(Statsample::TimeSeries) do
4
+ include Statsample::TimeSeries
5
+ setup do
6
+ @ts = (1..20).map { |x| x * 10 }.to_ts
7
+ #setting up a proc to get a closure for pacf calling with variable lags and methods
8
+ @pacf_proc =->(k, method) { @ts.pacf(k, method) }
9
+ end
10
+
11
+ should "return correct correct pacf size for lags = 5" do
12
+ assert_equal @pacf_proc.call(5, 'yw').size, 6
13
+ assert_equal @pacf_proc.call(5, 'mle').size, 6
14
+ #first element is 1.0
15
+ end
16
+
17
+ should "return correct correct pacf size for lags = 10" do
18
+ assert_equal @pacf_proc.call(10, 'yw').size, 11
19
+ assert_equal @pacf_proc.call(10, 'mle').size, 11
20
+ #first element is 1.0
21
+ end
22
+
23
+ should "have first element as 1.0" do
24
+ assert_equal @pacf_proc.call(10, 'yw')[0], 1.0
25
+ assert_equal @pacf_proc.call(10, 'mle')[0], 1.0
26
+ end
27
+
28
+ should "give correct pacf results for unbiased yule-walker" do
29
+ result_10 = [1.0, 0.8947368421052632, -0.10582010582010604, -0.11350188273265083, -0.12357534824820737, -0.13686534216335522, -0.15470588235294147, -0.17938011883732036, -0.2151192288178601, -0.2707082833133261, -0.3678160919540221]
30
+ result_5 = [1.0, 0.8947368421052632, -0.10582010582010604, -0.11350188273265083, -0.12357534824820737, -0.13686534216335522]
31
+ assert_equal @pacf_proc.call(10, 'yw'), result_10
32
+ assert_equal @pacf_proc.call(5, 'yw'), result_5
33
+
34
+ #Checking for lag = (1..10)
35
+ 1.upto(10) do |i|
36
+ assert_equal @pacf_proc.call(i, 'yw'), result_10[0..i]
37
+ end
38
+ end
39
+
40
+ should "give correct pacf results for mle yule-walker" do
41
+ result_10 = [1.0, 0.85, -0.07566212829370711, -0.07635069706072706, -0.07698628638512295, -0.07747034005560738, -0.0776780981161499, -0.07744984679625189, -0.0765803323191094, -0.07480650005932366, -0.07179435184923755]
42
+ result_5 = [1.0, 0.85, -0.07566212829370711, -0.07635069706072706, -0.07698628638512295, -0.07747034005560738]
43
+ assert_equal @pacf_proc.call(10, 'mle'), result_10
44
+ assert_equal @pacf_proc.call(5, 'mle'), result_5
45
+
46
+ #Checking for lag = (1..10)
47
+ 1.upto(10) do |i|
48
+ assert_equal @pacf_proc.call(i, 'mle'), result_10[0..i]
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,103 @@
1
+ require(File.expand_path(File.dirname(__FILE__)+'/helper.rb'))
2
+
3
+ class StatsampleTestTimeSeries < MiniTest::Unit::TestCase
4
+ include Statsample::Shorthand
5
+
6
+ # All calculations are compared to the output of the equivalent function in R
7
+
8
+ def setup
9
+ # daily closes of iShares XIU on the TSX
10
+ @xiu = Statsample::TimeSeries::TimeSeries.new [17.28, 17.45, 17.84, 17.74, 17.82, 17.85, 17.36, 17.3, 17.56, 17.49, 17.46, 17.4, 17.03, 17.01,
11
+ 16.86, 16.86, 16.56, 16.36, 16.66, 16.77], :scale
12
+ end
13
+
14
+ def test_acf
15
+ acf = @xiu.acf
16
+
17
+ assert_equal 14, acf.length
18
+
19
+ # test the first few autocorrelations
20
+ assert_in_delta 1.0, acf[0], 0.0001
21
+ assert_in_delta 0.852, acf[1], 0.001
22
+ assert_in_delta 0.669, acf[2], 0.001
23
+ assert_in_delta 0.486, acf[3], 0.001
24
+ end
25
+
26
+ def test_lag
27
+ #test of default lag
28
+ lag1 = @xiu.lag
29
+
30
+ assert_in_delta 16.66, lag1[lag1.size - 1], 0.001
31
+ assert_in_delta 16.36, lag1[lag1.size - 2], 0.001
32
+
33
+ #test with different lagging unit
34
+ lag2 = @xiu.lag(2)
35
+
36
+ assert_in_delta 16.36, lag2[lag2.size - 1], 0.001
37
+ assert_in_delta 16.56, lag2[lag2.size - 2], 0.001
38
+ end
39
+
40
+ def test_delta
41
+ diff = @xiu.diff
42
+
43
+ assert_in_delta 0.11, diff[@xiu.size - 1], 0.001
44
+ assert_in_delta 0.30, diff[@xiu.size - 2], 0.001
45
+ assert_in_delta -0.20, diff[@xiu.size - 3], 0.001
46
+ end
47
+
48
+ def test_ma
49
+ # test default
50
+ ma10 = @xiu.ma
51
+
52
+ assert_in_delta ma10[-1], 16.897, 0.001
53
+ assert_in_delta ma10[-5], 17.233, 0.001
54
+ assert_in_delta ma10[-10], 17.587, 0.001
55
+
56
+ # test with a different lookback period
57
+ ma5 = @xiu.ma 5
58
+
59
+ assert_in_delta ma5[-1], 16.642, 0.001
60
+ assert_in_delta ma5[-10], 17.434, 0.001
61
+ assert_in_delta ma5[-15], 17.74, 0.001
62
+ end
63
+
64
+ def test_ema
65
+ # test default
66
+ ema10 = @xiu.ema
67
+
68
+ assert_in_delta ema10[-1], 16.87187, 0.00001
69
+ assert_in_delta ema10[-5], 17.19187, 0.00001
70
+ assert_in_delta ema10[-10], 17.54918, 0.00001
71
+
72
+ # test with a different lookback period
73
+ ema5 = @xiu.ema 5
74
+
75
+ assert_in_delta ema5[-1], 16.71299, 0.0001
76
+ assert_in_delta ema5[-10], 17.49079, 0.0001
77
+ assert_in_delta ema5[-15], 17.70067, 0.0001
78
+
79
+ # test with a different smoother
80
+ ema_w = @xiu.ema 10, true
81
+
82
+ assert_in_delta ema_w[-1], 17.08044, 0.00001
83
+ assert_in_delta ema_w[-5], 17.33219, 0.00001
84
+ assert_in_delta ema_w[-10], 17.55810, 0.00001
85
+ end
86
+
87
+ def test_macd
88
+ # MACD uses a lot more data than the other ones, so we need a bigger vector
89
+ data = File.readlines(File.dirname(__FILE__) + "/fixtures/stock_data.csv").map(&:to_f).to_time_series
90
+
91
+ macd, signal = data.macd
92
+
93
+ # check the MACD
94
+ assert_in_delta 3.12e-4, macd[-1], 1e-6
95
+ assert_in_delta -1.07e-2, macd[-10], 1e-4
96
+ assert_in_delta -5.65e-3, macd[-20], 1e-5
97
+
98
+ # check the signal
99
+ assert_in_delta -0.00628, signal[-1], 1e-5
100
+ assert_in_delta -0.00971, signal[-10], 1e-5
101
+ assert_in_delta -0.00338, signal[-20], 1e-5
102
+ end
103
+ end
data/test/test_wald.rb ADDED
@@ -0,0 +1,71 @@
1
+ require(File.expand_path(File.dirname(__FILE__)+'/helper.rb'))
2
+
3
+ class StatsampleWaldTest < MiniTest::Unit::TestCase
4
+ # Wald test is useful to test a series of n acf with Chi-square
5
+ # degree of freedom. It is extremely useful to test fit the fit of
6
+ # an ARIMA model to test the residuals.
7
+
8
+ include Statsample::TimeSeries
9
+ include Statsample::Shorthand
10
+ include Distribution
11
+
12
+ def setup
13
+ #create time series to evaluate later
14
+ @wald = 100.times.map { rand(100) }.to_ts
15
+ end
16
+
17
+ def sum_of_squares_of_acf_series(lags)
18
+ #perform sum of squares for a series of acf with specified lags
19
+ acf = @wald.acf(lags)
20
+ return acf.map { |x| x ** 2 }.inject(:+)
21
+ end
22
+
23
+ def chisquare_cdf(sum_of_squares, lags)
24
+ 1 - ChiSquare.cdf(sum_of_squares, lags)
25
+ end
26
+
27
+
28
+ def test_wald_with_5_lags
29
+ #number of lags for acf = 5
30
+ lags = 5
31
+ sum_of_squares = sum_of_squares_of_acf_series(lags)
32
+ assert_in_delta chisquare_cdf(sum_of_squares, lags), 1, 0.05
33
+ assert_equal @wald.acf(lags).size, lags + 1
34
+ end
35
+
36
+
37
+ def test_wald_with_10_lags
38
+ #number of lags for acf = 10
39
+ lags = 10
40
+ sum_of_squares = sum_of_squares_of_acf_series(lags)
41
+ assert_in_delta chisquare_cdf(sum_of_squares, lags), 1, 0.05
42
+ assert_equal @wald.acf(lags).size, lags + 1
43
+ end
44
+
45
+
46
+ def test_wald_with_15_lags
47
+ #number of lags for acf = 15
48
+ lags = 15
49
+ sum_of_squares = sum_of_squares_of_acf_series(lags)
50
+ assert_in_delta chisquare_cdf(sum_of_squares, lags), 1, 0.05
51
+ assert_equal @wald.acf(lags).size, lags + 1
52
+ end
53
+
54
+
55
+ def test_wald_with_20_lags
56
+ #number of lags for acf = 20
57
+ lags = 20
58
+ sum_of_squares = sum_of_squares_of_acf_series(lags)
59
+ assert_in_delta chisquare_cdf(sum_of_squares, lags), 1, 0.05
60
+ assert_equal @wald.acf(lags).size, lags + 1
61
+ end
62
+
63
+
64
+ def test_wald_with_25_lags
65
+ #number of lags for acf = 25
66
+ lags = 25
67
+ sum_of_squares = sum_of_squares_of_acf_series(lags)
68
+ assert_in_delta chisquare_cdf(sum_of_squares, lags), 1, 0.05
69
+ assert_equal @wald.acf(lags).size, lags + 1
70
+ end
71
+ end