Set up the model for sampling/fitting.

        Performs any steps that require access to all model terms (e.g., scaling priors
        on each term), then calls the BackEnd's build() method.

        Args:
            backend (str): The name of the backend to use for model fitting.
                Currently, 'pymc' and 'stan' are supported. If None, assume
                that fit() has already been called (possibly without building),
                and look in self._backend_name.

def build(self, backend=None):
        '''Set up the model for sampling/fitting.

        Performs any steps that require access to all model terms (e.g., scaling priors
        on each term), then calls the BackEnd's build() method.

        Args:
            backend (str): The name of the backend to use for model fitting.
                Currently, 'pymc' and 'stan' are supported. If None, assume
                that fit() has already been called (possibly without building),
                and look in self._backend_name.
        '''

        # retain only the complete cases
        n_total = len(self.data.index)
        if len(self.completes):
            completes = [set(x) for x in sum(self.completes, [])]
            completes = set.intersection(*completes)
        else:
            completes = [x for x in range(len(self.data.index))]
        self.clean_data = self.data.iloc[list(completes), :]
        # warn the user about any dropped rows
        if len(completes) < n_total:
            msg = "Automatically removing {}/{} rows from the dataset."
            msg = msg.format(n_total - len(completes), n_total)
            warnings.warn(msg)

        # loop over the added terms and actually _add() them
        for term_args in self.added_terms:
            self._add(**term_args)

        # set custom priors
        self._set_priors(**self._added_priors)

        # prepare all priors
        for name, term in self.terms.items():
            type_ = 'intercept' if name == 'Intercept' else \
                'random' if self.terms[name].random else 'fixed'
            term.prior = self._prepare_prior(term.prior, type_)

        # check for backend
        if backend is None:
            if self._backend_name is None:
                raise ValueError("Error: no backend was passed or set in the "
                                 "Model; did you forget to call fit()?")
            backend = self._backend_name

        # check for outcome
        if self.y is None:
            raise ValueError("No outcome (y) variable is set! Please specify "
                             "an outcome variable using the formula interface "
                             "before build() or fit().")

        # X = fixed effects design matrix (excluding intercept/constant term)
        # r2_x = 1 - 1/VIF, i.e., R2 for predicting each x from all other x's.
        # only compute these stats if there are multiple terms in the model
        terms = [t for t in self.fixed_terms.values() if t.name != 'Intercept']

        if len(self.fixed_terms) > 1:

            X = [pd.DataFrame(x.data, columns=x.levels) for x in terms]
            X = pd.concat(X, axis=1)

            self.dm_statistics = {
                'r2_x': pd.Series({
                    x: sm.OLS(endog=X[x],
                              exog=sm.add_constant(X.drop(x, axis=1))
                              if 'Intercept' in self.term_names
                              else X.drop(x, axis=1)).fit().rsquared
                    for x in list(X.columns)}),
                'sd_x': X.std(),
                'mean_x': X.mean(axis=0)
            }

            # save potentially useful info for diagnostics, send to
            # ModelResults.
            # mat = correlation matrix of X, w/ diagonal replaced by X means
            mat = X.corr()
            for x in list(mat.columns):
                mat.loc[x, x] = self.dm_statistics['mean_x'][x]
            self._diagnostics = {
                # the Variance Inflation Factors (VIF), which is possibly
                # useful for diagnostics
                'VIF': 1/(1 - self.dm_statistics['r2_x']),
                'corr_mean_X': mat
            }

            # throw informative error if perfect collinearity among fixed fx
            if any(self.dm_statistics['r2_x'] > .999):
                raise ValueError(
                    "There is perfect collinearity among the fixed effects!\n"
                    "Printing some design matrix statistics:\n" +
                    str(self.dm_statistics) + '\n' +
                    str(self._diagnostics))

        # throw informative error message if any categorical predictors have 1
        # category
        num_cats = [x.data.size for x in self.fixed_terms.values()]
        if any(np.array(num_cats) == 0):
            raise ValueError(
                "At least one categorical predictor contains only 1 category!")

        # only set priors if there is at least one term in the model
        if len(self.terms) > 0:
            # Get and scale default priors if none are defined yet
            if self.taylor is not None:
                taylor = self.taylor
            else:
                taylor = 5 if self.family.name == 'gaussian' else 1
            scaler = PriorScaler(self, taylor=taylor)
            scaler.scale()

        # For bernoulli models with n_trials = 1 (most common use case),
        # tell user which event is being modeled
        if self.family.name == 'bernoulli' and np.max(self.y.data) < 1.01:
            event = next(
                i for i, x in enumerate(self.y.data.flatten()) if x > .99)
            warnings.warn('Modeling the probability that {}==\'{}\''.format(
                self.y.name, str(self.clean_data[self.y.name].iloc[event])))

        self._set_backend(backend)
        self.backend.build(self)
        self.built = True