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