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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b8cdf7fd6b309bc24d83bb12e4ad87ba1ba36ba0c6abc803485f11a5718ab649
4
- data.tar.gz: d63c201452d57ef99349f3bf8b51249863cadbc555daef6a1f122d2ede571fdb
3
+ metadata.gz: e97133837196d4e1c97d69687d42f2d97e552d7be897f5e7a805efb5bab73e32
4
+ data.tar.gz: 4450d57d2c3da8632011f9f5a802891586b3e19347abe377e533ba5e8922708f
5
5
  SHA512:
6
- metadata.gz: 81991c4edf0fa86d34e876ff8ebf2344491ae10dde95d736bbae3987592e7d419dc5167bf2b390eec19a88700152fff8c8f7eadb18f2d81fcfa928c9172c1ee8
7
- data.tar.gz: 17e146ec6c6f74c792ac6da8bcd1103ad34df8303c7c76961ef956fcdbd0b343f14ff6a97ec4774c4d813dcfd07f5c3a1404365f24f27a636450f2bed9e895b0
6
+ metadata.gz: 6f0ed88d1a93d2f15e9750640833dbd889d8dea86255c8ec29c0fdc608ce27d17a0f617cbcaaee0be4b469b8e945f0ead9161875907a44a0555173e0f1a2c984
7
+ data.tar.gz: 485b4742b5267a8540445a87d59320a6ba5cc5589192369d22d69bfc1002d1ae2cb822a88a547ab63ff113e44b5ba47db51c45acb4bedc84079afd57210ea4ed
@@ -1,3 +1,10 @@
1
+ ## 0.1.1 (2020-04-10)
2
+
3
+ - Added `add_changepoints_to_plot`
4
+ - Fixed error with `changepoints` option
5
+ - Fixed error with `mcmc_samples` option
6
+ - Fixed error with additional regressors
7
+
1
8
  ## 0.1.0 (2020-04-09)
2
9
 
3
10
  - First release
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
- m.plot_components(forecast).savefig("components.png")
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
- fcst = m.predict(future)
170
- m.plot(fcst).savefig("forecast.png")
242
+ forecast = m.predict(future)
171
243
  ```
172
244
 
173
245
  ## Resources
@@ -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["mu"]) / props["std"])
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
- hist_size = (@history.shape[0] * @changepoint_range).floor
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
- if @n_changepoints + 1 > hist_size
224
- @n_changepoints = hist_size - 1
225
- logger.info "n_changepoints greater than number of observations. Using #{@n_changepoints}"
226
- end
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
- if @n_changepoints > 0
229
- step = (hist_size - 1) / @n_changepoints.to_f
230
- cp_indexes = (@n_changepoints + 1).times.map { |i| (i * step).round }
231
- @changepoints = @history["ds"][*cp_indexes][1..-1]
232
- else
233
- @changepoints = []
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::NArray.asarray(((@changepoints - @start) / @t_scale.to_f).to_a).sort
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
- start = Time.utc(1970).to_i
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, true]
852
- m = @params["m"][iteration, true]
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
- dates = (periods + 1).times.map { |i| last_date + i * 86400 }
914
- when "H"
915
- dates = (periods + 1).times.map { |i| last_date + i * 3600 }
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
@@ -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.each do |name|
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
@@ -1,3 +1,3 @@
1
1
  module Prophet
2
- VERSION = "0.1.0"
2
+ VERSION = "0.1.1"
3
3
  end
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.0
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-09 00:00:00.000000000 Z
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: '0'
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: '0'
26
+ version: 0.1.2
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: daru
29
29
  requirement: !ruby/object:Gem::Requirement