Class: TS

Inherits:
Object
  • Object
show all
Includes:
Enumerable
Defined in:
lib/ts.rb

Overview

TS

Utility class for [timestamp, number] tuples, where periodicity is not guaranteed.

Constant Summary collapse

Version =
"1.0.3"

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(data) ⇒ TS

data an array of [timestamp/time, number] tuples



16
17
18
19
20
21
22
# File 'lib/ts.rb', line 16

def initialize data
  if data.nil?
    raise "Cannot instantiate timeseries without data"
  end

  @data = data
end

Instance Attribute Details

#dataObject (readonly)

Returns the value of attribute data.



13
14
15
# File 'lib/ts.rb', line 13

def data
  @data
end

Instance Method Details

#after(time) ⇒ Object

give the timeseries with values after time time the time boundary



124
125
126
127
128
129
130
131
# File 'lib/ts.rb', line 124

def after time
  idx = nearest(time)
  if time_at(idx) <= time
    idx += 1
  end

  TS.new(@data[idx..-1])
end

#before(time) ⇒ Object

give the timeseries with values before time time the time boundary



135
136
137
138
139
140
141
142
# File 'lib/ts.rb', line 135

def before time
  idx = nearest(time)
  return TS.new([]) if idx <= 0
  if time_at(idx) < time
    idx += 1
  end
  TS.new(@data[0..idx-1])
end

#eachObject

see Enumerable



30
31
32
# File 'lib/ts.rb', line 30

def each
  @data.each { |v| yield *v }
end

#mapObject

map the [time,value] tuples into other [time,value] tuples



35
36
37
# File 'lib/ts.rb', line 35

def map
  TS.new(@data.map { |v| yield *v })
end

#meanObject

get the mean value



92
93
94
# File 'lib/ts.rb', line 92

def mean
  stats[:mean]
end

#nearest(time) ⇒ Object

find the nearest idx for a given time using a fuzzy binary search



158
159
160
# File 'lib/ts.rb', line 158

def nearest time
  bsearch time, 0, size - 1
end

#projected_time(value) ⇒ Object

Estimate the time for a given value. Assumes a fairly linear model.

x = (y - b) / m

value the timestamp of the value you wish to predict



218
219
220
# File 'lib/ts.rb', line 218

def projected_time value
  (value - regression[:y_intercept]) / regression[:slope]
end

#projected_value(time) ⇒ Object

Project the value at a given time using the regresion

y = mx + b

time the timestamp of the value you wish to predict



208
209
210
# File 'lib/ts.rb', line 208

def projected_value time
  regression[:slope] * time + regression[:y_intercept]
end

#regressionObject

Run a regression on the series. Useful for weak projections and testing if your project is accurate (r2 =~ 1)

returns

:r2 => ...,
:slope => ...,
:y_intercept => ...



180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
# File 'lib/ts.rb', line 180

def regression
  return @regression if @regression

  times, values = @data.transpose

  t_mean = times.reduce(:+) / size
  v_mean = values.reduce(:+) / size

  slope = (0..size - 1).inject(0.0) { |sum, n|
    sum + (times[n] - t_mean) * (values[n] - v_mean)
  } / times.inject(0.0) { |sum, n|
    sum + (n - t_mean) ** 2
  }

  r = slope * (_stddev(times) / _stddev(values))

  @regression = {
    :r2 => r * r,
    :slope => slope,
    :y_intercept => v_mean - (slope * t_mean)
  }
end

#sizeObject

The number of elements in the set



25
26
27
# File 'lib/ts.rb', line 25

def size
  @data.size
end

#slice(t1, t2) ⇒ Object

slice a timeseries by timestamps t1 start time t2 end time



104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/ts.rb', line 104

def slice t1, t2
  idx1 = nearest(t1)
  idx2 = nearest(t2)

  # don't include a value not in range
  if time_at(idx1) < t1
    idx1 += 1
  end

  # slice goes up to, but doesn't include, so only
  # add if the nearest is less than
  if time_at(idx2) < t2
    idx2 += 1
  end

  TS.new(@data[idx1..idx2])
end

#sma(size) ⇒ Object

run a simple moving average, and return a new TS instance size the size of the window



41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/ts.rb', line 41

def sma size
  buf = []
  sum = 0

  map { |t, v|
    buf << v
    sum += v

    if buf.size > size
      sum -= buf.shift
    end

    [t, sum / buf.size]
  }
end

#statsObject

generate some statistics from the values of the series returns

:num => ...,
:min => ...,
:max => ...,
:sum => ...,
:mean => ...,
:stddev => ...,



66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
# File 'lib/ts.rb', line 66

def stats
  return @stats if @stats

  min  = Float::MAX
  max  = Float::MIN
  sum  = 0.0
  sum2 = 0.0

  each { |time, val|
    min = val if val < min
    max = val if val > max
    sum += val
    sum2 += val ** 2
  }

  @stats = {
    :num => size,
    :min => min,
    :max => max,
    :sum => sum,
    :mean => sum / size,
    :stddev => Math.sqrt((sum2 / size) - ((sum / size) ** 2))
  }
end

#stddevObject

get the standard deviation of the values



97
98
99
# File 'lib/ts.rb', line 97

def stddev
  stats[:stddev]
end

#time_at(idx) ⇒ Object

fetch the time at a given index idx the array index of the data



152
153
154
# File 'lib/ts.rb', line 152

def time_at idx
  @data[idx].first
end

#timestampsObject

get the timestamp series



163
164
165
# File 'lib/ts.rb', line 163

def timestamps
  @data.transpose.first
end

#value_at(idx) ⇒ Object

fetch the value at a given index idx the array index of the data



146
147
148
# File 'lib/ts.rb', line 146

def value_at idx
  @data[idx].last
end

#valuesObject

get the value series



168
169
170
# File 'lib/ts.rb', line 168

def values
  @data.transpose.last
end