ts 1.0.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.
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: []