Parcourir la source

Allow changepoints on dates that aren't in history, and allow for repeated observations on days. Previously we worked with changepoints via their index in the history. Now we work with them using just their value in scaled time.

Ben Letham il y a 8 ans
Parent
commit
443d475468

+ 20 - 44
R/R/prophet.R

@@ -101,8 +101,9 @@ prophet <- function(df = df,
     interval.width = interval.width,
     interval.width = interval.width,
     uncertainty.samples = uncertainty.samples,
     uncertainty.samples = uncertainty.samples,
     start = NULL,  # This and following attributes are set during fitting
     start = NULL,  # This and following attributes are set during fitting
-    end = NULL,
     y.scale = NULL,
     y.scale = NULL,
+    t.scale = NULL,
+    changepoints.t = NULL,
     stan.fit = NULL,
     stan.fit = NULL,
     params = list(),
     params = list(),
     history = NULL
     history = NULL
@@ -206,12 +207,10 @@ setup_dataframe <- function(m, df, initialize_scales = FALSE) {
   if (initialize_scales) {
   if (initialize_scales) {
     m$y.scale <- max(df$y)
     m$y.scale <- max(df$y)
     m$start <- min(df$ds)
     m$start <- min(df$ds)
-    m$end <- max(df$ds)
+    m$t.scale <- as.numeric(max(df$ds) - m$start)
   }
   }
 
 
-  t.scale <- as.numeric(m$end - m$start)
-
-  df$t <- as.numeric(df$ds - m$start) / t.scale
+  df$t <- as.numeric(df$ds - m$start) / m$t.scale
   if (exists('y', where=df)) {
   if (exists('y', where=df)) {
     df$y_scaled <- df$y / m$y.scale
     df$y_scaled <- df$y / m$y.scale
   }
   }
@@ -254,32 +253,13 @@ set_changepoints <- function(m) {
       m$changepoints <- c()
       m$changepoints <- c()
     }
     }
   }
   }
-  return(m)
-}
-
-#' Gets changepoint indexes in history dataframe.
-#'
-#' @param m Prophet object.
-#'
-#' @return array of indexes.
-#'
-get_changepoint_indexes <- function(m) {
-  if (length(m$changepoints) == 0) {
-    return(c(1))
+  if (length(m$changepoints) > 0) {
+    m$changepoints <- zoo::as.Date(m$changepoints)
+    m$changepoints.t <- sort(as.numeric(m$changepoints - m$start) / m$t.scale)
   } else {
   } else {
-    return(match(zoo::as.Date(m$changepoints), m$history$ds))
+    m$changepoints.t <- c(0)  # dummy changepoint
   }
   }
-}
-
-#' Gets changepoint times, in scaled space.
-#'
-#' @param m Prophet object.
-#'
-#' @return array of times.
-#'
-get_changepoint_times <- function(m) {
-  cpi <- get_changepoint_indexes(m)
-  return(m$history$t[cpi])
+  return(m)
 }
 }
 
 
 #' Gets changepoint matrix for history dataframe.
 #' Gets changepoint matrix for history dataframe.
@@ -289,11 +269,9 @@ get_changepoint_times <- function(m) {
 #' @return array of indexes.
 #' @return array of indexes.
 #'
 #'
 get_changepoint_matrix <- function(m) {
 get_changepoint_matrix <- function(m) {
-  changepoint.indexes <- get_changepoint_indexes(m)
-
-  A <- matrix(0, nrow(m$history), length(changepoint.indexes))
-  for (i in 1:length(changepoint.indexes)) {
-    A[changepoint.indexes[i]:nrow(m$history), i] <- 1
+  A <- matrix(0, nrow(m$history), length(m$changepoints.t))
+  for (i in 1:length(m$changepoints.t)) {
+    A[m$history$t >= m$changepoints.t[i], i] <- 1
   }
   }
   return(A)
   return(A)
 }
 }
@@ -470,17 +448,16 @@ fit.prophet <- function(m, df, ...) {
 
 
   m <- set_changepoints(m)
   m <- set_changepoints(m)
   A <- get_changepoint_matrix(m)
   A <- get_changepoint_matrix(m)
-  changepoint.indexes <- get_changepoint_indexes(m)
 
 
   # Construct input to stan
   # Construct input to stan
   dat <- list(
   dat <- list(
     T = nrow(history),
     T = nrow(history),
     K = ncol(seasonal.features),
     K = ncol(seasonal.features),
-    S = length(changepoint.indexes),
+    S = length(m$changepoints.t),
     y = history$y_scaled,
     y = history$y_scaled,
     t = history$t,
     t = history$t,
     A = A,
     A = A,
-    s_indx = array(changepoint.indexes),
+    t_change = array(m$changepoints.t),
     X = as.matrix(seasonal.features),
     X = as.matrix(seasonal.features),
     sigma = m$seasonality.prior.scale,
     sigma = m$seasonality.prior.scale,
     tau = m$changepoint.prior.scale
     tau = m$changepoint.prior.scale
@@ -499,7 +476,7 @@ fit.prophet <- function(m, df, ...) {
   stan_init <- function() {
   stan_init <- function() {
     list(k = kinit[1],
     list(k = kinit[1],
          m = kinit[2],
          m = kinit[2],
-         delta = array(rep(0, length(changepoint.indexes))),
+         delta = array(rep(0, length(m$changepoints.t))),
          beta = array(rep(0, ncol(seasonal.features))),
          beta = array(rep(0, ncol(seasonal.features))),
          sigma_obs = 1
          sigma_obs = 1
     )
     )
@@ -649,12 +626,12 @@ predict_trend <- function(model, df) {
   deltas <- colMeans(model$params$delta, na.rm = TRUE)
   deltas <- colMeans(model$params$delta, na.rm = TRUE)
 
 
   t <- df$t
   t <- df$t
-  cpts <- get_changepoint_times(model)
   if (model$growth == 'linear') {
   if (model$growth == 'linear') {
-    trend <- piecewise_linear(t, deltas, k, param.m, cpts)
+    trend <- piecewise_linear(t, deltas, k, param.m, model$changepoints.t)
   } else {
   } else {
     cap <- df$cap_scaled
     cap <- df$cap_scaled
-    trend <- piecewise_logistic(t, cap, deltas, k, param.m, cpts)
+    trend <- piecewise_logistic(
+      t, cap, deltas, k, param.m, model$changepoints.t)
   }
   }
   return(trend * model$y.scale)
   return(trend * model$y.scale)
 }
 }
@@ -785,7 +762,6 @@ sample_predictive_trend <- function(model, df, iteration) {
   deltas <- model$params$delta[iteration,]
   deltas <- model$params$delta[iteration,]
 
 
   t <- df$t
   t <- df$t
-  changepoint.ts <- get_changepoint_times(model)
   T <- max(t)
   T <- max(t)
 
 
   if (T > 1) {
   if (T > 1) {
@@ -794,7 +770,7 @@ sample_predictive_trend <- function(model, df, iteration) {
     dt <- min(dt[dt > 0])
     dt <- min(dt[dt > 0])
     # Number of time periods in the future
     # Number of time periods in the future
     N <- ceiling((T - 1) / dt)
     N <- ceiling((T - 1) / dt)
-    S <- length(changepoint.ts)
+    S <- length(model$changepoints.t)
     # The history had S split points, over t = [0, 1].
     # The history had S split points, over t = [0, 1].
     # The forecast is on [1, T], and should have the same average frequency of
     # The forecast is on [1, T], and should have the same average frequency of
     # rate changes. Thus for N time periods in the future, we want an average
     # rate changes. Thus for N time periods in the future, we want an average
@@ -820,7 +796,7 @@ sample_predictive_trend <- function(model, df, iteration) {
   deltas.new <- extraDistr::rlaplace(n.changes, mu = 0, sigma = lambda)
   deltas.new <- extraDistr::rlaplace(n.changes, mu = 0, sigma = lambda)
 
 
   # Combine with changepoints from the history
   # Combine with changepoints from the history
-  changepoint.ts <- c(changepoint.ts, changepoint.ts.new)
+  changepoint.ts <- c(model$changepoints.t, changepoint.ts.new)
   deltas <- c(deltas, deltas.new)
   deltas <- c(deltas, deltas.new)
 
 
   # Get the corresponding trend
   # Get the corresponding trend

+ 3 - 3
R/inst/stan/prophet_linear_growth.stan

@@ -3,9 +3,9 @@ data {
   int<lower=1> K;                       // Number of seasonal vectors
   int<lower=1> K;                       // Number of seasonal vectors
   vector[T] t;                            // Day
   vector[T] t;                            // Day
   vector[T] y;                            // Time-series
   vector[T] y;                            // Time-series
-  int S;                                // Number of split points
+  int S;                                // Number of changepoints
   matrix[T, S] A;                   // Split indicators
   matrix[T, S] A;                   // Split indicators
-  int s_indx[S];                 // Index of split points
+  real t_change[S];                 // Index of changepoints
   matrix[T,K] X;                // season vectors
   matrix[T,K] X;                // season vectors
   real<lower=0> sigma;              // scale on seasonality prior
   real<lower=0> sigma;              // scale on seasonality prior
   real<lower=0> tau;                  // scale on changepoints prior
   real<lower=0> tau;                  // scale on changepoints prior
@@ -23,7 +23,7 @@ transformed parameters {
   vector[S] gamma;                  // adjusted offsets, for piecewise continuity
   vector[S] gamma;                  // adjusted offsets, for piecewise continuity
 
 
   for (i in 1:S) {
   for (i in 1:S) {
-    gamma[i] = -t[s_indx[i]] * delta[i];
+    gamma[i] = -t_change[i] * delta[i];
   }
   }
 }
 }
 
 

+ 3 - 14
R/inst/stan/prophet_logistic_growth.stan

@@ -4,24 +4,14 @@ data {
   vector[T] t;                            // Day
   vector[T] t;                            // Day
   vector[T] cap;                          // Capacities
   vector[T] cap;                          // Capacities
   vector[T] y;                            // Time-series
   vector[T] y;                            // Time-series
-  int S;                                // Number of split points
+  int S;                                // Number of changepoints
   matrix[T, S] A;                   // Split indicators
   matrix[T, S] A;                   // Split indicators
-  int s_indx[S];                 // Index of split points
+  real t_change[S];                 // Index of changepoints
   matrix[T,K] X;                    // season vectors
   matrix[T,K] X;                    // season vectors
   real<lower=0> sigma;              // scale on seasonality prior
   real<lower=0> sigma;              // scale on seasonality prior
   real<lower=0> tau;                  // scale on changepoints prior
   real<lower=0> tau;                  // scale on changepoints prior
 }
 }
 
 
-
-transformed data {
-  int s_ext[S + 1];  // Segment endpoints
-  for (j in 1:S) {
-    s_ext[j] = s_indx[j];
-  }
-  s_ext[S + 1] = T + 1;  // Used for the m_adj loop below.
-}
-
-
 parameters {
 parameters {
   real k;                            // Base growth rate
   real k;                            // Base growth rate
   real m;                            // offset
   real m;                            // offset
@@ -30,7 +20,6 @@ parameters {
   vector[K] beta;                    // seasonal vector
   vector[K] beta;                    // seasonal vector
 }
 }
 
 
-
 transformed parameters {
 transformed parameters {
   vector[S] gamma;                  // adjusted offsets, for piecewise continuity
   vector[S] gamma;                  // adjusted offsets, for piecewise continuity
   vector[S + 1] k_s;                 // actual rate in each segment
   vector[S + 1] k_s;                 // actual rate in each segment
@@ -45,7 +34,7 @@ transformed parameters {
   // Piecewise offsets
   // Piecewise offsets
   m_pr = m; // The offset in the previous segment
   m_pr = m; // The offset in the previous segment
   for (i in 1:S) {
   for (i in 1:S) {
-    gamma[i] = (t[s_indx[i]] - m_pr) * (1 - k_s[i] / k_s[i + 1]);
+    gamma[i] = (t_change[i] - m_pr) * (1 - k_s[i] / k_s[i + 1]);
     m_pr = m_pr + gamma[i];  // update for the next segment
     m_pr = m_pr + gamma[i];  // update for the next segment
   }
   }
 }
 }

+ 0 - 18
R/man/get_changepoint_indexes.Rd

@@ -1,18 +0,0 @@
-% Generated by roxygen2: do not edit by hand
-% Please edit documentation in R/prophet.R
-\name{get_changepoint_indexes}
-\alias{get_changepoint_indexes}
-\title{Gets changepoint indexes in history dataframe.}
-\usage{
-get_changepoint_indexes(m)
-}
-\arguments{
-\item{m}{Prophet object.}
-}
-\value{
-array of indexes.
-}
-\description{
-Gets changepoint indexes in history dataframe.
-}
-

+ 0 - 18
R/man/get_changepoint_times.Rd

@@ -1,18 +0,0 @@
-% Generated by roxygen2: do not edit by hand
-% Please edit documentation in R/prophet.R
-\name{get_changepoint_times}
-\alias{get_changepoint_times}
-\title{Gets changepoint times, in scaled space.}
-\usage{
-get_changepoint_times(m)
-}
-\arguments{
-\item{m}{Prophet object.}
-}
-\value{
-array of times.
-}
-\description{
-Gets changepoint times, in scaled space.
-}
-

+ 22 - 3
R/tests/testthat/test_prophet.R

@@ -30,6 +30,25 @@ test_that("fit_predict_no_changepoints", {
   expect_error(predict(m, future), NA)
   expect_error(predict(m, future), NA)
 })
 })
 
 
+test_that("fit_predict_changepoint_not_in_history", {
+  skip_if_not(Sys.getenv('R_ARCH') != '/i386')
+  train_t <- dplyr::mutate(DATA, ds=zoo::as.Date(ds))
+  train_t <- dplyr::filter(train_t, (ds < zoo::as.Date('2013-01-01')) | 
+                                (ds > zoo::as.Date('2014-01-01')))
+  future <- data.frame(ds=DATA$ds)
+  m <- prophet(train_t, changepoints=c('2013-06-06'))
+  expect_error(predict(m, future), NA)
+})
+
+test_that("fit_predict_duplicates", {
+  skip_if_not(Sys.getenv('R_ARCH') != '/i386')
+  train2 <- train
+  train2$y <- train2$y + 10
+  train_t <- rbind(train, train2)
+  m <- prophet(train_t)
+  expect_error(predict(m, future), NA)
+})
+
 test_that("setup_dataframe", {
 test_that("setup_dataframe", {
   history <- train
   history <- train
   m <- prophet(history, fit = FALSE)
   m <- prophet(history, fit = FALSE)
@@ -56,7 +75,7 @@ test_that("get_changepoints", {
 
 
   m <- prophet:::set_changepoints(m)
   m <- prophet:::set_changepoints(m)
 
 
-  cp <- prophet:::get_changepoint_indexes(m)
+  cp <- m$changepoints.t
   expect_equal(length(cp), m$n.changepoints)
   expect_equal(length(cp), m$n.changepoints)
   expect_true(min(cp) > 0)
   expect_true(min(cp) > 0)
   expect_true(max(cp) < N)
   expect_true(max(cp) < N)
@@ -76,9 +95,9 @@ test_that("get_zero_changepoints", {
   m$history <- history
   m$history <- history
 
 
   m <- prophet:::set_changepoints(m)
   m <- prophet:::set_changepoints(m)
-  cp <- prophet:::get_changepoint_indexes(m)
+  cp <- m$changepoints.t
   expect_equal(length(cp), 1)
   expect_equal(length(cp), 1)
-  expect_equal(cp[1], 1)
+  expect_equal(cp[1], 0)
 
 
   mat <- prophet:::get_changepoint_matrix(m)
   mat <- prophet:::get_changepoint_matrix(m)
   expect_equal(nrow(mat), floor(N / 2))
   expect_equal(nrow(mat), floor(N / 2))

+ 24 - 40
python/fbprophet/forecaster.py

@@ -84,8 +84,9 @@ class Prophet(object):
 
 
         # Set during fitting
         # Set during fitting
         self.start = None
         self.start = None
-        self.end = None
         self.y_scale = None
         self.y_scale = None
+        self.t_scale = None
+        self.changepoints_t = None
         self.stan_fit = None
         self.stan_fit = None
         self.params = {}
         self.params = {}
         self.history = None
         self.history = None
@@ -124,14 +125,14 @@ class Prophet(object):
         df['ds'] = pd.to_datetime(df['ds'])
         df['ds'] = pd.to_datetime(df['ds'])
 
 
         df = df.sort_values('ds')
         df = df.sort_values('ds')
+        df.reset_index(inplace=True, drop=True)
 
 
         if initialize_scales:
         if initialize_scales:
             self.y_scale = df['y'].max()
             self.y_scale = df['y'].max()
-            self.start, self.end = df['ds'].min(), df['ds'].max()
+            self.start = df['ds'].min()
+            self.t_scale = df['ds'].max() - self.start
 
 
-        t_scale = self.end - self.start
-
-        df['t'] = (df['ds'] - self.start) / t_scale
+        df['t'] = (df['ds'] - self.start) / self.t_scale
         if 'y' in df:
         if 'y' in df:
             df['y_scaled'] = df['y'] / self.y_scale
             df['y_scaled'] = df['y'] / self.y_scale
 
 
@@ -171,31 +172,17 @@ class Prophet(object):
         else:
         else:
             # set empty changepoints
             # set empty changepoints
             self.changepoints = []
             self.changepoints = []
-
-    def get_changepoint_indexes(self):
-        if len(self.changepoints) == 0:
-            return np.array([0])  # a dummy changepoint
+        if len(self.changepoints) > 0:
+            self.changepoints_t = np.sort(np.array(
+                (self.changepoints - self.start) / self.t_scale))
         else:
         else:
-            row_index = pd.DatetimeIndex(self.history['ds'])
-            indexes = []
-            for cp in self.changepoints:
-                # In the future this may raise a KeyError, but for now we
-                # should guarantee that all changepoint dates are included in
-                # the historical data.
-                indexes.append(row_index.get_loc(cp))
-            return np.array(indexes).astype(np.int)
-
-    def get_changepoint_times(self):
-        cpi = self.get_changepoint_indexes()
-        return np.array(self.history['t'].iloc[cpi])
+            self.changepoints_t = np.array([0])  # dummy changepoint
 
 
-    def get_changepoint_matrix(self):
-        changepoint_indexes = self.get_changepoint_indexes()
-
-        A = np.zeros((self.history.shape[0], len(changepoint_indexes)))
-        for i, index in enumerate(changepoint_indexes):
-            A[index:self.history.shape[0], i] = 1
 
 
+    def get_changepoint_matrix(self):
+        A = np.zeros((self.history.shape[0], len(self.changepoints_t)))
+        for i, t_i in enumerate(self.changepoints_t):
+            A[self.history['t'].values >= t_i, i] = 1
         return A
         return A
 
 
     @staticmethod
     @staticmethod
@@ -345,7 +332,6 @@ class Prophet(object):
         The fitted Prophet object.
         The fitted Prophet object.
         """
         """
         history = df[df['y'].notnull()].copy()
         history = df[df['y'].notnull()].copy()
-        history.reset_index(inplace=True, drop=True)
 
 
         history = self.setup_dataframe(history, initialize_scales=True)
         history = self.setup_dataframe(history, initialize_scales=True)
         self.history = history
         self.history = history
@@ -353,17 +339,15 @@ class Prophet(object):
 
 
         self.set_changepoints()
         self.set_changepoints()
         A = self.get_changepoint_matrix()
         A = self.get_changepoint_matrix()
-        changepoint_indexes = self.get_changepoint_indexes()
 
 
         dat = {
         dat = {
             'T': history.shape[0],
             'T': history.shape[0],
             'K': seasonal_features.shape[1],
             'K': seasonal_features.shape[1],
-            'S': len(changepoint_indexes),
+            'S': len(self.changepoints_t),
             'y': history['y_scaled'],
             'y': history['y_scaled'],
             't': history['t'],
             't': history['t'],
             'A': A,
             'A': A,
-            # Need to add one because Stan is 1-indexed.
-            's_indx': changepoint_indexes + 1,
+            't_change': self.changepoints_t,
             'X': seasonal_features,
             'X': seasonal_features,
             'sigma': self.seasonality_prior_scale,
             'sigma': self.seasonality_prior_scale,
             'tau': self.changepoint_prior_scale,
             'tau': self.changepoint_prior_scale,
@@ -381,7 +365,7 @@ class Prophet(object):
             return {
             return {
                 'k': kinit[0],
                 'k': kinit[0],
                 'm': kinit[1],
                 'm': kinit[1],
-                'delta': np.zeros(len(changepoint_indexes)),
+                'delta': np.zeros(len(self.changepoints_t)),
                 'beta': np.zeros(seasonal_features.shape[1]),
                 'beta': np.zeros(seasonal_features.shape[1]),
                 'sigma_obs': 1,
                 'sigma_obs': 1,
             }
             }
@@ -419,7 +403,7 @@ class Prophet(object):
         `df` can be None, in which case we predict only on history.
         `df` can be None, in which case we predict only on history.
         """
         """
         if df is None:
         if df is None:
-            df = self.history
+            df = self.history.copy()
         else:
         else:
             df = self.setup_dataframe(df)
             df = self.setup_dataframe(df)
 
 
@@ -469,12 +453,12 @@ class Prophet(object):
         deltas = np.nanmean(self.params['delta'], axis=0)
         deltas = np.nanmean(self.params['delta'], axis=0)
 
 
         t = np.array(df['t'])
         t = np.array(df['t'])
-        cpts = self.get_changepoint_times()
         if self.growth == 'linear':
         if self.growth == 'linear':
-            trend = self.piecewise_linear(t, deltas, k, m, cpts)
+            trend = self.piecewise_linear(t, deltas, k, m, self.changepoints_t)
         else:
         else:
             cap = df['cap_scaled']
             cap = df['cap_scaled']
-            trend = self.piecewise_logistic(t, cap, deltas, k, m, cpts)
+            trend = self.piecewise_logistic(
+                t, cap, deltas, k, m, self.changepoints_t)
 
 
         return trend * self.y_scale
         return trend * self.y_scale
 
 
@@ -565,7 +549,6 @@ class Prophet(object):
         deltas = self.params['delta'][iteration]
         deltas = self.params['delta'][iteration]
 
 
         t = np.array(df['t'])
         t = np.array(df['t'])
-        changepoint_ts = self.get_changepoint_times()
         T = t.max()
         T = t.max()
 
 
         if T > 1:
         if T > 1:
@@ -574,7 +557,7 @@ class Prophet(object):
             dt = np.min(dt[dt > 0])
             dt = np.min(dt[dt > 0])
             # Number of time periods in the future
             # Number of time periods in the future
             N = np.ceil((T - 1) / float(dt))
             N = np.ceil((T - 1) / float(dt))
-            S = len(changepoint_ts)
+            S = len(self.changepoints_t)
 
 
             prob_change = min(1, (S * (T - 1)) / N)
             prob_change = min(1, (S * (T - 1)) / N)
             n_changes = np.random.binomial(N, prob_change)
             n_changes = np.random.binomial(N, prob_change)
@@ -593,7 +576,8 @@ class Prophet(object):
         deltas_new = np.random.laplace(0, lambda_, n_changes)
         deltas_new = np.random.laplace(0, lambda_, n_changes)
 
 
         # Prepend the times and deltas from the history
         # Prepend the times and deltas from the history
-        changepoint_ts = np.concatenate((changepoint_ts, changepoint_ts_new))
+        changepoint_ts = np.concatenate((self.changepoints_t,
+                                         changepoint_ts_new))
         deltas = np.concatenate((deltas, deltas_new))
         deltas = np.concatenate((deltas, deltas_new))
 
 
         if self.growth == 'linear':
         if self.growth == 'linear':

+ 21 - 2
python/fbprophet/tests/test_prophet.py

@@ -57,6 +57,25 @@ class TestProphet(TestCase):
         forecaster.fit(train)
         forecaster.fit(train)
         forecaster.predict(future)
         forecaster.predict(future)
 
 
+    def test_fit_changepoint_not_in_history(self):
+        train = DATA[(DATA['ds'] < '2013-01-01') | (DATA['ds'] > '2014-01-01')]
+        train[(train['ds'] > '2014-01-01')] += 20
+        future = pd.DataFrame({'ds': DATA['ds']})
+        forecaster = Prophet(changepoints=['2013-06-06'])
+        forecaster.fit(train)
+        forecaster.predict(future)
+
+    def test_fit_predict_duplicates(self):
+        N = DATA.shape[0]
+        train1 = DATA.head(N // 2).copy()
+        train2 = DATA.head(N // 2).copy()
+        train2['y'] += 10
+        train = train1.append(train2)
+        future = pd.DataFrame({'ds': DATA['ds'].tail(N // 2)})
+        forecaster = Prophet()
+        forecaster.fit(train)
+        forecaster.predict(future)
+
     def test_setup_dataframe(self):
     def test_setup_dataframe(self):
         m = Prophet()
         m = Prophet()
         N = DATA.shape[0]
         N = DATA.shape[0]
@@ -81,7 +100,7 @@ class TestProphet(TestCase):
 
 
         m.set_changepoints()
         m.set_changepoints()
 
 
-        cp = m.get_changepoint_indexes()
+        cp = m.changepoints_t
         self.assertEqual(cp.shape[0], m.n_changepoints)
         self.assertEqual(cp.shape[0], m.n_changepoints)
         self.assertEqual(len(cp.shape), 1)
         self.assertEqual(len(cp.shape), 1)
         self.assertTrue(cp.min() > 0)
         self.assertTrue(cp.min() > 0)
@@ -100,7 +119,7 @@ class TestProphet(TestCase):
         m.history = history
         m.history = history
 
 
         m.set_changepoints()
         m.set_changepoints()
-        cp = m.get_changepoint_indexes()
+        cp = m.changepoints_t
         self.assertEqual(cp.shape[0], 1)
         self.assertEqual(cp.shape[0], 1)
         self.assertEqual(cp[0], 0)
         self.assertEqual(cp[0], 0)
 
 

+ 3 - 10
python/stan/prophet_linear_growth.stan

@@ -1,18 +1,11 @@
-# Copyright (c) 2017-present, Facebook, Inc.
-# All rights reserved.
-#
-# This source code is licensed under the BSD-style license found in the
-# LICENSE file in the root directory of this source tree. An additional grant 
-# of patent rights can be found in the PATENTS file in the same directory.
-
 data {
 data {
   int T;                                // Sample size
   int T;                                // Sample size
   int<lower=1> K;                       // Number of seasonal vectors
   int<lower=1> K;                       // Number of seasonal vectors
   vector[T] t;                            // Day
   vector[T] t;                            // Day
   vector[T] y;                            // Time-series
   vector[T] y;                            // Time-series
-  int S;                                // Number of split points
+  int S;                                // Number of changepoints
   matrix[T, S] A;                   // Split indicators
   matrix[T, S] A;                   // Split indicators
-  int s_indx[S];                 // Index of split points
+  real t_change[S];                 // Index of changepoints
   matrix[T,K] X;                // season vectors
   matrix[T,K] X;                // season vectors
   real<lower=0> sigma;              // scale on seasonality prior
   real<lower=0> sigma;              // scale on seasonality prior
   real<lower=0> tau;                  // scale on changepoints prior
   real<lower=0> tau;                  // scale on changepoints prior
@@ -30,7 +23,7 @@ transformed parameters {
   vector[S] gamma;                  // adjusted offsets, for piecewise continuity
   vector[S] gamma;                  // adjusted offsets, for piecewise continuity
 
 
   for (i in 1:S) {
   for (i in 1:S) {
-    gamma[i] = -t[s_indx[i]] * delta[i];
+    gamma[i] = -t_change[i] * delta[i];
   }
   }
 }
 }
 
 

+ 3 - 21
python/stan/prophet_logistic_growth.stan

@@ -1,34 +1,17 @@
-# Copyright (c) 2017-present, Facebook, Inc.
-# All rights reserved.
-#
-# This source code is licensed under the BSD-style license found in the
-# LICENSE file in the root directory of this source tree. An additional grant 
-# of patent rights can be found in the PATENTS file in the same directory.
-
 data {
 data {
   int T;                                // Sample size
   int T;                                // Sample size
   int<lower=1> K;                       // Number of seasonal vectors
   int<lower=1> K;                       // Number of seasonal vectors
   vector[T] t;                            // Day
   vector[T] t;                            // Day
   vector[T] cap;                          // Capacities
   vector[T] cap;                          // Capacities
   vector[T] y;                            // Time-series
   vector[T] y;                            // Time-series
-  int S;                                // Number of split points
+  int S;                                // Number of changepoints
   matrix[T, S] A;                   // Split indicators
   matrix[T, S] A;                   // Split indicators
-  int s_indx[S];                 // Index of split points
+  real t_change[S];                 // Index of changepoints
   matrix[T,K] X;                    // season vectors
   matrix[T,K] X;                    // season vectors
   real<lower=0> sigma;              // scale on seasonality prior
   real<lower=0> sigma;              // scale on seasonality prior
   real<lower=0> tau;                  // scale on changepoints prior
   real<lower=0> tau;                  // scale on changepoints prior
 }
 }
 
 
-
-transformed data {
-  int s_ext[S + 1];  // Segment endpoints
-  for (j in 1:S) {
-    s_ext[j] = s_indx[j];
-  }
-  s_ext[S + 1] = T + 1;  // Used for the m_adj loop below.
-}
-
-
 parameters {
 parameters {
   real k;                            // Base growth rate
   real k;                            // Base growth rate
   real m;                            // offset
   real m;                            // offset
@@ -37,7 +20,6 @@ parameters {
   vector[K] beta;                    // seasonal vector
   vector[K] beta;                    // seasonal vector
 }
 }
 
 
-
 transformed parameters {
 transformed parameters {
   vector[S] gamma;                  // adjusted offsets, for piecewise continuity
   vector[S] gamma;                  // adjusted offsets, for piecewise continuity
   vector[S + 1] k_s;                 // actual rate in each segment
   vector[S + 1] k_s;                 // actual rate in each segment
@@ -52,7 +34,7 @@ transformed parameters {
   // Piecewise offsets
   // Piecewise offsets
   m_pr = m; // The offset in the previous segment
   m_pr = m; // The offset in the previous segment
   for (i in 1:S) {
   for (i in 1:S) {
-    gamma[i] = (t[s_indx[i]] - m_pr) * (1 - k_s[i] / k_s[i + 1]);
+    gamma[i] = (t_change[i] - m_pr) * (1 - k_s[i] / k_s[i + 1]);
     m_pr = m_pr + gamma[i];  // update for the next segment
     m_pr = m_pr + gamma[i];  // update for the next segment
   }
   }
 }
 }