statsample-timeseries 0.0.3 → 0.3.0
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.
- checksums.yaml +7 -0
- data/.gitignore +19 -0
- data/.travis.yml +13 -10
- data/Gemfile +2 -21
- data/History.md +4 -0
- data/LICENSE.txt +1 -1
- data/README.md +62 -0
- data/Rakefile +12 -17
- data/lib/statsample-timeseries.rb +3 -13
- data/lib/statsample-timeseries/arima.rb +72 -74
- data/lib/statsample-timeseries/arima/kalman.rb +20 -40
- data/lib/statsample-timeseries/arima/likelihood.rb +3 -4
- data/lib/statsample-timeseries/daru_monkeys.rb +78 -0
- data/lib/statsample-timeseries/timeseries/pacf.rb +47 -38
- data/lib/statsample-timeseries/utility.rb +105 -133
- data/lib/statsample-timeseries/version.rb +5 -0
- data/statsample-timeseries.gemspec +31 -0
- data/test/helper.rb +6 -29
- data/test/test_acf.rb +41 -0
- data/test/test_arima_ks.rb +28 -12
- data/test/test_arima_simulators.rb +59 -42
- data/test/test_matrix.rb +1 -1
- data/test/test_pacf.rb +7 -2
- data/test/test_wald.rb +7 -3
- metadata +81 -132
- data/README.rdoc +0 -72
- data/VERSION +0 -1
- data/bin/bio-statsample-timeseries +0 -74
- data/features/acf.feature +0 -31
- data/features/pacf.feature +0 -42
- data/features/step_definitions/bio-statsample-timeseries_steps.rb +0 -0
- data/features/step_definitions/step_definitions.rb +0 -37
- data/features/step_definitions/step_definitions_acf.rb +0 -8
- data/features/support/env.rb +0 -15
- data/lib/statsample-timeseries/timeseries.rb +0 -291
- data/test/test_tseries.rb +0 -103
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 2aa5a74001152045a97547f2341b4a03f23ed408
|
4
|
+
data.tar.gz: 9f1c370a27c0b3ffb99d13f0d96eb6bfc6ca93bc
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 75272189f0b3f8f242820dc5693d335abb8d160d9322c36f7e82372d282ad2f7368ddb5fbedea0ebb2afd6bab21559651f8faaf224a02d462758131cd48098e5
|
7
|
+
data.tar.gz: ca16839c93757d521eb4322640b425fb0672187fa4c05d8adc6ed741fcc74271e5d67d074898a757a1814ff2c45d4a3e86e32d4a540d5cb16fc98b7504f7c8d7
|
data/.gitignore
ADDED
data/.travis.yml
CHANGED
@@ -1,13 +1,16 @@
|
|
1
1
|
language: ruby
|
2
|
+
|
2
3
|
rvm:
|
3
|
-
- 1.9.2
|
4
|
-
- 1.9.3
|
5
4
|
- 2.0.0
|
6
|
-
-
|
7
|
-
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
5
|
+
- 2.1.0
|
6
|
+
- 2.2.1
|
7
|
+
|
8
|
+
script: "bundle exec rake test"
|
9
|
+
|
10
|
+
install:
|
11
|
+
- gem install bundler
|
12
|
+
- bundle install
|
13
|
+
|
14
|
+
before_install:
|
15
|
+
- sudo apt-get update -qq
|
16
|
+
- sudo apt-get install -y libgsl0-dev
|
data/Gemfile
CHANGED
@@ -1,21 +1,2 @@
|
|
1
|
-
source "
|
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
|
-
end
|
1
|
+
source "https://rubygems.org"
|
2
|
+
gemspec
|
data/History.md
ADDED
data/LICENSE.txt
CHANGED
@@ -9,7 +9,7 @@ You *must* read the Contributor Agreement before contributing code to the SciRub
|
|
9
9
|
|
10
10
|
-----
|
11
11
|
|
12
|
-
Copyright (c)
|
12
|
+
Copyright (c) 2013, Ankur Goel and Ruby Science Foundation
|
13
13
|
All rights reserved.
|
14
14
|
|
15
15
|
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
data/README.md
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
# statsample-timeseries
|
2
|
+
|
3
|
+
{<img
|
4
|
+
src="https://secure.travis-ci.org/SciRuby/statsample-timeseries.png"
|
5
|
+
/>}[http://travis-ci.org/#!/SciRuby/statsample-timeseries]
|
6
|
+
|
7
|
+
Statsample-Timeseries is an extension to [Statsample](https://github.com/sciruby/statsample), a suite for advanced statistics with Ruby.
|
8
|
+
|
9
|
+
## Description
|
10
|
+
|
11
|
+
Statsample-Timeseries is extension of Statsample, and incorporates helpful time series functions, estimation techniques, and modules, such as:
|
12
|
+
|
13
|
+
* ACF
|
14
|
+
* PACF
|
15
|
+
* ARIMA
|
16
|
+
* Kalman Filter
|
17
|
+
* Log Likelihood
|
18
|
+
* Autocovariances
|
19
|
+
* Moving Averages
|
20
|
+
|
21
|
+
Statsample-Timeseries is part of the [SciRuby project](http://sciruby.com).
|
22
|
+
|
23
|
+
## Dependency
|
24
|
+
|
25
|
+
Please install [rb-gsl]() which is a Ruby wrapper over GNU Scientific Library.
|
26
|
+
It enables us to use various minimization techniques during estimations.
|
27
|
+
|
28
|
+
## Installation
|
29
|
+
|
30
|
+
`gem install statsample-timeseries`
|
31
|
+
|
32
|
+
## Usage
|
33
|
+
|
34
|
+
To use the library:
|
35
|
+
|
36
|
+
`require 'statsample-timeseries'`
|
37
|
+
|
38
|
+
See [Ankur's blog posts](http://ankurgoel.com) for explanations and examples.
|
39
|
+
|
40
|
+
## Documentation
|
41
|
+
|
42
|
+
The API doc is [online](http://rubygems.org/gems/statsample-timeseries). For more code examples see also the test files in the source tree.
|
43
|
+
|
44
|
+
## Contributing
|
45
|
+
|
46
|
+
* Fork the project.
|
47
|
+
* Create your feature branch
|
48
|
+
* Add/Modify code.
|
49
|
+
* Write equivalent documentation and *tests*.
|
50
|
+
* Run `rake test` to verify that all tests pass.
|
51
|
+
* Push your branch.
|
52
|
+
* Pull request. :)
|
53
|
+
|
54
|
+
## Project home page
|
55
|
+
|
56
|
+
For information on the source tree, documentation, issues and how to contribute, see [http://github.com/SciRuby/statsample-timeseries](git@github.com:SciRuby/statsample-timeseries.git).
|
57
|
+
|
58
|
+
== Copyright
|
59
|
+
|
60
|
+
Copyright (c) 2013 Ankur Goel and the Ruby Science Foundation. See LICENSE.txt for further details.
|
61
|
+
|
62
|
+
Statsample is (c) 2009-2013 Claudio Bustos and the Ruby Science Foundation.
|
data/Rakefile
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
# encoding: utf-8
|
2
|
+
require 'rake'
|
3
|
+
require 'bundler/gem_tasks'
|
2
4
|
|
3
|
-
|
5
|
+
# Setup the necessary gems, specified in the gemspec.
|
4
6
|
require 'bundler'
|
5
7
|
begin
|
6
8
|
Bundler.setup(:default, :development)
|
@@ -9,20 +11,16 @@ rescue Bundler::BundlerError => e
|
|
9
11
|
$stderr.puts "Run `bundle install` to install missing gems"
|
10
12
|
exit e.status_code
|
11
13
|
end
|
12
|
-
require 'rake'
|
13
14
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
gem.authors = ["Ankur Goel", "Claudio Bustos"]
|
23
|
-
# dependencies defined in Gemfile
|
15
|
+
desc "Open IRB with statsample-timeseries loaded."
|
16
|
+
task :console do
|
17
|
+
require 'irb'
|
18
|
+
require 'irb/completion'
|
19
|
+
$:.unshift File.expand_path("../lib", __FILE__)
|
20
|
+
require 'statsample-timeseries'
|
21
|
+
ARGV.clear
|
22
|
+
IRB.start
|
24
23
|
end
|
25
|
-
Jeweler::RubygemsDotOrgTasks.new
|
26
24
|
|
27
25
|
require 'rake/testtask'
|
28
26
|
Rake::TestTask.new(:test) do |test|
|
@@ -31,14 +29,11 @@ Rake::TestTask.new(:test) do |test|
|
|
31
29
|
test.verbose = true
|
32
30
|
end
|
33
31
|
|
34
|
-
require 'cucumber/rake/task'
|
35
|
-
Cucumber::Rake::Task.new(:features)
|
36
|
-
|
37
32
|
task :default => :test
|
38
33
|
|
39
34
|
require 'rdoc/task'
|
40
35
|
Rake::RDocTask.new do |rdoc|
|
41
|
-
version =
|
36
|
+
version = Statsample::TimeSeries::VERSION
|
42
37
|
|
43
38
|
rdoc.rdoc_dir = 'rdoc'
|
44
39
|
rdoc.title = "statsample-timeseries #{version}"
|
@@ -1,18 +1,8 @@
|
|
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
1
|
require 'statsample'
|
12
|
-
|
2
|
+
require 'statsample-timeseries/version'
|
3
|
+
|
4
|
+
require_relative 'statsample-timeseries/daru_monkeys.rb'
|
13
5
|
require_relative 'statsample-timeseries/arima.rb'
|
14
6
|
require_relative 'statsample-timeseries/arima/kalman'
|
15
7
|
require_relative 'statsample-timeseries/arima/likelihood'
|
16
8
|
require_relative 'statsample-timeseries/utility.rb'
|
17
|
-
|
18
|
-
|
@@ -1,4 +1,3 @@
|
|
1
|
-
#require 'debugger'
|
2
1
|
require 'statsample-timeseries/arima/kalman'
|
3
2
|
require 'statsample-timeseries/arima/likelihood'
|
4
3
|
module Statsample
|
@@ -10,31 +9,31 @@ module Statsample
|
|
10
9
|
Statsample::TimeSeries::ARIMA.new
|
11
10
|
end
|
12
11
|
|
13
|
-
class ARIMA
|
14
|
-
include Statsample::TimeSeries
|
12
|
+
class ARIMA
|
15
13
|
|
16
14
|
#= Kalman filter on ARIMA model
|
17
|
-
|
15
|
+
# == Arguments
|
18
16
|
#
|
19
17
|
#* *ts*: timeseries object
|
20
18
|
#* *p*: AR order
|
21
19
|
#* *i*: Integerated part order
|
22
20
|
#* *q*: MA order
|
23
21
|
#
|
24
|
-
|
25
|
-
#
|
26
|
-
#
|
27
|
-
#
|
28
|
-
#
|
29
|
-
#
|
30
|
-
#
|
22
|
+
# == Usage
|
23
|
+
#
|
24
|
+
# ts = (1..100).map { rand }.to_ts
|
25
|
+
# k_obj = Statsample::TimeSeries::ARIMA.ks(ts, 2, 1, 1)
|
26
|
+
# k_obj.ar
|
27
|
+
# #=> AR's phi coefficients
|
28
|
+
# k_obj.ma
|
29
|
+
# #=> MA's theta coefficients
|
31
30
|
#
|
32
31
|
#== Returns
|
33
|
-
#Kalman filter object
|
32
|
+
# Kalman filter object
|
34
33
|
def self.ks(ts, p, i, q)
|
35
34
|
#prototype
|
36
35
|
if i > 0
|
37
|
-
ts = ts.diff(i).reject { |x| x.nil? }
|
36
|
+
ts = ts.diff(i).reject { |x| x.nil? }
|
38
37
|
end
|
39
38
|
if Statsample.has_gsl?
|
40
39
|
filter = Arima::KalmanFilter.new(ts, p, i, q)
|
@@ -49,14 +48,15 @@ module Statsample
|
|
49
48
|
#http://en.wikipedia.org/wiki/Autoregressive_model#Definition
|
50
49
|
#For finding parameters(to fit), we will use either Yule-walker
|
51
50
|
#or Burg's algorithm(more efficient)
|
51
|
+
raise NotImplementedError
|
52
52
|
end
|
53
53
|
|
54
|
-
#Converts a linear array into a
|
54
|
+
# Converts a linear array into a Daru vector
|
55
55
|
#== Parameters
|
56
56
|
#
|
57
|
-
#* *arr*: Array which has to be converted
|
57
|
+
#* *arr*: Array which has to be converted to Daru vector
|
58
58
|
def create_vector(arr)
|
59
|
-
|
59
|
+
Daru::Vector.new(arr)
|
60
60
|
end
|
61
61
|
|
62
62
|
#=Yule Walker
|
@@ -70,7 +70,7 @@ module Statsample
|
|
70
70
|
#==Returns
|
71
71
|
#phi and sigma vectors
|
72
72
|
def yule_walker(ts, n, k)
|
73
|
-
phi, sigma =
|
73
|
+
phi, sigma = Statsample::TimeSeries::Pacf.yule_walker(ts, k)
|
74
74
|
return phi, sigma
|
75
75
|
#return ar_sim(n, phi, sigma)
|
76
76
|
end
|
@@ -89,22 +89,23 @@ module Statsample
|
|
89
89
|
intermediate = Pacf::Pacf.levinson_durbin(ts, k)
|
90
90
|
phi, sigma = intermediate[1], intermediate[0]
|
91
91
|
return phi, sigma
|
92
|
-
#return ar_sim(n, phi, sigma)
|
93
92
|
end
|
94
93
|
|
95
94
|
#=Autoregressive Simulator
|
96
95
|
#Simulates an autoregressive AR(p) model with specified number of
|
97
96
|
#observations(n), with phi number of values for order p and sigma.
|
98
97
|
#
|
99
|
-
|
100
|
-
# [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/)
|
98
|
+
# == Analysis
|
101
99
|
#
|
102
|
-
|
100
|
+
# http://ankurgoel.com/blog/2013/07/20/ar-ma-arma-acf-pacf-visualizations/
|
101
|
+
#
|
102
|
+
# == Parameters
|
103
103
|
#* *n*: integer, number of observations
|
104
104
|
#* *phi* :array of phi values, e.g: [0.35, 0.213] for p = 2
|
105
105
|
#* *sigma*: float, sigma value for error generalization
|
106
106
|
#
|
107
|
-
|
107
|
+
# == Usage
|
108
|
+
#
|
108
109
|
# ar = ARIMA.new
|
109
110
|
# ar.ar_sim(1500, [0.3, 0.9], 0.12)
|
110
111
|
# # => AR(2) autoregressive series of 1500 values
|
@@ -127,14 +128,13 @@ module Statsample
|
|
127
128
|
11.upto(n+11) do |i|
|
128
129
|
if i <= phi.size
|
129
130
|
#dependent on previous accumulation of x
|
130
|
-
backshifts =
|
131
|
+
backshifts = x[0...i].reverse
|
131
132
|
else
|
132
133
|
#dependent on number of phi size/order
|
133
|
-
backshifts =
|
134
|
+
backshifts = x[(i - phi.size)...i].reverse
|
134
135
|
end
|
135
|
-
parameters =
|
136
|
-
|
137
|
-
summation = (backshifts * parameters).inject(:+)
|
136
|
+
parameters = phi[0...backshifts.size]
|
137
|
+
summation = (backshifts.map.with_index { |e,i| e*parameters[i] }).inject(:+)
|
138
138
|
x[i] = summation + err_nor.call()
|
139
139
|
end
|
140
140
|
x - buffer
|
@@ -144,70 +144,74 @@ module Statsample
|
|
144
144
|
#Simulates a moving average model with specified number of
|
145
145
|
#observations(n), with theta values for order k and sigma
|
146
146
|
#
|
147
|
-
|
147
|
+
# == Parameters
|
148
148
|
#* *n*: integer, number of observations
|
149
149
|
#* *theta*: array of floats, e.g: [0.23, 0.732], must be < 1
|
150
150
|
#* *sigma*: float, sigma value for whitenoise error
|
151
151
|
#
|
152
|
-
|
153
|
-
#
|
154
|
-
#
|
152
|
+
# == Usage
|
153
|
+
# ar = ARIMA.new
|
154
|
+
# ar.ma_sim(1500, [0.23, 0.732], 0.27)
|
155
155
|
#
|
156
|
-
|
157
|
-
#Array of generated MA(q) model
|
156
|
+
# == Returns
|
157
|
+
# Array of generated MA(q) model
|
158
|
+
#
|
159
|
+
# == Notes
|
160
|
+
# n is number of observations (eg: 1000). theta are the model parameters
|
161
|
+
# containting q values. q is the order of MA
|
158
162
|
def ma_sim(n, theta, sigma)
|
159
|
-
|
160
|
-
|
161
|
-
#q is the order of MA
|
162
|
-
mean = theta.to_ts.mean()
|
163
|
+
q = theta.size
|
164
|
+
mean = theta.inject(:+) / q
|
163
165
|
whitenoise_gen = Distribution::Normal.rng(0, sigma)
|
164
166
|
x = Array.new(n, 0)
|
165
|
-
q = theta.size
|
166
167
|
noise_arr = (n+1).times.map { whitenoise_gen.call() }
|
167
168
|
|
168
169
|
1.upto(n) do |i|
|
169
170
|
#take care that noise vector doesn't try to index -ve value:
|
170
171
|
if i <= q
|
171
|
-
noises =
|
172
|
+
noises = noise_arr[0..i].reverse
|
172
173
|
else
|
173
|
-
noises =
|
174
|
+
noises = noise_arr[(i-q)..i].reverse
|
174
175
|
end
|
175
|
-
weights =
|
176
|
+
weights = [1] + theta[0...noises.size - 1]
|
176
177
|
|
177
|
-
summation =
|
178
|
+
summation = weights.map.with_index { |e,i| e*noises[i] }.inject(:+)
|
178
179
|
x[i] = mean + summation
|
179
180
|
end
|
181
|
+
|
180
182
|
x
|
181
183
|
end
|
182
184
|
|
183
185
|
#=ARMA(Autoregressive and Moving Average) Simulator
|
184
|
-
#ARMA is represented by:
|
185
|
-
#http://upload.wikimedia.org/math/2/e/d/2ed0485927b4370ae288f1bc1fe2fc8b.png
|
186
|
-
#This simulates the ARMA model against p, q and sigma.
|
187
|
-
#If p = 0, then model is pure MA(q),
|
188
|
-
#If q = 0, then model is pure AR(p),
|
189
|
-
#otherwise, model is ARMA(p, q) represented by above.
|
186
|
+
# ARMA is represented by:
|
187
|
+
# http://upload.wikimedia.org/math/2/e/d/2ed0485927b4370ae288f1bc1fe2fc8b.png
|
188
|
+
# This simulates the ARMA model against p, q and sigma.
|
189
|
+
# If p = 0, then model is pure MA(q),
|
190
|
+
# If q = 0, then model is pure AR(p),
|
191
|
+
# otherwise, model is ARMA(p, q) represented by above.
|
190
192
|
#
|
191
|
-
|
192
|
-
#
|
193
|
+
# == Detailed analysis:
|
194
|
+
#
|
195
|
+
# http://ankurgoel.com/blog/2013/07/20/ar-ma-arma-acf-pacf-visualizations/
|
196
|
+
#
|
197
|
+
# == Parameters
|
193
198
|
#
|
194
|
-
#==Parameters
|
195
199
|
#* *n*: integer, number of observations
|
196
200
|
#* *p*: array, contains p number of phi values for AR(p) process
|
197
201
|
#* *q*: array, contains q number of theta values for MA(q) process
|
198
202
|
#* *sigma*: float, sigma value for whitenoise error generation
|
199
203
|
#
|
200
|
-
|
201
|
-
# ar = ARIMA.new
|
202
|
-
# ar.arma_sim(1500, [0.3, 0.272], [0.8, 0.317], 0.92)
|
204
|
+
# == Usage
|
203
205
|
#
|
204
|
-
|
205
|
-
#
|
206
|
+
# ar = ARIMA.new
|
207
|
+
# ar.arma_sim(1500, [0.3, 0.272], [0.8, 0.317], 0.92)
|
208
|
+
#
|
209
|
+
# == Returns
|
210
|
+
#
|
211
|
+
# array of generated ARMA model values
|
206
212
|
def arma_sim(n, p, q, sigma)
|
207
213
|
#represented by :
|
208
214
|
#http://upload.wikimedia.org/math/2/e/d/2ed0485927b4370ae288f1bc1fe2fc8b.png
|
209
|
-
|
210
|
-
|
211
215
|
whitenoise_gen = Distribution::Normal.rng(0, sigma)
|
212
216
|
noise_arr = (n+11).times.map { whitenoise_gen.call() }
|
213
217
|
|
@@ -216,35 +220,29 @@ module Statsample
|
|
216
220
|
|
217
221
|
11.upto(n+11) do |i|
|
218
222
|
if i <= p.size
|
219
|
-
backshifts =
|
223
|
+
backshifts = x[0...i].reverse
|
220
224
|
else
|
221
|
-
backshifts =
|
225
|
+
backshifts = x[(i - p.size)...i].reverse
|
222
226
|
end
|
223
|
-
parameters =
|
224
|
-
|
225
|
-
ar_summation = (backshifts * parameters).inject(:+)
|
227
|
+
parameters = p[0...backshifts.size]
|
228
|
+
ar_summation = backshifts.map.with_index { |e,i| e*parameters[i] }.inject(:+)
|
226
229
|
|
227
230
|
if i <= q.size
|
228
|
-
noises =
|
231
|
+
noises = noise_arr[0..i].reverse
|
229
232
|
else
|
230
|
-
noises =
|
233
|
+
noises = noise_arr[(i-q.size)..i].reverse
|
231
234
|
end
|
232
|
-
weights =
|
233
|
-
|
234
|
-
ma_summation = (weights * noises).inject(:+)
|
235
|
-
|
235
|
+
weights = [1] + q[0...noises.size - 1]
|
236
|
+
ma_summation = (weights.map.with_index { |e,i| e*noises[i] }).inject(:+)
|
236
237
|
x[i] = ar_summation + ma_summation
|
237
238
|
end
|
238
239
|
x - buffer
|
239
240
|
end
|
240
241
|
|
241
|
-
|
242
|
+
# Hannan-Rissanen for ARMA fit
|
242
243
|
def self.hannan(ts, p, q, k)
|
243
|
-
|
244
|
-
ts_dup = ts.dup
|
245
|
-
|
244
|
+
raise NotImplementedError
|
246
245
|
end
|
247
246
|
end
|
248
|
-
|
249
247
|
end
|
250
248
|
end
|