statsample-timeseries 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.travis.yml +13 -0
- data/Gemfile +22 -0
- data/LICENSE.txt +22 -0
- data/README.rdoc +72 -0
- data/Rakefile +47 -0
- data/VERSION +1 -0
- data/bin/bio-statsample-timeseries +74 -0
- data/features/acf.feature +31 -0
- data/features/pacf.feature +42 -0
- data/features/step_definitions/bio-statsample-timeseries_steps.rb +0 -0
- data/features/step_definitions/step_definitions.rb +37 -0
- data/features/step_definitions/step_definitions_acf.rb +8 -0
- data/features/support/env.rb +15 -0
- data/lib/statsample-timeseries.rb +18 -0
- data/lib/statsample-timeseries/arima.rb +246 -0
- data/lib/statsample-timeseries/arima/kalman.rb +148 -0
- data/lib/statsample-timeseries/arima/likelihood.rb +101 -0
- data/lib/statsample-timeseries/timeseries.rb +291 -0
- data/lib/statsample-timeseries/timeseries/pacf.rb +164 -0
- data/lib/statsample-timeseries/utility.rb +154 -0
- data/test/fixtures/stock_data.csv +500 -0
- data/test/helper.rb +81 -0
- data/test/test_arima_ks.rb +106 -0
- data/test/test_arima_simulators.rb +186 -0
- data/test/test_matrix.rb +92 -0
- data/test/test_pacf.rb +52 -0
- data/test/test_tseries.rb +103 -0
- data/test/test_wald.rb +71 -0
- metadata +273 -0
data/.document
ADDED
data/.travis.yml
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
language: ruby
|
2
|
+
rvm:
|
3
|
+
- 1.9.2
|
4
|
+
- 1.9.3
|
5
|
+
- 2.0.0
|
6
|
+
- jruby-19mode # JRuby in 1.9 mode
|
7
|
+
- rbx-19mode
|
8
|
+
# - 1.8.7
|
9
|
+
# - jruby-18mode # JRuby in 1.8 mode
|
10
|
+
# - rbx-18mode
|
11
|
+
|
12
|
+
# uncomment this line if your project needs to run something other than `rake`:
|
13
|
+
# script: bundle exec rspec spec
|
data/Gemfile
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
source "http://rubygems.org"
|
2
|
+
|
3
|
+
gem 'statsample', '=1.2.0'
|
4
|
+
|
5
|
+
# Add dependencies required to use your gem here.
|
6
|
+
# Example:
|
7
|
+
gem "activesupport", "= 3.2.10"
|
8
|
+
|
9
|
+
# Add dependencies to develop your gem here.
|
10
|
+
# Include everything needed to run rake, tests, features, etc.
|
11
|
+
group :development do
|
12
|
+
gem "shoulda", ">= 0"
|
13
|
+
gem "rdoc", "~> 3.12"
|
14
|
+
gem "minitest", "~> 4.7.5"
|
15
|
+
gem "cucumber", ">= 0"
|
16
|
+
gem "bundler", "~> 1.3.5"
|
17
|
+
gem "jeweler", "~> 1.8.4"
|
18
|
+
gem "bio", ">= 1.4.2"
|
19
|
+
gem "rdoc", "~> 3.12"
|
20
|
+
gem 'mocha', '~> 0.14.0'
|
21
|
+
gem 'gsl'
|
22
|
+
end
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
This version of Statsample-Timeseries is licensed under the BSD 2-clause license.
|
2
|
+
|
3
|
+
* http://sciruby.com
|
4
|
+
* http://github.com/sciruby/sciruby/wiki/License
|
5
|
+
|
6
|
+
You *must* read the Contributor Agreement before contributing code to the SciRuby Project. This is available online:
|
7
|
+
|
8
|
+
* http://github.com/sciruby/sciruby/wiki/Contributor-Agreement
|
9
|
+
|
10
|
+
-----
|
11
|
+
|
12
|
+
Copyright (c) 2010 - 2013, Ruby Science Foundation
|
13
|
+
All rights reserved.
|
14
|
+
|
15
|
+
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
16
|
+
|
17
|
+
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
18
|
+
|
19
|
+
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
20
|
+
|
21
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
22
|
+
|
data/README.rdoc
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
= statsample-timeseries
|
2
|
+
|
3
|
+
{<img
|
4
|
+
src="https://secure.travis-ci.org/AnkurGel/statsample-timeseries.png"
|
5
|
+
/>}[http://travis-ci.org/#!/AnkurGel/statsample-timeseries]
|
6
|
+
|
7
|
+
Statsample-Timeseries is an extension to [Statsample](https://github.com/clbustos/statsample), a suite of advance statistics in Ruby.
|
8
|
+
|
9
|
+
* {sciruby.com}[http://sciruby.com]
|
10
|
+
* {Google+}[https://plus.google.com/109304769076178160953/posts]
|
11
|
+
* {Ankur Goel}[http://ankurgoel.com]
|
12
|
+
* {Statsample}[https://github.com/SciRuby/statsample]
|
13
|
+
|
14
|
+
|
15
|
+
== Description
|
16
|
+
|
17
|
+
Statsample-Timeseries is extension of Statsample, and incorporates helpful timeseries functions, estimations and modules such as:
|
18
|
+
|
19
|
+
* ACF
|
20
|
+
* PACF
|
21
|
+
* ARIMA
|
22
|
+
* KalmanFilter
|
23
|
+
* LogLikelihood
|
24
|
+
* Autocovariances
|
25
|
+
* Moving Averages
|
26
|
+
|
27
|
+
Statsample-Timeseries was created by Ankur Goel as part of Google's Summer of Code 2013. It is the part of {SciRuby}[http://sciruby.com]
|
28
|
+
|
29
|
+
|
30
|
+
== Dependency
|
31
|
+
|
32
|
+
Please install [`rb-gsl`](http://rb-gsl.rubyforge.org/) which is a Ruby wrapper over GNU Scientific Library. It enables us to use various minimization techniques during estimations.
|
33
|
+
|
34
|
+
|
35
|
+
== Installation
|
36
|
+
|
37
|
+
gem install statsample-timeseries
|
38
|
+
|
39
|
+
== Usage
|
40
|
+
|
41
|
+
To use the library
|
42
|
+
|
43
|
+
require 'statsample-timeseries'
|
44
|
+
|
45
|
+
You can also go through the blog-posts on {my blog}[http://ankurgoel.com] for descriptive explanation and examples.
|
46
|
+
|
47
|
+
|
48
|
+
== Documentation
|
49
|
+
|
50
|
+
The API doc is {online}[http://rubygems.org/gems/statsample-timeseries]. For more code examples see also the test files in the source tree.
|
51
|
+
|
52
|
+
|
53
|
+
== Contributing
|
54
|
+
|
55
|
+
* Fork the project.
|
56
|
+
* Create your feature branch
|
57
|
+
* Add/Modify code.
|
58
|
+
* Write equivalent documentation and **tests**.
|
59
|
+
* Run `rake test` to verify that all test case passes.
|
60
|
+
* Push your branch.
|
61
|
+
* Pull request. :)
|
62
|
+
|
63
|
+
== Project home page
|
64
|
+
|
65
|
+
Information on the source tree, documentation, issues and how to contribute, see
|
66
|
+
|
67
|
+
{http://github.com/AnkurGel/statsample-timeseries}[git@github.com:AnkurGel/statsample-timeseries.git]
|
68
|
+
|
69
|
+
== Copyright
|
70
|
+
|
71
|
+
Copyright (c) 2013 Ankur Goel. See LICENSE.txt for further details.
|
72
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bundler'
|
5
|
+
begin
|
6
|
+
Bundler.setup(:default, :development)
|
7
|
+
rescue Bundler::BundlerError => e
|
8
|
+
$stderr.puts e.message
|
9
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
10
|
+
exit e.status_code
|
11
|
+
end
|
12
|
+
require 'rake'
|
13
|
+
|
14
|
+
require 'jeweler'
|
15
|
+
Jeweler::Tasks.new do |gem|
|
16
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
17
|
+
gem.name = "statsample-timeseries"
|
18
|
+
gem.homepage = "http://github.com/AnkurGel/statsample-timeseries"
|
19
|
+
gem.summary = %Q{TimeSeries modules for Statsample}
|
20
|
+
gem.description = %Q{Statsample-timeseries is an extension to Statsample. It incorporates helpful timeseries functions and modules like ARMA, ARIMA, acf, pacf, lags etc.}
|
21
|
+
gem.email = "ankurgel@gmail.com"
|
22
|
+
gem.authors = ["Ankur Goel", "Claudio Bustos"]
|
23
|
+
# dependencies defined in Gemfile
|
24
|
+
end
|
25
|
+
Jeweler::RubygemsDotOrgTasks.new
|
26
|
+
|
27
|
+
require 'rake/testtask'
|
28
|
+
Rake::TestTask.new(:test) do |test|
|
29
|
+
test.libs << 'lib' << 'test'
|
30
|
+
test.pattern = 'test/**/test_*.rb'
|
31
|
+
test.verbose = true
|
32
|
+
end
|
33
|
+
|
34
|
+
require 'cucumber/rake/task'
|
35
|
+
Cucumber::Rake::Task.new(:features)
|
36
|
+
|
37
|
+
task :default => :test
|
38
|
+
|
39
|
+
require 'rdoc/task'
|
40
|
+
Rake::RDocTask.new do |rdoc|
|
41
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
42
|
+
|
43
|
+
rdoc.rdoc_dir = 'rdoc'
|
44
|
+
rdoc.title = "statsample-timeseries #{version}"
|
45
|
+
rdoc.rdoc_files.include('README*')
|
46
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
47
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.2
|
@@ -0,0 +1,74 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# BioRuby bio-statsample-timeseries Plugin BioStatsampleTimeseries
|
4
|
+
# Author:: Ankur Goel
|
5
|
+
# Copyright:: 2013
|
6
|
+
|
7
|
+
USAGE = "Describe bio-statsample-timeseries"
|
8
|
+
|
9
|
+
if ARGV.size == 0
|
10
|
+
print USAGE
|
11
|
+
end
|
12
|
+
|
13
|
+
require 'bio-statsample-timeseries'
|
14
|
+
require 'optparse'
|
15
|
+
|
16
|
+
# Uncomment when using the bio-logger
|
17
|
+
# require 'bio-logger'
|
18
|
+
# Bio::Log::CLI.logger('stderr')
|
19
|
+
# Bio::Log::CLI.trace('info')
|
20
|
+
|
21
|
+
options = {:example_switch=>false,:show_help=>false}
|
22
|
+
opts = OptionParser.new do |o|
|
23
|
+
o.banner = "Usage: #{File.basename($0)} [options] reponame\ne.g. #{File.basename($0)} the-perfect-gem"
|
24
|
+
|
25
|
+
o.on('--example_parameter [EXAMPLE_PARAMETER]', 'TODO: put a description for the PARAMETER') do |example_parameter|
|
26
|
+
# TODO: your logic here, below an example
|
27
|
+
options[:example_parameter] = 'this is a parameter'
|
28
|
+
end
|
29
|
+
|
30
|
+
o.separator ""
|
31
|
+
o.on("--switch-example", 'TODO: put a description for the SWITCH') do
|
32
|
+
# TODO: your logic here, below an example
|
33
|
+
self[:example_switch] = true
|
34
|
+
end
|
35
|
+
|
36
|
+
# Uncomment the following when using the bio-logger
|
37
|
+
# o.separator ""
|
38
|
+
# o.on("--logger filename",String,"Log to file (default stderr)") do | name |
|
39
|
+
# Bio::Log::CLI.logger(name)
|
40
|
+
# end
|
41
|
+
#
|
42
|
+
# o.on("--trace options",String,"Set log level (default INFO, see bio-logger)") do | s |
|
43
|
+
# Bio::Log::CLI.trace(s)
|
44
|
+
# end
|
45
|
+
#
|
46
|
+
# o.on("-q", "--quiet", "Run quietly") do |q|
|
47
|
+
# Bio::Log::CLI.trace('error')
|
48
|
+
# end
|
49
|
+
#
|
50
|
+
# o.on("-v", "--verbose", "Run verbosely") do |v|
|
51
|
+
# Bio::Log::CLI.trace('info')
|
52
|
+
# end
|
53
|
+
#
|
54
|
+
# o.on("--debug", "Show debug messages") do |v|
|
55
|
+
# Bio::Log::CLI.trace('debug')
|
56
|
+
# end
|
57
|
+
|
58
|
+
o.separator ""
|
59
|
+
o.on_tail('-h', '--help', 'display this help and exit') do
|
60
|
+
options[:show_help] = true
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
begin
|
65
|
+
opts.parse!(ARGV)
|
66
|
+
|
67
|
+
# Uncomment the following when using the bio-logger
|
68
|
+
# Bio::Log::CLI.configure('bio-statsample-timeseries')
|
69
|
+
|
70
|
+
# TODO: your code here
|
71
|
+
# use options for your logic
|
72
|
+
rescue OptionParser::InvalidOption => e
|
73
|
+
options[:invalid_argument] = e.message
|
74
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
Feature: ACF
|
2
|
+
|
3
|
+
As a statistician
|
4
|
+
So that I can evaluate autocorrelation of a series
|
5
|
+
I want to evaluate acf
|
6
|
+
|
7
|
+
Background: a timeseries
|
8
|
+
|
9
|
+
Given the following values in a timeseries:
|
10
|
+
| timeseries |
|
11
|
+
| 10 20 30 40 50 60 70 80 90 100 |
|
12
|
+
| 110 120 130 140 150 160 170 180 190 200 |
|
13
|
+
|
14
|
+
Scenario: cross-check acf for 10 lags
|
15
|
+
When I provide 10 lags for acf
|
16
|
+
And I calculate acf
|
17
|
+
Then I should get 11 values in resultant acf
|
18
|
+
And I should see "1.0, 0.85, 0.7015037593984963, 0.556015037593985, 0.4150375939849624, 0.2800751879699248, 0.15263157894736842, 0.034210526315789476, -0.07368421052631578, -0.16954887218045114, -0.2518796992481203" as complete series
|
19
|
+
|
20
|
+
Scenario: cross-check acf for 5 lags
|
21
|
+
When I provide 5 lags for acf
|
22
|
+
And I calculate acf
|
23
|
+
Then I should get 6 values in resultant acf
|
24
|
+
And I should see "1.0, 0.85, 0.7015037593984963, 0.556015037593985, 0.4150375939849624, 0.2800751879699248" as complete series
|
25
|
+
|
26
|
+
Scenario: first value should be 1.0
|
27
|
+
When I provide 2 lags for acf
|
28
|
+
And I calculate acf
|
29
|
+
Then I should get 3 values in resultant acf
|
30
|
+
And I should see 1.0 as first value
|
31
|
+
|
@@ -0,0 +1,42 @@
|
|
1
|
+
Feature: PACF
|
2
|
+
|
3
|
+
As a statistician
|
4
|
+
So that I can quickly evaluate partial autocorrelation of a series
|
5
|
+
I want to evaluate pacf
|
6
|
+
|
7
|
+
Background: a timeseries
|
8
|
+
|
9
|
+
Given the following values in a timeseries:
|
10
|
+
| timeseries |
|
11
|
+
| 10 20 30 40 50 60 70 80 90 100 |
|
12
|
+
| 110 120 130 140 150 160 170 180 190 200 |
|
13
|
+
|
14
|
+
Scenario: check pacf for 10 lags with unbiased
|
15
|
+
When I provide 10 lags for pacf
|
16
|
+
When I provide yw yule walker as method
|
17
|
+
Then I should get Array as resultant output
|
18
|
+
Then I should get 11 values in resultant pacf
|
19
|
+
|
20
|
+
Scenario: check pacf for 5 lags with mle
|
21
|
+
When I provide 5 lags for pacf
|
22
|
+
When I provide mle yule walker as method
|
23
|
+
Then I should get Array as resultant output
|
24
|
+
Then I should get 6 values in resultant pacf
|
25
|
+
|
26
|
+
Scenario: check first value of pacf
|
27
|
+
When I provide 5 lags for pacf
|
28
|
+
When I provide yw yule walker as method
|
29
|
+
Then I should get Array as resultant output
|
30
|
+
And I should see 1.0 as first value
|
31
|
+
|
32
|
+
Scenario: check all values in pacf for 5 lags with mle
|
33
|
+
When I provide 5 lags for pacf
|
34
|
+
When I provide mle yule walker as method
|
35
|
+
Then I should get Array as resultant output
|
36
|
+
And I should see "1.0, 0.85, -0.07566212829370711, -0.07635069706072706, -0.07698628638512295, -0.07747034005560738" as complete series
|
37
|
+
|
38
|
+
Scenario: check all values in pacf for 5 lags with unbiased
|
39
|
+
When I provide 5 lags for pacf
|
40
|
+
When I provide yw yule walker as method
|
41
|
+
Then I should get Array as resultant output
|
42
|
+
And I should see "1.0, 0.8947368421052632, -0.10582010582010604, -0.11350188273265083, -0.12357534824820737, -0.13686534216335522" as complete series
|
File without changes
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'statsample-timeseries'
|
2
|
+
include Statsample::TimeSeries
|
3
|
+
|
4
|
+
Given /^the following values in a timeseries:$/ do |series|
|
5
|
+
arr = []
|
6
|
+
series.hashes.each do |sequence|
|
7
|
+
arr += sequence['timeseries'].split(' ').map(&:to_i).to_ts
|
8
|
+
end
|
9
|
+
@timeseries = arr.to_ts
|
10
|
+
end
|
11
|
+
|
12
|
+
When /^I provide (\d+) lags for p?acf$/ do |lags|
|
13
|
+
@lags = lags.to_i
|
14
|
+
end
|
15
|
+
|
16
|
+
When /^I provide (\w+) yule walker as method$/ do |method|
|
17
|
+
@method = method
|
18
|
+
end
|
19
|
+
|
20
|
+
Then /^I should get (\w+) as resultant output$/ do |klass|
|
21
|
+
@result = @timeseries.pacf(@lags, @method)
|
22
|
+
assert_equal @result.class.to_s, klass
|
23
|
+
end
|
24
|
+
|
25
|
+
Then /^I should get (\w+) values in resultant p?acf$/ do |values_count|
|
26
|
+
assert_equal @result.size, values_count.to_i
|
27
|
+
end
|
28
|
+
|
29
|
+
And /^I should see (\d+\.\d) as first value$/ do |first_value|
|
30
|
+
assert_equal @result.first, first_value.to_f
|
31
|
+
end
|
32
|
+
|
33
|
+
And /^I should see \"(.+)\" as complete series$/ do |series|
|
34
|
+
series = series.split(',').map(&:to_f)
|
35
|
+
assert_equal @result, series
|
36
|
+
end
|
37
|
+
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
begin
|
3
|
+
Bundler.setup(:default, :development)
|
4
|
+
rescue Bundler::BundlerError => e
|
5
|
+
$stderr.puts e.message
|
6
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
7
|
+
exit e.status_code
|
8
|
+
end
|
9
|
+
|
10
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__) + '/../../lib')
|
11
|
+
require 'statsample-timeseries'
|
12
|
+
|
13
|
+
require 'test/unit/assertions'
|
14
|
+
|
15
|
+
World(Test::Unit::Assertions)
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# Please require your code below, respecting the naming conventions in the
|
2
|
+
# bioruby directory tree.
|
3
|
+
#
|
4
|
+
# For example, say you have a plugin named bio-plugin, the only uncommented
|
5
|
+
# line in this file would be
|
6
|
+
#
|
7
|
+
# require 'bio/bio-plugin/plugin'
|
8
|
+
#
|
9
|
+
# In this file only require other files. Avoid other source code.
|
10
|
+
|
11
|
+
require 'statsample'
|
12
|
+
require_relative 'statsample-timeseries/timeseries.rb'
|
13
|
+
require_relative 'statsample-timeseries/arima.rb'
|
14
|
+
require_relative 'statsample-timeseries/arima/kalman'
|
15
|
+
require_relative 'statsample-timeseries/arima/likelihood'
|
16
|
+
require_relative 'statsample-timeseries/utility.rb'
|
17
|
+
|
18
|
+
|
@@ -0,0 +1,246 @@
|
|
1
|
+
#require 'debugger'
|
2
|
+
require 'statsample-timeseries/arima/kalman'
|
3
|
+
require 'statsample-timeseries/arima/likelihood'
|
4
|
+
module Statsample
|
5
|
+
module TimeSeries
|
6
|
+
|
7
|
+
def self.arima
|
8
|
+
#not passing (ds,p,i,q) elements for now
|
9
|
+
#will do that once #arima is ready for all modelling
|
10
|
+
Statsample::TimeSeries::ARIMA.new
|
11
|
+
end
|
12
|
+
|
13
|
+
class ARIMA < Statsample::Vector
|
14
|
+
include Statsample::TimeSeries
|
15
|
+
|
16
|
+
#= Kalman filter on ARIMA model
|
17
|
+
#== Params
|
18
|
+
#
|
19
|
+
#* *ts*: timeseries object
|
20
|
+
#* *p*: AR order
|
21
|
+
#* *i*: Integerated part order
|
22
|
+
#* *q*: MA order
|
23
|
+
#
|
24
|
+
#== Usage
|
25
|
+
# ts = (1..100).map { rand }.to_ts
|
26
|
+
# k_obj = Statsample::TimeSeries::ARIMA.ks(ts, 2, 1, 1)
|
27
|
+
# k_obj.ar
|
28
|
+
# #=> AR's phi coefficients
|
29
|
+
# k_obj.ma
|
30
|
+
# #=> MA's theta coefficients
|
31
|
+
#
|
32
|
+
#== Returns
|
33
|
+
#Kalman filter object
|
34
|
+
def self.ks(ts, p, i, q)
|
35
|
+
#prototype
|
36
|
+
if i > 0
|
37
|
+
ts = ts.diff(i).reject { |x| x.nil? }.to_ts
|
38
|
+
end
|
39
|
+
filter = Arima::KalmanFilter.new(ts, p, i, q)
|
40
|
+
filter
|
41
|
+
end
|
42
|
+
|
43
|
+
def ar(p)
|
44
|
+
#AutoRegressive part of model
|
45
|
+
#http://en.wikipedia.org/wiki/Autoregressive_model#Definition
|
46
|
+
#For finding parameters(to fit), we will use either Yule-walker
|
47
|
+
#or Burg's algorithm(more efficient)
|
48
|
+
end
|
49
|
+
|
50
|
+
#Converts a linear array into a Statsample vector
|
51
|
+
#== Parameters
|
52
|
+
#
|
53
|
+
#* *arr*: Array which has to be converted in Statsample vector
|
54
|
+
def create_vector(arr)
|
55
|
+
Statsample::Vector.new(arr, :scale)
|
56
|
+
end
|
57
|
+
|
58
|
+
#=Yule Walker
|
59
|
+
#Performs yule walker estimation on given timeseries, observations and order
|
60
|
+
#==Parameters
|
61
|
+
#
|
62
|
+
#* *ts*: timeseries object
|
63
|
+
#* *n* : number of observations
|
64
|
+
#* *k* : order
|
65
|
+
#
|
66
|
+
#==Returns
|
67
|
+
#phi and sigma vectors
|
68
|
+
def yule_walker(ts, n, k)
|
69
|
+
phi, sigma = Pacf::Pacf.yule_walker(ts, k)
|
70
|
+
return phi, sigma
|
71
|
+
#return ar_sim(n, phi, sigma)
|
72
|
+
end
|
73
|
+
|
74
|
+
#=Levinson Durbin estimation
|
75
|
+
#Performs levinson durbin estimation on given timeseries, observations and order
|
76
|
+
#==Parameters
|
77
|
+
#
|
78
|
+
#* *ts*: timeseries object
|
79
|
+
#* *n* : number of observations
|
80
|
+
#* *k* : autoregressive order
|
81
|
+
#
|
82
|
+
#==Returns
|
83
|
+
#phi and sigma vectors
|
84
|
+
def levinson_durbin(ts, n, k)
|
85
|
+
intermediate = Pacf::Pacf.levinson_durbin(ts, k)
|
86
|
+
phi, sigma = intermediate[1], intermediate[0]
|
87
|
+
return phi, sigma
|
88
|
+
#return ar_sim(n, phi, sigma)
|
89
|
+
end
|
90
|
+
|
91
|
+
#=Autoregressive Simulator
|
92
|
+
#Simulates an autoregressive AR(p) model with specified number of
|
93
|
+
#observations(n), with phi number of values for order p and sigma.
|
94
|
+
#
|
95
|
+
#==Analysis:
|
96
|
+
# [http://ankurgoel.com/blog/2013/07/20/ar-ma-arma-acf-pacf-visualizations/](http://ankurgoel.com/blog/2013/07/20/ar-ma-arma-acf-pacf-visualizations/)
|
97
|
+
#
|
98
|
+
#==Parameters:
|
99
|
+
#* *n*: integer, number of observations
|
100
|
+
#* *phi* :array of phi values, e.g: [0.35, 0.213] for p = 2
|
101
|
+
#* *sigma*: float, sigma value for error generalization
|
102
|
+
#
|
103
|
+
#==Usage
|
104
|
+
# ar = ARIMA.new
|
105
|
+
# ar.ar_sim(1500, [0.3, 0.9], 0.12)
|
106
|
+
# # => AR(2) autoregressive series of 1500 values
|
107
|
+
#
|
108
|
+
#==Returns
|
109
|
+
#Array of generated autoregressive series against attributes
|
110
|
+
def ar_sim(n, phi, sigma)
|
111
|
+
#using random number generator for inclusion of white noise
|
112
|
+
err_nor = Distribution::Normal.rng(0, sigma)
|
113
|
+
#creating buffer with 10 random values
|
114
|
+
buffer = Array.new(10, err_nor.call())
|
115
|
+
|
116
|
+
x = buffer + Array.new(n, 0)
|
117
|
+
|
118
|
+
#For now "phi" are the known model parameters
|
119
|
+
#later we will obtain it by Yule-walker/Burg
|
120
|
+
|
121
|
+
#instead of starting from 0, start from 11
|
122
|
+
#and later take away buffer values for failsafe
|
123
|
+
11.upto(n+11) do |i|
|
124
|
+
if i <= phi.size
|
125
|
+
#dependent on previous accumulation of x
|
126
|
+
backshifts = create_vector(x[0...i].reverse)
|
127
|
+
else
|
128
|
+
#dependent on number of phi size/order
|
129
|
+
backshifts = create_vector(x[(i - phi.size)...i].reverse)
|
130
|
+
end
|
131
|
+
parameters = create_vector(phi[0...backshifts.size])
|
132
|
+
|
133
|
+
summation = (backshifts * parameters).inject(:+)
|
134
|
+
x[i] = summation + err_nor.call()
|
135
|
+
end
|
136
|
+
x - buffer
|
137
|
+
end
|
138
|
+
|
139
|
+
#=Moving Average Simulator
|
140
|
+
#Simulates a moving average model with specified number of
|
141
|
+
#observations(n), with theta values for order k and sigma
|
142
|
+
#
|
143
|
+
#==Parameters
|
144
|
+
#* *n*: integer, number of observations
|
145
|
+
#* *theta*: array of floats, e.g: [0.23, 0.732], must be < 1
|
146
|
+
#* *sigma*: float, sigma value for whitenoise error
|
147
|
+
#
|
148
|
+
#==Usage
|
149
|
+
# ar = ARIMA.new
|
150
|
+
# ar.ma_sim(1500, [0.23, 0.732], 0.27)
|
151
|
+
#
|
152
|
+
#==Returns
|
153
|
+
#Array of generated MA(q) model
|
154
|
+
def ma_sim(n, theta, sigma)
|
155
|
+
#n is number of observations (eg: 1000)
|
156
|
+
#theta are the model parameters containting q values
|
157
|
+
#q is the order of MA
|
158
|
+
mean = theta.to_ts.mean()
|
159
|
+
whitenoise_gen = Distribution::Normal.rng(0, sigma)
|
160
|
+
x = Array.new(n, 0)
|
161
|
+
q = theta.size
|
162
|
+
noise_arr = (n+1).times.map { whitenoise_gen.call() }
|
163
|
+
|
164
|
+
1.upto(n) do |i|
|
165
|
+
#take care that noise vector doesn't try to index -ve value:
|
166
|
+
if i <= q
|
167
|
+
noises = create_vector(noise_arr[0..i].reverse)
|
168
|
+
else
|
169
|
+
noises = create_vector(noise_arr[(i-q)..i].reverse)
|
170
|
+
end
|
171
|
+
weights = create_vector([1] + theta[0...noises.size - 1])
|
172
|
+
|
173
|
+
summation = (weights * noises).inject(:+)
|
174
|
+
x[i] = mean + summation
|
175
|
+
end
|
176
|
+
x
|
177
|
+
end
|
178
|
+
|
179
|
+
#=ARMA(Autoregressive and Moving Average) Simulator
|
180
|
+
#ARMA is represented by:
|
181
|
+
#http://upload.wikimedia.org/math/2/e/d/2ed0485927b4370ae288f1bc1fe2fc8b.png
|
182
|
+
#This simulates the ARMA model against p, q and sigma.
|
183
|
+
#If p = 0, then model is pure MA(q),
|
184
|
+
#If q = 0, then model is pure AR(p),
|
185
|
+
#otherwise, model is ARMA(p, q) represented by above.
|
186
|
+
#
|
187
|
+
#==Detailed analysis:
|
188
|
+
# [http://ankurgoel.com/blog/2013/07/20/ar-ma-arma-acf-pacf-visualizations/](http://ankurgoel.com/blog/2013/07/20/ar-ma-arma-acf-pacf-visualizations/)
|
189
|
+
#
|
190
|
+
#==Parameters
|
191
|
+
#* *n*: integer, number of observations
|
192
|
+
#* *p*: array, contains p number of phi values for AR(p) process
|
193
|
+
#* *q*: array, contains q number of theta values for MA(q) process
|
194
|
+
#* *sigma*: float, sigma value for whitenoise error generation
|
195
|
+
#
|
196
|
+
#==Usage
|
197
|
+
# ar = ARIMA.new
|
198
|
+
# ar.arma_sim(1500, [0.3, 0.272], [0.8, 0.317], 0.92)
|
199
|
+
#
|
200
|
+
#==Returns
|
201
|
+
#array of generated ARMA model values
|
202
|
+
def arma_sim(n, p, q, sigma)
|
203
|
+
#represented by :
|
204
|
+
#http://upload.wikimedia.org/math/2/e/d/2ed0485927b4370ae288f1bc1fe2fc8b.png
|
205
|
+
|
206
|
+
|
207
|
+
whitenoise_gen = Distribution::Normal.rng(0, sigma)
|
208
|
+
noise_arr = (n+11).times.map { whitenoise_gen.call() }
|
209
|
+
|
210
|
+
buffer = Array.new(10, whitenoise_gen.call())
|
211
|
+
x = buffer + Array.new(n, 0)
|
212
|
+
|
213
|
+
11.upto(n+11) do |i|
|
214
|
+
if i <= p.size
|
215
|
+
backshifts = create_vector(x[0...i].reverse)
|
216
|
+
else
|
217
|
+
backshifts = create_vector(x[(i - p.size)...i].reverse)
|
218
|
+
end
|
219
|
+
parameters = create_vector(p[0...backshifts.size])
|
220
|
+
|
221
|
+
ar_summation = (backshifts * parameters).inject(:+)
|
222
|
+
|
223
|
+
if i <= q.size
|
224
|
+
noises = create_vector(noise_arr[0..i].reverse)
|
225
|
+
else
|
226
|
+
noises = create_vector(noise_arr[(i-q.size)..i].reverse)
|
227
|
+
end
|
228
|
+
weights = create_vector([1] + q[0...noises.size - 1])
|
229
|
+
|
230
|
+
ma_summation = (weights * noises).inject(:+)
|
231
|
+
|
232
|
+
x[i] = ar_summation + ma_summation
|
233
|
+
end
|
234
|
+
x - buffer
|
235
|
+
end
|
236
|
+
|
237
|
+
#=Hannan-Rissanen for ARMA fit
|
238
|
+
def self.hannan(ts, p, q, k)
|
239
|
+
start_params = create_vector(Array.new(p+q+k, 0))
|
240
|
+
ts_dup = ts.dup
|
241
|
+
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
end
|
246
|
+
end
|