prophet-rb 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +7 -0
- data/README.md +75 -3
- data/lib/prophet/forecaster.rb +42 -22
- data/lib/prophet/plot.rb +19 -1
- data/lib/prophet/version.rb +1 -1
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e97133837196d4e1c97d69687d42f2d97e552d7be897f5e7a805efb5bab73e32
|
4
|
+
data.tar.gz: 4450d57d2c3da8632011f9f5a802891586b3e19347abe377e533ba5e8922708f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6f0ed88d1a93d2f15e9750640833dbd889d8dea86255c8ec29c0fdc608ce27d17a0f617cbcaaee0be4b469b8e945f0ead9161875907a44a0555173e0f1a2c984
|
7
|
+
data.tar.gz: 485b4742b5267a8540445a87d59320a6ba5cc5589192369d22d69bfc1002d1ae2cb822a88a547ab63ff113e44b5ba47db51c45acb4bedc84079afd57210ea4ed
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -10,6 +10,8 @@ Supports:
|
|
10
10
|
|
11
11
|
And gracefully handles missing data
|
12
12
|
|
13
|
+
[![Build Status](https://travis-ci.org/ankane/prophet.svg?branch=master)](https://travis-ci.org/ankane/prophet)
|
14
|
+
|
13
15
|
## Installation
|
14
16
|
|
15
17
|
Add this line to your application’s Gemfile:
|
@@ -98,6 +100,45 @@ m.plot_components(forecast).savefig("components.png")
|
|
98
100
|
|
99
101
|
![Components](https://blazer.dokkuapp.com/assets/prophet/components-b9e31bfcf77e57bbd503c0bcff5e5544e66085b90709b06dd96c5f622a87d84f.png)
|
100
102
|
|
103
|
+
## Saturating Forecasts
|
104
|
+
|
105
|
+
[Explanation](https://facebook.github.io/prophet/docs/saturating_forecasts.html)
|
106
|
+
|
107
|
+
Forecast logistic growth instead of linear
|
108
|
+
|
109
|
+
```ruby
|
110
|
+
df = Daru::DataFrame.from_csv("example_wp_log_R.csv")
|
111
|
+
df["cap"] = 8.5
|
112
|
+
m = Prophet.new(growth: "logistic")
|
113
|
+
m.fit(df)
|
114
|
+
future = m.make_future_dataframe(periods: 365)
|
115
|
+
future["cap"] = 8.5
|
116
|
+
forecast = m.predict(future)
|
117
|
+
```
|
118
|
+
|
119
|
+
## Trend Changepoints
|
120
|
+
|
121
|
+
[Explanation](https://facebook.github.io/prophet/docs/trend_changepoints.html)
|
122
|
+
|
123
|
+
Plot changepoints
|
124
|
+
|
125
|
+
```ruby
|
126
|
+
fig = m.plot(forecast)
|
127
|
+
m.add_changepoints_to_plot(fig.gca, forecast)
|
128
|
+
```
|
129
|
+
|
130
|
+
Adjust trend flexibility
|
131
|
+
|
132
|
+
```ruby
|
133
|
+
m = Prophet.new(changepoint_prior_scale: 0.5)
|
134
|
+
```
|
135
|
+
|
136
|
+
Specify the location of changepoints
|
137
|
+
|
138
|
+
```ruby
|
139
|
+
m = Prophet.new(changepoints: ["2014-01-01"])
|
140
|
+
```
|
141
|
+
|
101
142
|
## Holidays and Special Events
|
102
143
|
|
103
144
|
[Explanation](https://facebook.github.io/prophet/docs/seasonality,_holiday_effects,_and_regressors.html)
|
@@ -141,7 +182,25 @@ Specify custom seasonalities
|
|
141
182
|
m = Prophet.new(weekly_seasonality: false)
|
142
183
|
m.add_seasonality(name: "monthly", period: 30.5, fourier_order: 5)
|
143
184
|
forecast = m.fit(df).predict(future)
|
144
|
-
|
185
|
+
```
|
186
|
+
|
187
|
+
Specify additional regressors
|
188
|
+
|
189
|
+
```ruby
|
190
|
+
nfl_sunday = lambda do |ds|
|
191
|
+
date = ds.respond_to?(:to_date) ? ds.to_date : Date.parse(ds)
|
192
|
+
date.wday == 0 && (date.month > 8 || date.month < 2) ? 1 : 0
|
193
|
+
end
|
194
|
+
|
195
|
+
df["nfl_sunday"] = df["ds"].map(&nfl_sunday)
|
196
|
+
|
197
|
+
m = Prophet.new
|
198
|
+
m.add_regressor("nfl_sunday")
|
199
|
+
m.fit(df)
|
200
|
+
|
201
|
+
future["nfl_sunday"] = future["ds"].map(&nfl_sunday)
|
202
|
+
|
203
|
+
forecast = m.predict(future)
|
145
204
|
```
|
146
205
|
|
147
206
|
## Multiplicative Seasonality
|
@@ -156,6 +215,20 @@ future = m.make_future_dataframe(periods: 50, freq: "MS")
|
|
156
215
|
forecast = m.predict(future)
|
157
216
|
```
|
158
217
|
|
218
|
+
## Uncertainty Intervals
|
219
|
+
|
220
|
+
Specify the width of uncertainty intervals (80% by default)
|
221
|
+
|
222
|
+
```ruby
|
223
|
+
Prophet.new(interval_width: 0.95)
|
224
|
+
```
|
225
|
+
|
226
|
+
Get uncertainty in seasonality
|
227
|
+
|
228
|
+
```ruby
|
229
|
+
Prophet.new(mcmc_samples: 300)
|
230
|
+
```
|
231
|
+
|
159
232
|
## Non-Daily Data
|
160
233
|
|
161
234
|
[Explanation](https://facebook.github.io/prophet/docs/non-daily_data.html)
|
@@ -166,8 +239,7 @@ Sub-daily data
|
|
166
239
|
df = Daru::DataFrame.from_csv("example_yosemite_temps.csv")
|
167
240
|
m = Prophet.new(changepoint_prior_scale: 0.01).fit(df)
|
168
241
|
future = m.make_future_dataframe(periods: 300, freq: "H")
|
169
|
-
|
170
|
-
m.plot(fcst).savefig("forecast.png")
|
242
|
+
forecast = m.predict(future)
|
171
243
|
```
|
172
244
|
|
173
245
|
## Resources
|
data/lib/prophet/forecaster.rb
CHANGED
@@ -156,7 +156,7 @@ module Prophet
|
|
156
156
|
raise ArgumentError, "Regressor #{name.inspect} missing from dataframe"
|
157
157
|
end
|
158
158
|
df[name] = df[name].map(&:to_f)
|
159
|
-
if df[name].any?(&:nil)
|
159
|
+
if df[name].any?(&:nil?)
|
160
160
|
raise ArgumentError, "Found NaN in column #{name.inspect}"
|
161
161
|
end
|
162
162
|
end
|
@@ -201,7 +201,7 @@ module Prophet
|
|
201
201
|
end
|
202
202
|
|
203
203
|
@extra_regressors.each do |name, props|
|
204
|
-
df[name] = ((df[name] - props[
|
204
|
+
df[name] = ((df[name] - props[:mu]) / props[:std])
|
205
205
|
end
|
206
206
|
|
207
207
|
df
|
@@ -218,32 +218,40 @@ module Prophet
|
|
218
218
|
end
|
219
219
|
|
220
220
|
def set_changepoints
|
221
|
-
|
221
|
+
if @changepoints
|
222
|
+
if @changepoints.size > 0
|
223
|
+
too_low = @changepoints.min < @history["ds"].min
|
224
|
+
too_high = @changepoints.max > @history["ds"].max
|
225
|
+
if too_low || too_high
|
226
|
+
raise ArgumentError, "Changepoints must fall within training data."
|
227
|
+
end
|
228
|
+
end
|
229
|
+
else
|
230
|
+
hist_size = (@history.shape[0] * @changepoint_range).floor
|
222
231
|
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
232
|
+
if @n_changepoints + 1 > hist_size
|
233
|
+
@n_changepoints = hist_size - 1
|
234
|
+
logger.info "n_changepoints greater than number of observations. Using #{@n_changepoints}"
|
235
|
+
end
|
227
236
|
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
237
|
+
if @n_changepoints > 0
|
238
|
+
step = (hist_size - 1) / @n_changepoints.to_f
|
239
|
+
cp_indexes = (@n_changepoints + 1).times.map { |i| (i * step).round }
|
240
|
+
@changepoints = ensure_arr(@history["ds"][*cp_indexes].to_a.last(cp_indexes.size - 1))
|
241
|
+
else
|
242
|
+
@changepoints = []
|
243
|
+
end
|
234
244
|
end
|
235
245
|
|
236
246
|
if @changepoints.size > 0
|
237
|
-
@changepoints_t = Numo::
|
247
|
+
@changepoints_t = (Numo::DFloat.cast(@changepoints.map(&:to_i).sort) - @start.to_i) / @t_scale.to_f
|
238
248
|
else
|
239
249
|
@changepoints_t = Numo::NArray.asarray([0])
|
240
250
|
end
|
241
251
|
end
|
242
252
|
|
243
253
|
def fourier_series(dates, period, series_order)
|
244
|
-
|
245
|
-
# uses to_datetime first so we get UTC
|
246
|
-
t = Numo::DFloat.asarray(dates.map { |v| v.to_i - start }) / (3600 * 24.0)
|
254
|
+
t = Numo::DFloat.asarray(dates.map(&:to_i)) / (3600 * 24.0)
|
247
255
|
|
248
256
|
# no need for column_stack
|
249
257
|
series_order.times.flat_map do |i|
|
@@ -848,8 +856,8 @@ module Prophet
|
|
848
856
|
end
|
849
857
|
|
850
858
|
def sample_predictive_trend(df, iteration)
|
851
|
-
k = @params["k"][iteration
|
852
|
-
m = @params["m"][iteration
|
859
|
+
k = @params["k"][iteration]
|
860
|
+
m = @params["m"][iteration]
|
853
861
|
deltas = @params["delta"][iteration, true]
|
854
862
|
|
855
863
|
t = Numo::NArray.asarray(df["t"].to_a)
|
@@ -907,12 +915,19 @@ module Prophet
|
|
907
915
|
def make_future_dataframe(periods:, freq: "D", include_history: true)
|
908
916
|
raise Error, "Model has not been fit" unless @history_dates
|
909
917
|
last_date = @history_dates.max
|
918
|
+
# TODO add more freq
|
919
|
+
# https://pandas.pydata.org/pandas-docs/stable/user_guide/timeseries.html#timeseries-offset-aliases
|
910
920
|
case freq
|
921
|
+
when "H"
|
922
|
+
hour = 3600
|
923
|
+
dates = (periods + 1).times.map { |i| last_date + i * hour }
|
911
924
|
when "D"
|
912
925
|
# days have constant length with UTC (no DST or leap seconds)
|
913
|
-
|
914
|
-
|
915
|
-
|
926
|
+
day = 24 * 3600
|
927
|
+
dates = (periods + 1).times.map { |i| last_date + i * day }
|
928
|
+
when "W"
|
929
|
+
week = 7 * 24 * 3600
|
930
|
+
dates = (periods + 1).times.map { |i| last_date + i * week }
|
916
931
|
when "MS"
|
917
932
|
dates = [last_date]
|
918
933
|
periods.times do
|
@@ -982,5 +997,10 @@ module Prophet
|
|
982
997
|
u = Numo::DFloat.new(size).rand - 0.5
|
983
998
|
loc - scale * u.sign * Numo::NMath.log(1 - 2 * u.abs)
|
984
999
|
end
|
1000
|
+
|
1001
|
+
def ensure_arr(value)
|
1002
|
+
value = [value] unless value.is_a?(Array)
|
1003
|
+
value
|
1004
|
+
end
|
985
1005
|
end
|
986
1006
|
end
|
data/lib/prophet/plot.rb
CHANGED
@@ -93,6 +93,24 @@ module Prophet
|
|
93
93
|
fig
|
94
94
|
end
|
95
95
|
|
96
|
+
# in Python, this is a separate method
|
97
|
+
def add_changepoints_to_plot(ax, fcst, threshold: 0.01, cp_color: "r", cp_linestyle: "--", trend: true)
|
98
|
+
artists = []
|
99
|
+
if trend
|
100
|
+
artists << ax.plot(to_pydatetime(fcst["ds"]), fcst["trend"].map(&:to_f), c: cp_color)
|
101
|
+
end
|
102
|
+
signif_changepoints =
|
103
|
+
if @changepoints.size > 0
|
104
|
+
(@params["delta"].mean(axis: 0, nan: true).abs >= threshold).mask(@changepoints)
|
105
|
+
else
|
106
|
+
[]
|
107
|
+
end
|
108
|
+
to_pydatetime(signif_changepoints).each do |cp|
|
109
|
+
artists << ax.axvline(x: cp, c: cp_color, ls: cp_linestyle)
|
110
|
+
end
|
111
|
+
artists
|
112
|
+
end
|
113
|
+
|
96
114
|
private
|
97
115
|
|
98
116
|
def plot_forecast_component(fcst, name, ax: nil, uncertainty: true, plot_cap: false, figsize: [10, 6])
|
@@ -128,7 +146,7 @@ module Prophet
|
|
128
146
|
|
129
147
|
def seasonality_plot_df(ds)
|
130
148
|
df_dict = {"ds" => ds, "cap" => [1.0] * ds.size, "floor" => [0.0] * ds.size}
|
131
|
-
@extra_regressors.
|
149
|
+
@extra_regressors.each_key do |name|
|
132
150
|
df_dict[name] = [0.0] * ds.size
|
133
151
|
end
|
134
152
|
# Activate all conditional seasonality columns
|
data/lib/prophet/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: prophet-rb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrew Kane
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-04-
|
11
|
+
date: 2020-04-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: cmdstan
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
19
|
+
version: 0.1.2
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version:
|
26
|
+
version: 0.1.2
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: daru
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|