prophet-rb 0.1.0 → 0.1.1
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.
- 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
|
+
[](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
|

|
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
|