ts 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (8) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +0 -0
  3. data/README.md +32 -0
  4. data/Rakefile +8 -0
  5. data/lib/ts.rb +182 -0
  6. data/test/test_ts.rb +60 -0
  7. data/ts.gemspec +20 -0
  8. metadata +48 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 28be043c878e154e056aca129a35fb9a2c225e1d
4
+ data.tar.gz: 9f6582a2e8298dcabfe7103daeedb37eec3fd2ee
5
+ SHA512:
6
+ metadata.gz: 79d5dbdade0dd0c12f9213a34f6f4f36dbe84252874d04e9189da4bb6c9e74bfe25e6ccf7e95b3b13304426518e3da815b9c683afc3c45e904a4e4b1329bb2f3
7
+ data.tar.gz: e285a9dcc3389ff35de9891cd4dbef0af18d72a05f1ebe5a6f6ae179547f741007be9518a46c74fc05760bba0fe91af5298c5a96cd41f915d30e26ec51dc7212
data/.gitignore ADDED
File without changes
data/README.md ADDED
@@ -0,0 +1,32 @@
1
+ ### ts.rb
2
+
3
+ Utility class for time series data, which does not require periodicity.
4
+
5
+ ##### Install
6
+
7
+ ```
8
+ gem install ts
9
+ ```
10
+
11
+ ##### Usage
12
+
13
+ ```ruby
14
+ require "ts"
15
+
16
+ ts = TS.new([
17
+ [time, value],
18
+ # ...
19
+ [time, value]
20
+ ])
21
+
22
+ ts.each { |time, value|
23
+ #...
24
+ }
25
+
26
+ ts.stats
27
+ ts.slice start, finish
28
+ ts.after time
29
+ ts.before time
30
+ ```
31
+
32
+ See rdoc for all methods.
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require "rake/testtask"
2
+
3
+ Rake::TestTask.new do |t|
4
+ t.libs << "test"
5
+ end
6
+
7
+ desc "Run tests"
8
+ task :default => :test
data/lib/ts.rb ADDED
@@ -0,0 +1,182 @@
1
+ #
2
+ # TS
3
+ #
4
+ # Utility class for [timestamp, number] tuples, where periodicity is not
5
+ # guaranteed.
6
+ #
7
+ class TS
8
+
9
+ Version = "1.0.0"
10
+
11
+ include Enumerable
12
+
13
+ attr_reader :data
14
+
15
+ # +data+ an array of [timestamp/time, number] tuples
16
+ def initialize data
17
+ if data.nil?
18
+ raise "Cannot instantiate timeseries without data"
19
+ end
20
+
21
+ @data = data
22
+ end
23
+
24
+ # The number of elements in the set
25
+ def size
26
+ @data.size
27
+ end
28
+
29
+ # see Enumerable
30
+ def each
31
+ @data.each { |v| yield *v }
32
+ end
33
+
34
+ # map the [time,value] tuples into other [time,value] tuples
35
+ def map
36
+ TS.new(@data.map { |v| yield *v })
37
+ end
38
+
39
+ def stats
40
+ return @stats if @stats
41
+
42
+ min = Float::MAX
43
+ max = Float::MIN
44
+ sum = 0.0
45
+ sum2 = 0.0
46
+
47
+ each { |time, val|
48
+ min = val if val < min
49
+ max = val if val > max
50
+ sum = sum + val
51
+ sum2 = sum2 + val ** 2
52
+ }
53
+
54
+ @stats = {
55
+ :num => size,
56
+ :min => min,
57
+ :max => max,
58
+ :sum => sum,
59
+ :mean => sum / size,
60
+ :stddev => Math.sqrt((sum2 / size) - ((sum / size) ** 2))
61
+ }
62
+ end
63
+
64
+ # slice a timeseries by timestamps
65
+ # +t1+ start time
66
+ # +t2+ end time
67
+ def slice t1, t2
68
+ idx1 = nearest(t1)
69
+ idx2 = nearest(t2)
70
+
71
+ # don't include a value not in range
72
+ if time_at(idx1) < t1
73
+ idx1 += 1
74
+ end
75
+
76
+ # slice goes up to, but doesn't include, so only
77
+ # add if the nearest is less than
78
+ if time_at(idx2) < t2
79
+ idx2 += 1
80
+ end
81
+
82
+ TS.new(@data[idx1..idx2])
83
+ end
84
+
85
+ # give the timeseries with values after time
86
+ # +time+ the time boundary
87
+ def after time
88
+ idx = nearest(time)
89
+ if time_at(idx) <= time
90
+ idx += 1
91
+ end
92
+
93
+ TS.new(@data[idx..-1])
94
+ end
95
+
96
+ # give the timeseries with values before time
97
+ # +time+ the time boundary
98
+ def before time
99
+ idx = nearest(time)
100
+ if time_at(idx) < time
101
+ idx += 1
102
+ end
103
+
104
+ TS.new(@data[0..idx-1])
105
+ end
106
+
107
+ def value_at idx
108
+ @data[idx].last
109
+ end
110
+
111
+ def time_at idx
112
+ @data[idx].first
113
+ end
114
+
115
+ # find the nearest idx for a given time
116
+ # using a fuzzy binary search
117
+ def nearest time
118
+ bsearch time, 0, size - 1
119
+ end
120
+
121
+ def timestamps
122
+ @data.transpose.first
123
+ end
124
+
125
+ def values
126
+ @data.transpose.last
127
+ end
128
+
129
+ # Run a regression and calculate r, r2, the slope, and intercept
130
+ def regression
131
+ return @regression if @regression
132
+
133
+ times, values = @data.transpose
134
+
135
+ t_mean = times.reduce(:+) / size
136
+ v_mean = values.reduce(:+) / size
137
+
138
+ slope = (0..size - 1).inject(0) { |sum, n|
139
+ sum + (times[n] - t_mean) * (values[n] - v_mean)
140
+ } / times.inject { |sum, n|
141
+ sum + (n - t_mean) ** 2
142
+ }
143
+
144
+ # now r2
145
+ r = slope * (stddev(times) / stddev(values))
146
+
147
+ @regression = {
148
+ :r2 => r ** 2,
149
+ :slope => slope,
150
+ :y_intercept => v_mean - (slope * t_mean)
151
+ }
152
+ end
153
+
154
+ private
155
+
156
+ # Find the nearest index for a given time (fuzzy search)
157
+ def bsearch time, idx1, idx2
158
+ mid = ((idx2 - idx1) / 2.0).floor.to_i + idx1
159
+ if idx1 == mid
160
+ diff1 = (time_at(idx1) - time).abs
161
+ diff2 = (time_at(idx2) - time).abs
162
+ diff2 > diff1 ? idx1 : idx2
163
+ elsif time < time_at(mid)
164
+ bsearch time, idx1, mid
165
+ elsif time > time_at(mid)
166
+ bsearch time, mid, idx2
167
+ else
168
+ mid
169
+ end
170
+ end
171
+
172
+ def stddev data
173
+ sum = 0.0
174
+ sum2 = 0.0
175
+ data.each { |v|
176
+ sum += v
177
+ sum2 += v ** 2
178
+ }
179
+ Math.sqrt(sum2 / data.size - (sum / data.size) ** 2)
180
+ end
181
+
182
+ end
data/test/test_ts.rb ADDED
@@ -0,0 +1,60 @@
1
+ require "test/unit"
2
+ require "ts"
3
+
4
+ class TSTest < Test::Unit::TestCase
5
+
6
+ def setup
7
+ raw = (1..1000).map { |i|
8
+ [i * 1000, i.to_f]
9
+ }
10
+ @ts = TS.new(raw)
11
+ end
12
+
13
+ def test_init
14
+ assert_equal 1000, @ts.size
15
+ end
16
+
17
+ def test_enum
18
+ assert_equal 1000, @ts.count
19
+ end
20
+
21
+ def test_stats
22
+ assert_equal 1, @ts.stats[:min]
23
+ assert_equal 1000, @ts.stats[:max]
24
+ assert_equal 1000, @ts.stats[:num]
25
+ assert_equal (1000 * (1000 + 1)) / 2, @ts.stats[:sum]
26
+ assert_in_delta 288, @ts.stats[:stddev], 1.0
27
+ assert_in_delta 500, @ts.stats[:mean], 1.0
28
+ end
29
+
30
+ def test_slice
31
+ assert_equal 3, @ts.slice(1000, 3000).size
32
+ end
33
+
34
+ def test_after
35
+ assert_equal 999, @ts.after(1000).size
36
+ end
37
+
38
+ def test_before
39
+ assert_equal 1, @ts.before(2000).size
40
+ end
41
+
42
+ def test_timestamps
43
+ assert_equal 1000, @ts.timestamps.size
44
+ end
45
+
46
+ def test_values
47
+ assert_equal 1000, @ts.values.last
48
+ end
49
+
50
+ def test_regression
51
+ assert_in_delta 0.001, @ts.regression[:slope], 0.001
52
+ assert_in_delta 1.0, @ts.regression[:r2], 0.01
53
+ assert_in_delta -1.5, @ts.regression[:y_intercept], 0.1
54
+ end
55
+
56
+ def test_collect
57
+ assert @ts.map { |t, v| [t, v * 2] }.stats[:min] == 2
58
+ end
59
+
60
+ end
data/ts.gemspec ADDED
@@ -0,0 +1,20 @@
1
+ $:.push File.expand_path("../lib", __FILE__)
2
+ require "ts"
3
+
4
+ spec = Gem::Specification.new do |s|
5
+ s.name = "ts"
6
+ s.version = TS::Version
7
+ s.date = "2013-08-14"
8
+ s.summary = "Utility gem for numeric time series data"
9
+ s.email = "dan.simpson@gmail.com"
10
+ s.homepage = "https://github.com/dansimpson/ts.rb"
11
+ s.description = "Utilities for numeric time series data"
12
+ s.has_rdoc = true
13
+
14
+ s.authors = ["Dan Simpson"]
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+
20
+ end
metadata ADDED
@@ -0,0 +1,48 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ts
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Dan Simpson
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-08-14 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Utilities for numeric time series data
14
+ email: dan.simpson@gmail.com
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - .gitignore
20
+ - README.md
21
+ - Rakefile
22
+ - lib/ts.rb
23
+ - test/test_ts.rb
24
+ - ts.gemspec
25
+ homepage: https://github.com/dansimpson/ts.rb
26
+ licenses: []
27
+ metadata: {}
28
+ post_install_message:
29
+ rdoc_options: []
30
+ require_paths:
31
+ - lib
32
+ required_ruby_version: !ruby/object:Gem::Requirement
33
+ requirements:
34
+ - - '>='
35
+ - !ruby/object:Gem::Version
36
+ version: '0'
37
+ required_rubygems_version: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - '>='
40
+ - !ruby/object:Gem::Version
41
+ version: '0'
42
+ requirements: []
43
+ rubyforge_project:
44
+ rubygems_version: 2.0.3
45
+ signing_key:
46
+ specification_version: 4
47
+ summary: Utility gem for numeric time series data
48
+ test_files: []