Internal version of add(), with the same arguments.

        Runs during Model.build()

def _add(self, fixed=None, random=None, priors=None, family='gaussian',
             link=None, categorical=None, append=True):
        '''Internal version of add(), with the same arguments.

        Runs during Model.build()
        '''

        # use cleaned data with NAs removed (if user requested)
        data = self.clean_data
        # alter this pandas flag to avoid false positive SettingWithCopyWarnings
        data._is_copy = False

        # Explicitly convert columns to category if desired--though this
        # can also be done within the formula using C().
        if categorical is not None:
            data = data.copy()
            cats = listify(categorical)
            data[cats] = data[cats].apply(lambda x: x.astype('category'))

        if fixed is not None:
            if '~' in fixed:
                # check to see if formula is using the 'y[event] ~ x' syntax
                # (for bernoulli models). If so, chop it into groups:
                # 1 = 'y[event]', 2 = 'y', 3 = 'event', 4 = 'x'
                # If this syntax is not being used, event = None
                event = re.match(r'^((\S+)\[(\S+)\])\s*~(.*)$', fixed)
                if event is not None:
                    fixed = '{}~{}'.format(event.group(2), event.group(4))
                y, X = dmatrices(fixed, data=data, NA_action='raise')
                y_label = y.design_info.term_names[0]
                if event is not None:
                    # pass in new Y data that has 1 if y=event and 0 otherwise
                    y_data = y[:, y.design_info.column_names.index(event.group(1))]
                    y_data = pd.DataFrame({event.group(3): y_data})
                    self._add_y(y_label, family=family, link=link, data=y_data)
                else:
                    # use Y as-is
                    self._add_y(y_label, family=family, link=link)
            else:
                X = dmatrix(fixed, data=data, NA_action='raise')

            # Loop over predictor terms
            for _name, _slice in X.design_info.term_name_slices.items():
                cols = X.design_info.column_names[_slice]
                term_data = pd.DataFrame(X[:, _slice], columns=cols)
                prior = priors.pop(_name, priors.get('fixed', None))
                self.terms[_name] = Term(_name, term_data, prior=prior)

        # Random effects
        if random is not None:

            random = listify(random)

            for f in random:

                f = f.strip()

                # Split specification into intercept, predictor, and grouper
                patt = r'^([01]+)*[\s\+]*([^\|]+)*\|(.*)'

                intcpt, pred, grpr = re.search(patt, f).groups()
                label = '{}|{}'.format(pred, grpr) if pred else grpr
                prior = priors.pop(label, priors.get('random', None))

                # Treat all grouping variables as categoricals, regardless of
                # their dtype and what the user may have specified in the
                # 'categorical' argument.
                var_names = re.findall('(\w+)', grpr)
                for v in var_names:
                    if v in data.columns:
                        data.loc[:, v] = data.loc[:, v].astype('category')
                        self.clean_data.loc[:, v] = data.loc[:, v]

                # Default to including random intercepts
                intcpt = 1 if intcpt is None else int(intcpt)

                grpr_df = dmatrix('0+%s' % grpr, data, return_type='dataframe',
                                  NA_action='raise')

                # If there's no predictor, we must be adding random intercepts
                if not pred and grpr not in self.terms:
                    name = '1|' + grpr
                    pred = np.ones((len(grpr_df), 1))
                    term = RandomTerm(name, grpr_df, pred, grpr_df.values,
                                      categorical=True, prior=prior)
                    self.terms[name] = term
                else:
                    pred_df = dmatrix('%s+%s' % (intcpt, pred), data,
                                      return_type='dataframe',
                                      NA_action='raise')
                    # determine value of the 'constant' attribute
                    const = np.atleast_2d(pred_df.T).T.sum(1).var() == 0

                    for col, i in pred_df.design_info.column_name_indexes.items():
                        pred_data = pred_df.iloc[:, i]
                        lev_data = grpr_df.multiply(pred_data, axis=0)

                        # Also rename intercepts and skip if already added.
                        # This can happen if user specifies something like
                        # random=['1|school', 'student|school'].
                        if col == 'Intercept':
                            if grpr in self.terms:
                                continue
                            label = '1|%s' % grpr
                        else:
                            label = col + '|' + grpr

                        prior = priors.pop(label, priors.get('random', None))

                        # Categorical or continuous is determined from data
                        ld = lev_data.values
                        if ((ld == 0) | (ld == 1)).all():
                            lev_data = lev_data.astype(int)
                            cat = True
                        else:
                            cat = False

                        pred_data = pred_data[:, None]  # Must be 2D later
                        term = RandomTerm(label, lev_data, pred_data,
                                          grpr_df.values, categorical=cat,
                                          constant=const if const else None,
                                          prior=prior)
                        self.terms[label] = term