ts 1.0.0 → 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/lib/ts.rb +68 -9
- data/test/test_ts.rb +15 -1
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 22f6b0505160ceb7fe7c019d4c101aacbd35039c
|
4
|
+
data.tar.gz: 76a7f282ebfd76d98e430708d0a78ada36b4ad59
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cadb0aea96d5b5614455f618ca3759f6446e0dae53ece7259d851a2fbe7aa0bbef94675d2eb7ffaea53413f3468b2388aef59acae307c87c3beb72e3831dc9f0
|
7
|
+
data.tar.gz: d77c9c1418a81bf0415a33b9cc0d664c8cfa32137065ee2446f3d2cccbb28f105a24d1b3647f4c4502ed4a15b3fe0182eadae22dadef8575b7fc87060b5be3d6
|
data/.gitignore
CHANGED
@@ -0,0 +1 @@
|
|
1
|
+
*.gem
|
data/lib/ts.rb
CHANGED
@@ -6,7 +6,7 @@
|
|
6
6
|
#
|
7
7
|
class TS
|
8
8
|
|
9
|
-
Version = "1.0.
|
9
|
+
Version = "1.0.1"
|
10
10
|
|
11
11
|
include Enumerable
|
12
12
|
|
@@ -36,6 +36,33 @@ class TS
|
|
36
36
|
TS.new(@data.map { |v| yield *v })
|
37
37
|
end
|
38
38
|
|
39
|
+
# run a simple moving average, and return a new TS instance
|
40
|
+
# +size+ the size of the window
|
41
|
+
def sma size
|
42
|
+
buf = []
|
43
|
+
sum = 0
|
44
|
+
|
45
|
+
map { |t, v|
|
46
|
+
buf << v
|
47
|
+
sum += v
|
48
|
+
|
49
|
+
if buf.size > size
|
50
|
+
sum -= buf.shift
|
51
|
+
end
|
52
|
+
|
53
|
+
[t, sum / buf.size]
|
54
|
+
}
|
55
|
+
end
|
56
|
+
|
57
|
+
# generate some statistics from the values of the series
|
58
|
+
# returns {
|
59
|
+
# :num => ...,
|
60
|
+
# :min => ...,
|
61
|
+
# :max => ...,
|
62
|
+
# :sum => ...,
|
63
|
+
# :mean => ...,
|
64
|
+
# :stddev => ...,
|
65
|
+
# }
|
39
66
|
def stats
|
40
67
|
return @stats if @stats
|
41
68
|
|
@@ -47,8 +74,8 @@ class TS
|
|
47
74
|
each { |time, val|
|
48
75
|
min = val if val < min
|
49
76
|
max = val if val > max
|
50
|
-
sum
|
51
|
-
sum2
|
77
|
+
sum += val
|
78
|
+
sum2 += val ** 2
|
52
79
|
}
|
53
80
|
|
54
81
|
@stats = {
|
@@ -104,10 +131,14 @@ class TS
|
|
104
131
|
TS.new(@data[0..idx-1])
|
105
132
|
end
|
106
133
|
|
134
|
+
# fetch the value at a given index
|
135
|
+
# +idx+ the array index of the data
|
107
136
|
def value_at idx
|
108
137
|
@data[idx].last
|
109
138
|
end
|
110
139
|
|
140
|
+
# fetch the time at a given index
|
141
|
+
# +idx+ the array index of the data
|
111
142
|
def time_at idx
|
112
143
|
@data[idx].first
|
113
144
|
end
|
@@ -118,15 +149,24 @@ class TS
|
|
118
149
|
bsearch time, 0, size - 1
|
119
150
|
end
|
120
151
|
|
152
|
+
# get the timestamp series
|
121
153
|
def timestamps
|
122
154
|
@data.transpose.first
|
123
155
|
end
|
124
156
|
|
157
|
+
# get the value series
|
125
158
|
def values
|
126
159
|
@data.transpose.last
|
127
160
|
end
|
128
161
|
|
129
|
-
# Run a regression
|
162
|
+
# Run a regression on the series. Useful for weak projections
|
163
|
+
# and testing if your project is accurate (r2 =~ 1)
|
164
|
+
#
|
165
|
+
# returns {
|
166
|
+
# :r2 => ...,
|
167
|
+
# :slope => ...,
|
168
|
+
# :y_intercept => ...
|
169
|
+
# }
|
130
170
|
def regression
|
131
171
|
return @regression if @regression
|
132
172
|
|
@@ -135,22 +175,40 @@ class TS
|
|
135
175
|
t_mean = times.reduce(:+) / size
|
136
176
|
v_mean = values.reduce(:+) / size
|
137
177
|
|
138
|
-
slope = (0..size - 1).inject(0) { |sum, n|
|
178
|
+
slope = (0..size - 1).inject(0.0) { |sum, n|
|
139
179
|
sum + (times[n] - t_mean) * (values[n] - v_mean)
|
140
|
-
} / times.inject { |sum, n|
|
180
|
+
} / times.inject(0.0) { |sum, n|
|
141
181
|
sum + (n - t_mean) ** 2
|
142
182
|
}
|
143
183
|
|
144
|
-
# now r2
|
145
184
|
r = slope * (stddev(times) / stddev(values))
|
146
185
|
|
147
186
|
@regression = {
|
148
|
-
:r2 => r
|
187
|
+
:r2 => r * r,
|
149
188
|
:slope => slope,
|
150
189
|
:y_intercept => v_mean - (slope * t_mean)
|
151
190
|
}
|
152
191
|
end
|
153
192
|
|
193
|
+
# Project the value at a given time using the regresion
|
194
|
+
#
|
195
|
+
# y = mx + b
|
196
|
+
#
|
197
|
+
# +time+ the timestamp of the value you wish to predict
|
198
|
+
def projected_value time
|
199
|
+
regression[:slope] * time + regression[:y_intercept]
|
200
|
+
end
|
201
|
+
|
202
|
+
# Estimate the time for a given value. Assumes a fairly linear
|
203
|
+
# model.
|
204
|
+
#
|
205
|
+
# x = (y - b) / m
|
206
|
+
#
|
207
|
+
# +value+ the timestamp of the value you wish to predict
|
208
|
+
def projected_time value
|
209
|
+
(value - regression[:y_intercept]) / regression[:slope]
|
210
|
+
end
|
211
|
+
|
154
212
|
private
|
155
213
|
|
156
214
|
# Find the nearest index for a given time (fuzzy search)
|
@@ -169,6 +227,7 @@ class TS
|
|
169
227
|
end
|
170
228
|
end
|
171
229
|
|
230
|
+
# calculate the std deviation of the 1d data set
|
172
231
|
def stddev data
|
173
232
|
sum = 0.0
|
174
233
|
sum2 = 0.0
|
@@ -176,7 +235,7 @@ class TS
|
|
176
235
|
sum += v
|
177
236
|
sum2 += v ** 2
|
178
237
|
}
|
179
|
-
Math.sqrt(sum2 / data.size - (sum / data.size) ** 2)
|
238
|
+
Math.sqrt((sum2 / data.size) - ((sum / data.size) ** 2))
|
180
239
|
end
|
181
240
|
|
182
241
|
end
|
data/test/test_ts.rb
CHANGED
@@ -50,11 +50,25 @@ class TSTest < Test::Unit::TestCase
|
|
50
50
|
def test_regression
|
51
51
|
assert_in_delta 0.001, @ts.regression[:slope], 0.001
|
52
52
|
assert_in_delta 1.0, @ts.regression[:r2], 0.01
|
53
|
-
assert_in_delta
|
53
|
+
assert_in_delta 0.0, @ts.regression[:y_intercept], 0.1
|
54
54
|
end
|
55
55
|
|
56
56
|
def test_collect
|
57
57
|
assert @ts.map { |t, v| [t, v * 2] }.stats[:min] == 2
|
58
58
|
end
|
59
59
|
|
60
|
+
def test_sma
|
61
|
+
assert_equal [2000, 2], @ts.data[1]
|
62
|
+
assert_equal [2000, 1.5], @ts.sma(7).data[1]
|
63
|
+
end
|
64
|
+
|
65
|
+
def test_projection
|
66
|
+
assert_equal 5000, @ts.projected_value(5000000)
|
67
|
+
end
|
68
|
+
|
69
|
+
def test_projection_time
|
70
|
+
assert_equal 5000000, @ts.projected_time(5000)
|
71
|
+
assert_equal -1000, @ts.projected_time(-1)
|
72
|
+
end
|
73
|
+
|
60
74
|
end
|