prophet-rb 0.1.1 → 0.2.4
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 +21 -0
- data/LICENSE.txt +1 -1
- data/README.md +58 -21
- data/lib/prophet.rb +64 -1
- data/lib/prophet/forecaster.rb +135 -130
- data/lib/prophet/holidays.rb +2 -2
- data/lib/prophet/plot.rb +31 -31
- data/lib/prophet/stan_backend.rb +1 -1
- data/lib/prophet/version.rb +1 -1
- data/stan/unix/prophet.stan +27 -16
- data/stan/win/prophet.stan +15 -2
- metadata +12 -82
data/lib/prophet/holidays.rb
CHANGED
@@ -6,7 +6,7 @@ module Prophet
|
|
6
6
|
end
|
7
7
|
|
8
8
|
def make_holidays_df(year_list, country)
|
9
|
-
holidays_df
|
9
|
+
holidays_df[(holidays_df["country"] == country) & (holidays_df["year"].in?(year_list))][["ds", "holiday"]]
|
10
10
|
end
|
11
11
|
|
12
12
|
# TODO marshal on installation
|
@@ -20,7 +20,7 @@ module Prophet
|
|
20
20
|
holidays["country"] << row["country"]
|
21
21
|
holidays["year"] << row["year"]
|
22
22
|
end
|
23
|
-
|
23
|
+
Rover::DataFrame.new(holidays)
|
24
24
|
end
|
25
25
|
end
|
26
26
|
end
|
data/lib/prophet/plot.rb
CHANGED
@@ -8,16 +8,16 @@ module Prophet
|
|
8
8
|
fig = ax.get_figure
|
9
9
|
end
|
10
10
|
fcst_t = to_pydatetime(fcst["ds"])
|
11
|
-
ax.plot(to_pydatetime(@history["ds"]), @history["y"].
|
12
|
-
ax.plot(fcst_t, fcst["yhat"].
|
13
|
-
if fcst.
|
14
|
-
ax.plot(fcst_t, fcst["cap"].
|
11
|
+
ax.plot(to_pydatetime(@history["ds"]), @history["y"].to_a, "k.")
|
12
|
+
ax.plot(fcst_t, fcst["yhat"].to_a, ls: "-", c: "#0072B2")
|
13
|
+
if fcst.include?("cap") && plot_cap
|
14
|
+
ax.plot(fcst_t, fcst["cap"].to_a, ls: "--", c: "k")
|
15
15
|
end
|
16
|
-
if @logistic_floor && fcst.
|
17
|
-
ax.plot(fcst_t, fcst["floor"].
|
16
|
+
if @logistic_floor && fcst.include?("floor") && plot_cap
|
17
|
+
ax.plot(fcst_t, fcst["floor"].to_a, ls: "--", c: "k")
|
18
18
|
end
|
19
19
|
if uncertainty && @uncertainty_samples
|
20
|
-
ax.fill_between(fcst_t, fcst["yhat_lower"].
|
20
|
+
ax.fill_between(fcst_t, fcst["yhat_lower"].to_a, fcst["yhat_upper"].to_a, color: "#0072B2", alpha: 0.2)
|
21
21
|
end
|
22
22
|
# Specify formatting to workaround matplotlib issue #12925
|
23
23
|
locator = dates.AutoDateLocator.new(interval_multiples: false)
|
@@ -33,25 +33,25 @@ module Prophet
|
|
33
33
|
|
34
34
|
def plot_components(fcst, uncertainty: true, plot_cap: true, weekly_start: 0, yearly_start: 0, figsize: nil)
|
35
35
|
components = ["trend"]
|
36
|
-
if @train_holiday_names && fcst.
|
36
|
+
if @train_holiday_names && fcst.include?("holidays")
|
37
37
|
components << "holidays"
|
38
38
|
end
|
39
39
|
# Plot weekly seasonality, if present
|
40
|
-
if @seasonalities["weekly"] && fcst.
|
40
|
+
if @seasonalities["weekly"] && fcst.include?("weekly")
|
41
41
|
components << "weekly"
|
42
42
|
end
|
43
43
|
# Yearly if present
|
44
|
-
if @seasonalities["yearly"] && fcst.
|
44
|
+
if @seasonalities["yearly"] && fcst.include?("yearly")
|
45
45
|
components << "yearly"
|
46
46
|
end
|
47
47
|
# Other seasonalities
|
48
|
-
components.concat(@seasonalities.keys.select { |name| fcst.
|
48
|
+
components.concat(@seasonalities.keys.select { |name| fcst.include?(name) && !["weekly", "yearly"].include?(name) }.sort)
|
49
49
|
regressors = {"additive" => false, "multiplicative" => false}
|
50
50
|
@extra_regressors.each do |name, props|
|
51
51
|
regressors[props[:mode]] = true
|
52
52
|
end
|
53
53
|
["additive", "multiplicative"].each do |mode|
|
54
|
-
if regressors[mode] && fcst.
|
54
|
+
if regressors[mode] && fcst.include?("extra_regressors_#{mode}")
|
55
55
|
components << "extra_regressors_#{mode}"
|
56
56
|
end
|
57
57
|
end
|
@@ -97,11 +97,11 @@ module Prophet
|
|
97
97
|
def add_changepoints_to_plot(ax, fcst, threshold: 0.01, cp_color: "r", cp_linestyle: "--", trend: true)
|
98
98
|
artists = []
|
99
99
|
if trend
|
100
|
-
artists << ax.plot(to_pydatetime(fcst["ds"]), fcst["trend"].
|
100
|
+
artists << ax.plot(to_pydatetime(fcst["ds"]), fcst["trend"].to_a, c: cp_color)
|
101
101
|
end
|
102
102
|
signif_changepoints =
|
103
103
|
if @changepoints.size > 0
|
104
|
-
(@params["delta"].mean(axis: 0, nan: true).abs >= threshold).mask(@changepoints)
|
104
|
+
(@params["delta"].mean(axis: 0, nan: true).abs >= threshold).mask(@changepoints.to_numo)
|
105
105
|
else
|
106
106
|
[]
|
107
107
|
end
|
@@ -120,15 +120,15 @@ module Prophet
|
|
120
120
|
ax = fig.add_subplot(111)
|
121
121
|
end
|
122
122
|
fcst_t = to_pydatetime(fcst["ds"])
|
123
|
-
artists += ax.plot(fcst_t, fcst[name].
|
124
|
-
if fcst.
|
125
|
-
artists += ax.plot(fcst_t, fcst["cap"].
|
123
|
+
artists += ax.plot(fcst_t, fcst[name].to_a, ls: "-", c: "#0072B2")
|
124
|
+
if fcst.include?("cap") && plot_cap
|
125
|
+
artists += ax.plot(fcst_t, fcst["cap"].to_a, ls: "--", c: "k")
|
126
126
|
end
|
127
|
-
if @logistic_floor && fcst.
|
128
|
-
ax.plot(fcst_t, fcst["floor"].
|
127
|
+
if @logistic_floor && fcst.include?("floor") && plot_cap
|
128
|
+
ax.plot(fcst_t, fcst["floor"].to_a, ls: "--", c: "k")
|
129
129
|
end
|
130
130
|
if uncertainty && @uncertainty_samples
|
131
|
-
artists += [ax.fill_between(fcst_t, fcst[name + "_lower"].
|
131
|
+
artists += [ax.fill_between(fcst_t, fcst[name + "_lower"].to_a, fcst[name + "_upper"].to_a, color: "#0072B2", alpha: 0.2)]
|
132
132
|
end
|
133
133
|
# Specify formatting to workaround matplotlib issue #12925
|
134
134
|
locator = dates.AutoDateLocator.new(interval_multiples: false)
|
@@ -145,17 +145,17 @@ module Prophet
|
|
145
145
|
end
|
146
146
|
|
147
147
|
def seasonality_plot_df(ds)
|
148
|
-
df_dict = {"ds" => ds, "cap" =>
|
148
|
+
df_dict = {"ds" => ds, "cap" => 1.0, "floor" => 0.0}
|
149
149
|
@extra_regressors.each_key do |name|
|
150
|
-
df_dict[name] =
|
150
|
+
df_dict[name] = 0.0
|
151
151
|
end
|
152
152
|
# Activate all conditional seasonality columns
|
153
153
|
@seasonalities.values.each do |props|
|
154
154
|
if props[:condition_name]
|
155
|
-
df_dict[props[:condition_name]] =
|
155
|
+
df_dict[props[:condition_name]] = true
|
156
156
|
end
|
157
157
|
end
|
158
|
-
df =
|
158
|
+
df = Rover::DataFrame.new(df_dict)
|
159
159
|
df = setup_dataframe(df)
|
160
160
|
df
|
161
161
|
end
|
@@ -172,9 +172,9 @@ module Prophet
|
|
172
172
|
df_w = seasonality_plot_df(days)
|
173
173
|
seas = predict_seasonal_components(df_w)
|
174
174
|
days = days.map { |v| v.strftime("%A") }
|
175
|
-
artists += ax.plot(days.size.times.to_a, seas[name].
|
175
|
+
artists += ax.plot(days.size.times.to_a, seas[name].to_a, ls: "-", c: "#0072B2")
|
176
176
|
if uncertainty && @uncertainty_samples
|
177
|
-
artists += [ax.fill_between(days.size.times.to_a, seas[name + "_lower"].
|
177
|
+
artists += [ax.fill_between(days.size.times.to_a, seas[name + "_lower"].to_a, seas[name + "_upper"].to_a, color: "#0072B2", alpha: 0.2)]
|
178
178
|
end
|
179
179
|
ax.grid(true, which: "major", c: "gray", ls: "-", lw: 1, alpha: 0.2)
|
180
180
|
ax.set_xticks(days.size.times.to_a)
|
@@ -198,9 +198,9 @@ module Prophet
|
|
198
198
|
days = 365.times.map { |i| start + i + yearly_start }
|
199
199
|
df_y = seasonality_plot_df(days)
|
200
200
|
seas = predict_seasonal_components(df_y)
|
201
|
-
artists += ax.plot(to_pydatetime(df_y["ds"]), seas[name].
|
201
|
+
artists += ax.plot(to_pydatetime(df_y["ds"]), seas[name].to_a, ls: "-", c: "#0072B2")
|
202
202
|
if uncertainty && @uncertainty_samples
|
203
|
-
artists += [ax.fill_between(to_pydatetime(df_y["ds"]), seas[name + "_lower"].
|
203
|
+
artists += [ax.fill_between(to_pydatetime(df_y["ds"]), seas[name + "_lower"].to_a, seas[name + "_upper"].to_a, color: "#0072B2", alpha: 0.2)]
|
204
204
|
end
|
205
205
|
ax.grid(true, which: "major", c: "gray", ls: "-", lw: 1, alpha: 0.2)
|
206
206
|
months = dates.MonthLocator.new((1..12).to_a, bymonthday: 1, interval: 2)
|
@@ -231,9 +231,9 @@ module Prophet
|
|
231
231
|
days = plot_points.times.map { |i| Time.at(start + i * step).utc }
|
232
232
|
df_y = seasonality_plot_df(days)
|
233
233
|
seas = predict_seasonal_components(df_y)
|
234
|
-
artists += ax.plot(to_pydatetime(df_y["ds"]), seas[name].
|
234
|
+
artists += ax.plot(to_pydatetime(df_y["ds"]), seas[name].to_a, ls: "-", c: "#0072B2")
|
235
235
|
if uncertainty && @uncertainty_samples
|
236
|
-
artists += [ax.fill_between(to_pydatetime(df_y["ds"]), seas[name + "_lower"].
|
236
|
+
artists += [ax.fill_between(to_pydatetime(df_y["ds"]), seas[name + "_lower"].to_a, seas[name + "_upper"].to_a, color: "#0072B2", alpha: 0.2)]
|
237
237
|
end
|
238
238
|
ax.grid(true, which: "major", c: "gray", ls: "-", lw: 1, alpha: 0.2)
|
239
239
|
step = (finish - start) / (7 - 1).to_f
|
@@ -281,7 +281,7 @@ module Prophet
|
|
281
281
|
|
282
282
|
def to_pydatetime(v)
|
283
283
|
datetime = PyCall.import_module("datetime")
|
284
|
-
v.map { |v| datetime.datetime.utcfromtimestamp(v.to_i) }
|
284
|
+
v.map { |v| datetime.datetime.utcfromtimestamp(v.to_i) }.to_a
|
285
285
|
end
|
286
286
|
end
|
287
287
|
end
|
data/lib/prophet/stan_backend.rb
CHANGED
@@ -127,7 +127,7 @@ module Prophet
|
|
127
127
|
stan_data["t_change"] = stan_data["t_change"].to_a
|
128
128
|
stan_data["s_a"] = stan_data["s_a"].to_a
|
129
129
|
stan_data["s_m"] = stan_data["s_m"].to_a
|
130
|
-
stan_data["X"] = stan_data["X"].
|
130
|
+
stan_data["X"] = stan_data["X"].to_numo.to_a
|
131
131
|
stan_init["delta"] = stan_init["delta"].to_a
|
132
132
|
stan_init["beta"] = stan_init["beta"].to_a
|
133
133
|
[stan_init, stan_data]
|
data/lib/prophet/version.rb
CHANGED
data/stan/unix/prophet.stan
CHANGED
@@ -73,6 +73,15 @@ functions {
|
|
73
73
|
) {
|
74
74
|
return (k + A * delta) .* t + (m + A * (-t_change .* delta));
|
75
75
|
}
|
76
|
+
|
77
|
+
// Flat trend function
|
78
|
+
|
79
|
+
vector flat_trend(
|
80
|
+
real m,
|
81
|
+
int T
|
82
|
+
) {
|
83
|
+
return rep_vector(m, T);
|
84
|
+
}
|
76
85
|
}
|
77
86
|
|
78
87
|
data {
|
@@ -86,7 +95,7 @@ data {
|
|
86
95
|
matrix[T,K] X; // Regressors
|
87
96
|
vector[K] sigmas; // Scale on seasonality prior
|
88
97
|
real<lower=0> tau; // Scale on changepoints prior
|
89
|
-
int trend_indicator; // 0 for linear, 1 for logistic
|
98
|
+
int trend_indicator; // 0 for linear, 1 for logistic, 2 for flat
|
90
99
|
vector[K] s_a; // Indicator of additive features
|
91
100
|
vector[K] s_m; // Indicator of multiplicative features
|
92
101
|
}
|
@@ -104,6 +113,17 @@ parameters {
|
|
104
113
|
vector[K] beta; // Regressor coefficients
|
105
114
|
}
|
106
115
|
|
116
|
+
transformed parameters {
|
117
|
+
vector[T] trend;
|
118
|
+
if (trend_indicator == 0) {
|
119
|
+
trend = linear_trend(k, m, delta, t, A, t_change);
|
120
|
+
} else if (trend_indicator == 1) {
|
121
|
+
trend = logistic_trend(k, m, delta, t, cap, A, t_change, S);
|
122
|
+
} else if (trend_indicator == 2) {
|
123
|
+
trend = flat_trend(m, T);
|
124
|
+
}
|
125
|
+
}
|
126
|
+
|
107
127
|
model {
|
108
128
|
//priors
|
109
129
|
k ~ normal(0, 5);
|
@@ -113,19 +133,10 @@ model {
|
|
113
133
|
beta ~ normal(0, sigmas);
|
114
134
|
|
115
135
|
// Likelihood
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
);
|
123
|
-
} else if (trend_indicator == 1) {
|
124
|
-
y ~ normal(
|
125
|
-
logistic_trend(k, m, delta, t, cap, A, t_change, S)
|
126
|
-
.* (1 + X * (beta .* s_m))
|
127
|
-
+ X * (beta .* s_a),
|
128
|
-
sigma_obs
|
129
|
-
);
|
130
|
-
}
|
136
|
+
y ~ normal(
|
137
|
+
trend
|
138
|
+
.* (1 + X * (beta .* s_m))
|
139
|
+
+ X * (beta .* s_a),
|
140
|
+
sigma_obs
|
141
|
+
);
|
131
142
|
}
|
data/stan/win/prophet.stan
CHANGED
@@ -47,7 +47,7 @@ functions {
|
|
47
47
|
}
|
48
48
|
return gamma;
|
49
49
|
}
|
50
|
-
|
50
|
+
|
51
51
|
real[] logistic_trend(
|
52
52
|
real k,
|
53
53
|
real m,
|
@@ -94,6 +94,17 @@ functions {
|
|
94
94
|
}
|
95
95
|
return Y;
|
96
96
|
}
|
97
|
+
|
98
|
+
// Flat trend function
|
99
|
+
|
100
|
+
real[] flat_trend(
|
101
|
+
real m,
|
102
|
+
int T
|
103
|
+
) {
|
104
|
+
return rep_array(m, T);
|
105
|
+
}
|
106
|
+
|
107
|
+
|
97
108
|
}
|
98
109
|
|
99
110
|
data {
|
@@ -107,7 +118,7 @@ data {
|
|
107
118
|
real X[T,K]; // Regressors
|
108
119
|
vector[K] sigmas; // Scale on seasonality prior
|
109
120
|
real<lower=0> tau; // Scale on changepoints prior
|
110
|
-
int trend_indicator; // 0 for linear, 1 for logistic
|
121
|
+
int trend_indicator; // 0 for linear, 1 for logistic, 2 for flat
|
111
122
|
real s_a[K]; // Indicator of additive features
|
112
123
|
real s_m[K]; // Indicator of multiplicative features
|
113
124
|
}
|
@@ -135,6 +146,8 @@ transformed parameters {
|
|
135
146
|
trend = linear_trend(k, m, delta, t, A, t_change, S, T);
|
136
147
|
} else if (trend_indicator == 1) {
|
137
148
|
trend = logistic_trend(k, m, delta, t, cap, A, t_change, S, T);
|
149
|
+
} else if (trend_indicator == 2){
|
150
|
+
trend = flat_trend(m, T);
|
138
151
|
}
|
139
152
|
|
140
153
|
for (i in 1:K) {
|
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.
|
4
|
+
version: 0.2.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrew Kane
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-04-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: cmdstan
|
@@ -24,106 +24,36 @@ dependencies:
|
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: 0.1.2
|
27
|
-
- !ruby/object:Gem::Dependency
|
28
|
-
name: daru
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
30
|
-
requirements:
|
31
|
-
- - ">="
|
32
|
-
- !ruby/object:Gem::Version
|
33
|
-
version: '0'
|
34
|
-
type: :runtime
|
35
|
-
prerelease: false
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
37
|
-
requirements:
|
38
|
-
- - ">="
|
39
|
-
- !ruby/object:Gem::Version
|
40
|
-
version: '0'
|
41
27
|
- !ruby/object:Gem::Dependency
|
42
28
|
name: numo-narray
|
43
29
|
requirement: !ruby/object:Gem::Requirement
|
44
30
|
requirements:
|
45
31
|
- - ">="
|
46
32
|
- !ruby/object:Gem::Version
|
47
|
-
version:
|
33
|
+
version: 0.9.1.7
|
48
34
|
type: :runtime
|
49
35
|
prerelease: false
|
50
36
|
version_requirements: !ruby/object:Gem::Requirement
|
51
37
|
requirements:
|
52
38
|
- - ">="
|
53
39
|
- !ruby/object:Gem::Version
|
54
|
-
version:
|
55
|
-
- !ruby/object:Gem::Dependency
|
56
|
-
name: bundler
|
57
|
-
requirement: !ruby/object:Gem::Requirement
|
58
|
-
requirements:
|
59
|
-
- - ">="
|
60
|
-
- !ruby/object:Gem::Version
|
61
|
-
version: '0'
|
62
|
-
type: :development
|
63
|
-
prerelease: false
|
64
|
-
version_requirements: !ruby/object:Gem::Requirement
|
65
|
-
requirements:
|
66
|
-
- - ">="
|
67
|
-
- !ruby/object:Gem::Version
|
68
|
-
version: '0'
|
69
|
-
- !ruby/object:Gem::Dependency
|
70
|
-
name: rake
|
71
|
-
requirement: !ruby/object:Gem::Requirement
|
72
|
-
requirements:
|
73
|
-
- - ">="
|
74
|
-
- !ruby/object:Gem::Version
|
75
|
-
version: '0'
|
76
|
-
type: :development
|
77
|
-
prerelease: false
|
78
|
-
version_requirements: !ruby/object:Gem::Requirement
|
79
|
-
requirements:
|
80
|
-
- - ">="
|
81
|
-
- !ruby/object:Gem::Version
|
82
|
-
version: '0'
|
40
|
+
version: 0.9.1.7
|
83
41
|
- !ruby/object:Gem::Dependency
|
84
|
-
name:
|
42
|
+
name: rover-df
|
85
43
|
requirement: !ruby/object:Gem::Requirement
|
86
|
-
requirements:
|
87
|
-
- - ">="
|
88
|
-
- !ruby/object:Gem::Version
|
89
|
-
version: '5'
|
90
|
-
type: :development
|
91
|
-
prerelease: false
|
92
|
-
version_requirements: !ruby/object:Gem::Requirement
|
93
|
-
requirements:
|
94
|
-
- - ">="
|
95
|
-
- !ruby/object:Gem::Version
|
96
|
-
version: '5'
|
97
|
-
- !ruby/object:Gem::Dependency
|
98
|
-
name: matplotlib
|
99
|
-
requirement: !ruby/object:Gem::Requirement
|
100
|
-
requirements:
|
101
|
-
- - ">="
|
102
|
-
- !ruby/object:Gem::Version
|
103
|
-
version: '0'
|
104
|
-
type: :development
|
105
|
-
prerelease: false
|
106
|
-
version_requirements: !ruby/object:Gem::Requirement
|
107
44
|
requirements:
|
108
45
|
- - ">="
|
109
46
|
- !ruby/object:Gem::Version
|
110
47
|
version: '0'
|
111
|
-
|
112
|
-
name: ruby-prof
|
113
|
-
requirement: !ruby/object:Gem::Requirement
|
114
|
-
requirements:
|
115
|
-
- - ">="
|
116
|
-
- !ruby/object:Gem::Version
|
117
|
-
version: '0'
|
118
|
-
type: :development
|
48
|
+
type: :runtime
|
119
49
|
prerelease: false
|
120
50
|
version_requirements: !ruby/object:Gem::Requirement
|
121
51
|
requirements:
|
122
52
|
- - ">="
|
123
53
|
- !ruby/object:Gem::Version
|
124
54
|
version: '0'
|
125
|
-
description:
|
126
|
-
email: andrew@
|
55
|
+
description:
|
56
|
+
email: andrew@ankane.org
|
127
57
|
executables: []
|
128
58
|
extensions:
|
129
59
|
- ext/prophet/extconf.rb
|
@@ -148,7 +78,7 @@ homepage: https://github.com/ankane/prophet
|
|
148
78
|
licenses:
|
149
79
|
- MIT
|
150
80
|
metadata: {}
|
151
|
-
post_install_message:
|
81
|
+
post_install_message:
|
152
82
|
rdoc_options: []
|
153
83
|
require_paths:
|
154
84
|
- lib
|
@@ -163,8 +93,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
163
93
|
- !ruby/object:Gem::Version
|
164
94
|
version: '0'
|
165
95
|
requirements: []
|
166
|
-
rubygems_version: 3.
|
167
|
-
signing_key:
|
96
|
+
rubygems_version: 3.2.3
|
97
|
+
signing_key:
|
168
98
|
specification_version: 4
|
169
99
|
summary: Time series forecasting for Ruby
|
170
100
|
test_files: []
|