Beispiel #1
0
    def build(self, spec, reset=True):
        '''
        Compile the PyMC3 model from an abstract model specification.
        Args:
            spec (Model): A bambi Model instance containing the abstract
                specification of the model to compile.
            reset (bool): if True (default), resets the PyMC3BackEnd instance
                before compiling.
        '''
        if reset:
            self.reset()

        with self.model:

            self.mu = 0.

            for t in spec.terms.values():

                data = t.data
                label = t.name
                dist_name = t.prior.name
                dist_args = t.prior.args

                # Effects w/ hyperparameters (i.e., random effects)
                if isinstance(data, dict):
                    for level, level_data in data.items():
                        n_cols = level_data.shape[1]
                        mu_label = 'u_%s_%s' % (label, level)
                        u = self._build_dist(mu_label,
                                             dist_name,
                                             shape=n_cols,
                                             **dist_args)
                        self.mu += pm.dot(level_data, u)[:, None]
                else:
                    prefix = 'u_' if t.random else 'b_'
                    n_cols = data.shape[1]
                    coef = self._build_dist(prefix + label,
                                            dist_name,
                                            shape=n_cols,
                                            **dist_args)
                    self.mu += pm.dot(data, coef)[:, None]

            y = spec.y.data
            y_prior = spec.family.prior
            link_f = spec.family.link
            if not callable(link_f):
                link_f = self.links[link_f]
            y_prior.args[spec.family.parent] = link_f(self.mu)
            y_prior.args['observed'] = y
            y_like = self._build_dist(spec.y.name, y_prior.name,
                                      **y_prior.args)

            self.spec = spec
Beispiel #2
0
    def build_model(self):
        wells = pm.get_data_file('pymc3.examples', 'data/wells.dat')
        data = pd.read_csv(wells, delimiter=u' ', index_col=u'id', dtype={u'switch': np.int8})
        data.dist /= 100
        data.educ /= 4
        col = data.columns
        P = data[col[1:]]
        P -= P.mean()
        P['1'] = 1

        with pm.Model() as model:
            effects = pm.Normal('effects', mu=0, tau=100. ** -2, shape=len(P.columns))
            p = pm.sigmoid(pm.dot(np.array(P), effects))
            pm.Bernoulli('s', p, observed=np.array(data.switch))
        return model
Beispiel #3
0
    def _setup_y(self, y_data, ar, by_run):
        from theano import shared
        from theano import tensor as T
        ''' Sets up y to be a theano shared variable. '''
        if 'y' not in self.shared_params:
            self.shared_params['y'] = shared(y_data)

            with self.model:

                n_vols = self.dataset.n_vols
                n_runs = int(len(y_data) / n_vols)

                for i in range(1, ar+1):

                    _pad = shared(np.zeros((i,)))
                    _trunc = self.shared_params['y'][:-i]
                    y_shifted = T.concatenate((_pad, _trunc))
                    weights = np.r_[np.zeros(i), np.ones(n_vols-i)]

                    # Model an AR term for each run or use just one for all runs
                    if by_run:
                        smoother = pm.Cauchy('AR(%d)' % i, alpha=0, beta=1, shape=n_runs)
                        weights = np.outer(weights, np.eye(n_runs))
                        weights = np.reshape(weights, (n_vols*n_runs, n_runs), order='F')
                        _ar = pm.dot(weights, smoother) * y_shifted
                    else:
                        smoother = pm.Cauchy('AR(%d)' % i, alpha=0, beta=1)
                        weights = np.tile(weights, n_runs)
                        _ar = shared(weights) * y_shifted * smoother

                    self.mu += _ar

                sigma = pm.HalfCauchy('sigma_y_obs', beta=10)
                y_obs = pm.Normal('Y_obs', mu=self.mu, sd=sigma, 
                                  observed=self.shared_params['y'])
        else:
            self.shared_params['y'].set_value(y_data)
Beispiel #4
0
def simulate(num_subs, num_stims, A_mean, B_mean, sub_A_sd, sub_B_sd, stim_A_sd,
    stim_B_sd, resid_sd, ar=None, block_size=None):
    # build stimulus list
    stims = np.random.normal(size=num_stims//2, loc=1, scale=stim_A_sd/A_mean).tolist() + \
            np.random.normal(size=num_stims//2, loc=1, scale=stim_B_sd/B_mean).tolist()
    stims = pd.DataFrame({'stim':range(num_stims),
                          'condition':np.repeat([0,1], num_stims//2),
                          'effect':np.array(stims)})
    
    # now build design matrix from stimulus list
    if block_size is None:
        # build event-related design
        data = pd.concat([build_seq(sub_num=i, stims=stims, sub_A_sd=sub_A_sd, sub_B_sd=sub_B_sd) for i in range(num_subs)])
    else:
        # build blocked design
        data = pd.concat([build_seq_block(sub_num=i, stims=stims, sub_A_sd=sub_A_sd, sub_B_sd=sub_B_sd, block_size=block_size) for i in range(num_subs)])

    # add response variable and difference predictor
    if ar is None:
        # build y WITHOUT AR(2) errors
        data['y'] = (A_mean + data['sub_A'])*data.iloc[:,:(num_stims//2)].sum(axis=1).values + \
                    (B_mean + data['sub_B'])*data.iloc[:,(num_stims//2):num_stims].sum(axis=1).values + \
                    np.random.normal(size=len(data.index), scale=resid_sd)
    else:
        # build y WITH AR(2) errors
        data['y'] = np.empty(len(data.index))
        data['y_t-1'] = np.zeros(len(data.index))
        data['y_t-2'] = np.zeros(len(data.index))
        for t in range(len(pd.unique(data['time']))):
            data.loc[t,'y'] = pd.DataFrame(
                (A_mean + data.loc[t,'sub_A'])*data.loc[t, range(num_stims//2)].sum(axis=1).values + \
                (B_mean + data.loc[t,'sub_B'])*data.loc[t, range(num_stims//2, num_stims)].sum(axis=1).values + \
                np.random.normal(size=len(data.loc[t].index), scale=resid_sd)).values
            if t==1:
                data.loc[t,'y'] = pd.DataFrame(data.loc[t,'y'].values + ar[0]*data.loc[t-1,'y'].values).values
                data.loc[t,'y_t-1'] = pd.DataFrame(data.loc[t-1,'y']).values
            if t>1:
                data.loc[t,'y'] = pd.DataFrame(data.loc[t,'y'].values + ar[0]*data.loc[t-1,'y'].values + ar[1]*data.loc[t-2,'y'].values).values
                data.loc[t,'y_t-1'] = pd.DataFrame(data.loc[t-1,'y']).values
                data.loc[t,'y_t-2'] = pd.DataFrame(data.loc[t-2,'y']).values

    # remove random stimulus effects from regressors before fitting model
    data.iloc[:, :num_stims] = data.iloc[:, :num_stims] / stims['effect'].tolist()

    # build design DataFrame
    # create num_subs * num_stims DataFrame
    # where each cell is when that stim was presented for that sub
    # note that this depends on there being no repeated stimulus presentations
    gb = data.groupby('sub_num')
    pres = pd.DataFrame([[next(i-1 for i, val in enumerate(df.iloc[:,stim]) if abs(val) > .0001)
                          for stim in range(num_stims)] for sub_num, df in gb])
    # build the design DataFrame from pres
    design = pd.concat([pd.DataFrame({'onset':pres.iloc[sub,:].sort_values(),
                                      'run_onset':pres.iloc[sub,:].sort_values(),
                                      'stimulus':pres.iloc[sub,:].sort_values().index,
                                      'subject':sub,
                                      'duration':1,
                                      'amplitude':1,
                                      'run':1,
                                      'index':range(pres.shape[1])})
                        for sub in range(num_subs)])
    design['condition'] = stims['condition'][design['stimulus']]

    # build activation DataFrame
    activation = pd.DataFrame({'y':data['y'].values,
                               'vol':data['time'],
                               'run':1,
                               'subject':data['sub_num']})

    # build Dataset object
    dataset = nipymc.data.Dataset(design=design, activation=activation, TR=1)
    
    ####################################
    ############ FIT MODELS ############
    ####################################
    
    # SPM model
    def get_diff(df):
        X = pd.concat([df.iloc[:,:num_stims//2].sum(axis=1),
                       df.iloc[:,num_stims//2:num_stims].sum(axis=1),
                       df['y_t-1'],
                       df['y_t-2']], axis=1)
        beta = pd.stats.api.ols(y=df['y'], x=X, intercept=False).beta
        return pd.Series(beta[1] - beta[0]).append(beta)    
    sub_diffs = data.groupby('sub_num').apply(get_diff)

    # fit model with FIXED stim effects (LS-All model)
    with pm.Model():

        # Fixed effects
        b = pm.Normal('fixstim_b', mu=0, sd=10, shape=num_stims)
        if ar is not None:
            ar1 = pm.Cauchy('fixstim_AR1', alpha=0, beta=1)
            ar2 = pm.Cauchy('fixstim_AR2', alpha=0, beta=1)

        # random x1 & x2 slopes for participants
        sigma_sub_A = pm.HalfCauchy('fixstim_sigma_sub_A', beta=10)
        sigma_sub_B = pm.HalfCauchy('fixstim_sigma_sub_B', beta=10)
        u0 = pm.Normal('u0_sub_A_log', mu=0., sd=sigma_sub_A, shape=data['sub_num'].nunique())
        u1 = pm.Normal('u1_sub_B_log', mu=0., sd=sigma_sub_B, shape=data['sub_num'].nunique())

        # now write the mean model
        mu = u0[data['sub_num'].values]*data.iloc[:,:(num_stims//2)].sum(axis=1).values + \
             u1[data['sub_num'].values]*data.iloc[:,(num_stims//2):num_stims].sum(axis=1).values + \
             pm.dot(data.iloc[:, :num_stims].values, b)
        if ar is not None: mu += ar1*data['y_t-1'].values + ar2*data['y_t-2'].values

        # define the condition contrast
        cond_diff = Deterministic('fixstim_cond_diff', T.mean(b[num_stims//2:]) - T.mean(b[:num_stims//2]))
        
        # model for the observed values
        Y_obs = pm.Normal('Y_obs', mu=mu, sd=pm.HalfCauchy('fixstim_sigma', beta=10),
                          observed=data['y'].values)

        # run the sampler
        step = pm.NUTS()
        print('fitting fixstim model...')
        trace0 = pm.sample(SAMPLES, step=step, progressbar=False)   

    # fit model WITHOUT random stim effects
    with pm.Model():

        # Fixed effects
        b1 = pm.Normal('nostim_b_A', mu=0, sd=10)
        b2 = pm.Normal('nostim_b_B', mu=0, sd=10)
        if ar is not None:
            ar1 = pm.Cauchy('nostim_AR1', alpha=0, beta=1)
            ar2 = pm.Cauchy('nostim_AR2', alpha=0, beta=1)

        # random x1 & x2 slopes for participants
        sigma_sub_A = pm.HalfCauchy('nostim_sigma_sub_A', beta=10)
        sigma_sub_B = pm.HalfCauchy('nostim_sigma_sub_B', beta=10)
        u0 = pm.Normal('u0_sub_A_log', mu=0., sd=sigma_sub_A, shape=data['sub_num'].nunique())
        u1 = pm.Normal('u1_sub_B_log', mu=0., sd=sigma_sub_B, shape=data['sub_num'].nunique())

        # now write the mean model
        mu = (b1 + u0[data['sub_num'].values])*data.iloc[:,:(num_stims//2)].sum(axis=1).values + \
             (b2 + u1[data['sub_num'].values])*data.iloc[:,(num_stims//2):num_stims].sum(axis=1).values
        if ar is not None: mu += ar1*data['y_t-1'].values + ar2*data['y_t-2'].values

        # define the condition contrast
        cond_diff = Deterministic('nostim_cond_diff', b2 - b1)

        # model for the observed values
        Y_obs = pm.Normal('Y_obs', mu=mu, sd=pm.HalfCauchy('nostim_sigma', beta=10),
                          observed=data['y'].values)

        # run the sampler
        step = pm.NUTS()
        print('fitting nostim model...')
        trace1 = pm.sample(SAMPLES, step=step, progressbar=False)
    
    # fit model with separate dists + variances
    with pm.Model():

        # Fixed effects
        b1 = pm.Normal('randstim_b_A', mu=0, sd=10)
        b2 = pm.Normal('randstim_b_B', mu=0, sd=10)
        if ar is not None:
            ar1 = pm.Cauchy('randstim_AR1', alpha=0, beta=1)
            ar2 = pm.Cauchy('randstim_AR2', alpha=0, beta=1)

        # random x1 & x2 slopes for participants
        sigma_sub_A = pm.HalfCauchy('randstim_sigma_sub_A', beta=10)
        sigma_sub_B = pm.HalfCauchy('randstim_sigma_sub_B', beta=10)
        u0 = pm.Normal('u0_sub_A_log', mu=0., sd=sigma_sub_A, shape=data['sub_num'].nunique())
        u1 = pm.Normal('u1_sub_B_log', mu=0., sd=sigma_sub_B, shape=data['sub_num'].nunique())

        # random stim intercepts
        sigma_stim_A = pm.HalfCauchy('randstim_sigma_stim_A', beta=10)
        u2 = pm.Normal('randstim_stim_A', mu=0., sd=sigma_stim_A, shape=num_stims//2)
        sigma_stim_B = pm.HalfCauchy('randstim_sigma_stim_B', beta=10)
        u3 = pm.Normal('randstim_stim_B', mu=0., sd=sigma_stim_B, shape=num_stims//2)

        # now write the mean model
        mu = (b1 + u0[data['sub_num'].values])*data.iloc[:,:(num_stims//2)].sum(axis=1).values + \
             (b2 + u1[data['sub_num'].values])*data.iloc[:,(num_stims//2):num_stims].sum(axis=1).values + \
             pm.dot(data.iloc[:, :num_stims//2].values, u2) + pm.dot(data.iloc[:, (num_stims//2):num_stims].values, u3)
        if ar is not None: mu += ar1*data['y_t-1'].values + ar2*data['y_t-2'].values

        # define the condition contrast
        cond_diff = Deterministic('randstim_cond_diff', b2 - b1)

        # model for the observed values
        Y_obs = pm.Normal('Y_obs', mu=mu, sd=pm.HalfCauchy('randstim_sigma', beta=10),
                          observed=data['y'].values)

        # run the sampler
        step = pm.NUTS()
        print('fitting 2dist2var model...')
        trace2 = pm.sample(SAMPLES, step=step, progressbar=False)
    
    # fit FIX_STIM model using pymcwrap
    mod3 = nipymc.model.BayesianModel(dataset)
    mod3.add_term('subject', label='nipymc_fixstim_subject', split_by='condition', categorical=True, random=True)
    mod3.add_term('stimulus', label='nipymc_fixstim_stimulus', categorical=True)
    mod3.groupA = [mod3.level_map['nipymc_fixstim_stimulus'][i] for i in range(num_stims//2)]
    mod3.groupB = [mod3.level_map['nipymc_fixstim_stimulus'][i] for i in range(num_stims//2, num_stims)]
    mod3.add_deterministic('nipymc_fixstim_cond_diff',
        "T.mean(self.dists['b_nipymc_fixstim_stimulus'][self.groupB]) - T.mean(self.dists['b_nipymc_fixstim_stimulus'][self.groupA])")
    mod3.set_y('y', scale=None, detrend=False, ar=0 if ar is None else 2)
    print('fitting nipymc_fixstim model...')
    mod3_fitted = mod3.run(samples=SAMPLES, verbose=False, find_map=False)

    # fit NO_STIM model using pymcwrap
    mod4 = nipymc.model.BayesianModel(dataset)
    mod4.add_term('condition', label='nipymc_nostim_condition', categorical=True, scale=False)
    mod4.add_term('subject', label='nipymc_nostim_subject', split_by='condition', categorical=True, random=True)
    groupA = str(mod4.level_map['nipymc_nostim_condition'][0])
    groupB = str(mod4.level_map['nipymc_nostim_condition'][1])
    mod4.add_deterministic('nipymc_nostim_cond_diff',
        "self.dists['b_nipymc_nostim_condition']["+groupB+"] - self.dists['b_nipymc_nostim_condition']["+groupA+"]")
    mod4.set_y('y', scale=None, detrend=False, ar=0 if ar is None else 2)
    print('fitting nipymc_nostim model...')
    mod4_fitted = mod4.run(samples=SAMPLES, verbose=False, find_map=False)
    
    # fit 2dist2var model using pymcwrap
    mod5 = nipymc.model.BayesianModel(dataset)
    mod5.add_term('condition', label='nipymc_randstim_condition', categorical=True, scale=False)
    mod5.add_term('stimulus', label='nipymc_randstim_stimulus', split_by='condition',  categorical=True, random=True)
    mod5.add_term('subject', label='nipymc_randstim_subject', split_by='condition', categorical=True, random=True)
    groupA = str(mod5.level_map['nipymc_randstim_condition'][0])
    groupB = str(mod5.level_map['nipymc_randstim_condition'][1])
    mod5.add_deterministic('nipymc_randstim_cond_diff',
        "self.dists['b_nipymc_randstim_condition']["+groupB+"] - self.dists['b_nipymc_randstim_condition']["+groupA+"]")
    mod5.set_y('y', scale=None, detrend=False, ar=0 if ar is None else 2)
    print('fitting nipymc_randstim model...')
    mod5_fitted = mod5.run(samples=SAMPLES, verbose=False, find_map=False)

    # # save PNG of traceplot
    # plt.figure()
    # pm.traceplot(trace2[BURN:])
    # plt.savefig('pymc3_randstim.png')
    # plt.close()

    # plt.figure()
    # pm.traceplot(mod5_fitted.trace[BURN:])
    # plt.savefig('nipymc_randstim.png')
    # plt.close()

    ######################################
    ########## SAVE RESULTS ##############
    ######################################

    # return parameter estimates
    print('computing and returning parameter estimates...')

    # lists of traces and names of their model parameters
    traces = [trace0,            # fixstim
              trace1,            # nostim
              trace2,            # randstim
              mod3_fitted.trace, # nipymc_fixstim
              mod4_fitted.trace, # nipymc_nostim
              mod5_fitted.trace] # nipymc_randstim
    parlists = [[x for x in trace.varnames if 'log' not in x and 'u_' not in x] for trace in traces]

    # get posterior mean and SDs as lists of lists
    means = [[trace[param][BURN:].mean() for param in parlist] for trace, parlist in zip(traces, parlists)]
    SDs = [[trace[param][BURN:].std() for param in parlist] for trace, parlist in zip(traces, parlists)]

    # print list of summary statistics
    stats = sum([['posterior_mean']*len(x) + ['posterior_SD']*len(x) for x in parlists], [])
    print(stats)
    print(len(stats))

    # print parameter names in the order in which they are saved
    parlists = [2*parlist for parlist in parlists]
    extra_params = []
    params = [param for parlist in parlists for param in parlist] + extra_params
    print(params)

    # add SPM model results
    ans = [summary for model in zip(means, SDs) for summary in model]
    ans = [sub_diffs.mean(0).tolist(), (sub_diffs.std(0)/(len(sub_diffs.index)**.5)).tolist()] + ans
    params = ['SPM_cond_diff','SPM_A_mean','SPM_B_mean','SPM_AR1','SPM_AR2']*2 + params
    stats = ['posterior_mean']*5 + ['posterior_SD']*5 + stats

    # add test statistics for all models
    # grab all posterior means
    nums = [np.array(x) for x in ans][::2]
    # grab all posterior SDs
    denoms = [np.array(x) for x in ans][1::2]
    # divide them
    zs = [n/d for n,d in zip(nums,denoms)]
    zs = sum([x.tolist() for x in zs], [])
    # keep only the test statistics related to cond_diff
    labels = [params[i] for i in [j for j,x in enumerate(stats) if x=='posterior_mean']]
    zs = [(z,l) for z,l in zip(zs,labels) if 'cond_diff' in l]
    # add them to the results
    ans = [[x[0] for x in zs]] + ans
    params = [x[1] for x in zs] + params
    stats = ['test_statistic']*7 + stats

    # return the parameter values
    # for first instance only, also return param names and etc.
    if int(instance)==0: ans = [ans, params, stats]
    return ans
Beispiel #5
0
    def add_term(self,
                 variable,
                 label=None,
                 categorical=False,
                 random=False,
                 split_by=None,
                 yoke_random_mean=False,
                 estimate_random_mean=False,
                 dist='Normal',
                 scale=None,
                 trend=None,
                 orthogonalize=None,
                 convolution=None,
                 conv_kws=None,
                 sigma_kws=None,
                 withhold=False,
                 plot=False,
                 **kwargs):
        '''
        Args:
            variable (str): name of the variable in the Dataset that contains
                the predictor data for the term, or a list of variable names.
            label (str): short name/label of the term; will be used as the
                name passed to PyMC. If None, the variable name is used.
            categorical (bool): if False, treat the input data as continuous;
                if True, treats input as categorical, and assigns discrete
                levels to different columns in the predictor matrix
            random (bool): if False, model as fixed effect; if True, model as
                random effect
            split_by (str): optional name of another variable on which to split
                the target variable. A separate hyperparameter will be included
                for each level in the split_by variable. E.g., if variable = 
                'stimulus' and split_by = 'category', the model will include
                one parameter for each individual stimulus, plus C additional
                hyperparameters for the stimulus variances (one per category).
            yoke_random_mean (bool):
            estimate_random_mean (bool): If False (default), set mean of random
                effect distribution to 0. If True, estimate mean parameters for 
                each level of split_by (in which case the corresponding fixed
                effect parameter should be omitted, for identifiability reasons).
                If split_by=None, this is equivalent to estimating a fixed
                intercept term. Note that models parameterized in this way are
                often less numerically stable than the default parameterization.
            dist (str, Distribution): the PyMC3 distribution to use for the
                prior. Can be either a string (must be the name of a class in
                pymc3.distributions), or an uninitialized Distribution object.
            scale (str, bool): if 'before', scaling will be applied before
                convolving with the HRF. If 'after', scaling will be applied to
                the convolved regressor. True is treated like 'before'. If
                None (default), no scaling is done.
            trend (int): if variable is 'subject' or 'run', passing an int here
                will result in addition of an Nth-order polynomial trend
                instead of the expected intercept. E.g., when variable = 
                'run' and trend = 1, a linear trend will be added for each run.
            orthogonalize (list): list of variables to orthogonalize the target
                variable with respect to. For now, this only works for
                categorical covariates. E.g., if variable = 'condition' and
                orthogonalize = ['stimulus_category'], each level of condition
                will be residualized on all (binarized) levels of stimulus
                condition.
            convolution (str): the name of the convolution function to apply
                to the input data; must be a valid function in convolutions.py.
                If None, the default convolution function set at class
                initialization is used. If 'none' is passed, no convolution
                at all is applied.
            conv_kws (dict): optional dictionary of additional keyword
                arguments to pass onto the selected convolution function.
            sigma_kws (dict): optional dictionary of keyword arguments
                specifying the parameters of the Distribution to use as the
                sigma for a random variable. Defaults to HalfCauchy with
                beta=10. Ignored unless random=True.
            withhold (bool): if True, the PyMC distribution(s) will be created
                but not added to the prediction equation. This is useful when,
                e.g., yoking the mean of one distribution to the estimated
                value of another distribution, without including the same
                quantity twice.
            plot (bool): if True, plots the resulting design matrix component.
            kwargs: optional keyword arguments passed onto the selected PyMC3
                Distribution.
        '''

        if label is None:
            label = '_'.join(listify(variable))

        # Load design matrix for requested variable
        dm = self._get_variable_data(variable,
                                     categorical,
                                     label=label,
                                     trend=trend)
        n_cols = dm.shape[1]

        # Handle random effects with nesting/crossing. Basically this splits the design
        # matrix into a separate matrix for each level of split_by, stacked into 3D array
        if split_by is not None:
            split_dm = self._get_variable_data(split_by, True)
            dm = np.einsum('ab,ac->abc', dm, split_dm)

        # Orthogonalization
        # TODO: generalize this to handle any combination of settings; right
        # now it will only work properly when both the target variable and the
        # covariates are categorical fixed effects.
        if orthogonalize is not None:
            dm = self._orthogonalize(dm, orthogonalize)

        # Scaling and HRF: apply over last dimension
        # if there is no split_by, add a dummy 3rd dimension so code below works in general
        if dm.ndim == 2:
            dm = dm[..., None]

        if plot and plot != 'convolved':
            self.plot_design_matrix(dm, variable, split_by)

        for i in range(dm.shape[-1]):

            if scale and scale != 'after':
                dm[..., i] = standardize(dm[..., i])

            # Convolve with HRF
            if variable not in ['intercept'] and convolution is not 'none':
                if convolution is None:
                    convolution = self.convolution
                elif not hasattr(convolution, 'shape'):
                    convolution = get_convolution(convolution, conv_kws)

                # Convolve each run separately
                n_vols = self.dataset.n_vols
                n_runs = int(len(dm) / n_vols)
                for r in range(n_runs):
                    start, end = r * n_vols, (r * n_vols) + n_vols
                    _convolved = self._convolve(dm[start:end, :, i],
                                                convolution)
                    dm[start:end, :, i] = _convolved  # np.squeeze(_convolved)

            if scale == 'after':
                dm[..., i] = standardize(dm[..., i])

        if plot and plot == 'convolved':
            self.plot_design_matrix(dm, variable, split_by)

        # remove the dummy 3rd dimension if it was added prior to scaling/convolution
        if dm.shape[-1] == 1:
            dm = dm.reshape(dm.shape[:2])

        with self.model:

            # Random effects
            if random:
                # User can pass sigma specification in sigma_kws.
                # If not provided, default to HalfCauchy with beta = 10.
                if sigma_kws is None:
                    sigma_kws = {'dist': 'HalfCauchy', 'beta': 1}

                if split_by is None:
                    sigma = self._build_dist('sigma_' + label, **sigma_kws)
                    if estimate_random_mean:
                        mu = self._build_dist('b_' + label, dist)
                    else:
                        mu = 0.
                    u = self._build_dist('u_' + label,
                                         dist,
                                         mu=mu,
                                         sd=sigma,
                                         shape=n_cols,
                                         **kwargs)
                    self.mu += pm.dot(dm, u)
                else:
                    # id_map is essentially a crosstab except each cell is either 0 or 1
                    id_map = self._get_membership_graph(variable, split_by)
                    for i in range(id_map.shape[1]):
                        # select just the factor levels that appear with the
                        # current level of split_by
                        group_items = id_map.iloc[:, i].astype(bool)
                        selected = dm[:, group_items.values, i]
                        # add the level effects to the model
                        name = '%s_%s' % (label, id_map.columns[i])
                        sigma = self._build_dist('sigma_' + name, **sigma_kws)
                        if yoke_random_mean:
                            mu = self.dists['b_' + split_by][i]
                        elif estimate_random_mean:
                            mu = self._build_dist('b_' + name, dist)
                        else:
                            mu = 0.
                        name, size = 'u_' + name, selected.shape[1]
                        u = self._build_dist(name,
                                             dist,
                                             mu=mu,
                                             sd=sigma,
                                             shape=size,
                                             **kwargs)
                        self.mu += pm.dot(selected, u)

                        # Update the level map
                        levels = group_items[group_items].index.tolist()
                        self.level_map[name] = OrderedDict(
                            zip(levels, list(range(size))))

            # Fixed effects
            else:
                b = self._build_dist('b_' + label,
                                     dist,
                                     shape=dm.shape[-1],
                                     **kwargs)
                if split_by is not None:
                    dm = np.squeeze(dm)
                if not withhold:
                    self.mu += pm.dot(dm, b)
Beispiel #6
0
    y = np.sum(x * beta_true[1:].T, axis=1) + beta_true[0] + norm.rvs(0, sd_true, nData)
    # Select which predictors to include
    includeOnly = range(0, n_predictors) # default is to include all
    #x = x.iloc[includeOnly]
    predictorNames = x.columns
    n_predictors = len(predictorNames)



# THE MODEL
with pm.Model() as model:
    # define the priors
    beta0 = pm.Normal('beta0', mu=0, tau=1.0E-12)
    beta1 = pm.Normal('beta1', mu= 0, tau=1.0E-12, shape=n_predictors)
    tau = pm.Gamma('tau', 0.01, 0.01)
    mu = beta0 + pm.dot(beta1, x.values.T)
    # define the likelihood
    yl = pm.Normal('yl', mu=mu, tau=tau, observed=y)
    # Generate a MCMC chain
    start = pm.find_MAP()
    step1 = pm.NUTS([beta1])
    step2 = pm.Metropolis([beta0, tau])
    trace = pm.sample(10000, [step1, step2], start, progressbar=False)

# EXAMINE THE RESULTS
burnin = 5000
thin = 1

# Print summary for each trace
#pm.summary(trace[burnin::thin])
#pm.summary(trace)
Beispiel #7
0
    def add_term(self, variable, label=None, categorical=False, random=False,
                 split_by=None, yoke_random_mean=False, estimate_random_mean=False,
                 dist='Normal', scale=None, trend=None, orthogonalize=None,
                 convolution=None, conv_kws=None, sigma_kws=None, withhold=False,
                 plot=False, **kwargs):
        '''
        Args:
            variable (str): name of the variable in the Dataset that contains
                the predictor data for the term, or a list of variable names.
            label (str): short name/label of the term; will be used as the
                name passed to PyMC. If None, the variable name is used.
            categorical (bool): if False, treat the input data as continuous;
                if True, treats input as categorical, and assigns discrete
                levels to different columns in the predictor matrix
            random (bool): if False, model as fixed effect; if True, model as
                random effect
            split_by (str): optional name of another variable on which to split
                the target variable. A separate hyperparameter will be included
                for each level in the split_by variable. E.g., if variable = 
                'stimulus' and split_by = 'category', the model will include
                one parameter for each individual stimulus, plus C additional
                hyperparameters for the stimulus variances (one per category).
            yoke_random_mean (bool):
            estimate_random_mean (bool): If False (default), set mean of random
                effect distribution to 0. If True, estimate mean parameters for 
                each level of split_by (in which case the corresponding fixed
                effect parameter should be omitted, for identifiability reasons).
                If split_by=None, this is equivalent to estimating a fixed
                intercept term. Note that models parameterized in this way are
                often less numerically stable than the default parameterization.
            dist (str, Distribution): the PyMC3 distribution to use for the
                prior. Can be either a string (must be the name of a class in
                pymc3.distributions), or an uninitialized Distribution object.
            scale (str, bool): if 'before', scaling will be applied before
                convolving with the HRF. If 'after', scaling will be applied to
                the convolved regressor. True is treated like 'before'. If
                None (default), no scaling is done.
            trend (int): if variable is 'subject' or 'run', passing an int here
                will result in addition of an Nth-order polynomial trend
                instead of the expected intercept. E.g., when variable = 
                'run' and trend = 1, a linear trend will be added for each run.
            orthogonalize (list): list of variables to orthogonalize the target
                variable with respect to. For now, this only works for
                categorical covariates. E.g., if variable = 'condition' and
                orthogonalize = ['stimulus_category'], each level of condition
                will be residualized on all (binarized) levels of stimulus
                condition.
            convolution (str): the name of the convolution function to apply
                to the input data; must be a valid function in convolutions.py.
                If None, the default convolution function set at class
                initialization is used. If 'none' is passed, no convolution
                at all is applied.
            conv_kws (dict): optional dictionary of additional keyword
                arguments to pass onto the selected convolution function.
            sigma_kws (dict): optional dictionary of keyword arguments
                specifying the parameters of the Distribution to use as the
                sigma for a random variable. Defaults to HalfCauchy with
                beta=10. Ignored unless random=True.
            withhold (bool): if True, the PyMC distribution(s) will be created
                but not added to the prediction equation. This is useful when,
                e.g., yoking the mean of one distribution to the estimated
                value of another distribution, without including the same
                quantity twice.
            plot (bool): if True, plots the resulting design matrix component.
            kwargs: optional keyword arguments passed onto the selected PyMC3
                Distribution.
        '''

        if label is None:
            label = '_'.join(listify(variable))

        # Load design matrix for requested variable
        dm = self._get_variable_data(variable, categorical, label=label,
                                     trend=trend)
        n_cols = dm.shape[1]

        # Handle random effects with nesting/crossing. Basically this splits the design
        # matrix into a separate matrix for each level of split_by, stacked into 3D array
        if split_by is not None:
            split_dm = self._get_variable_data(split_by, True)
            dm = np.einsum('ab,ac->abc', dm, split_dm)

        # Orthogonalization
        # TODO: generalize this to handle any combination of settings; right
        # now it will only work properly when both the target variable and the
        # covariates are categorical fixed effects.
        if orthogonalize is not None:
            dm = self._orthogonalize(dm, orthogonalize)

        # Scaling and HRF: apply over last dimension
        # if there is no split_by, add a dummy 3rd dimension so code below works in general
        if dm.ndim == 2:
            dm = dm[..., None]

        for i in range(dm.shape[-1]):

            if scale and scale != 'after':
                dm[..., i] = standardize(dm[..., i])

        if plot:
            self.plot_design_matrix(dm, variable, split_by)

            # Convolve with HRF
            if variable not in ['subject', 'run', 'intercept'] and convolution is not 'none':
                if convolution is None:
                    convolution = self.convolution
                elif not hasattr(convolution, 'shape'):
                    convolution = get_convolution(convolution, conv_kws)

                _convolved = self._convolve(dm[..., i], convolution)
                dm[..., i] = _convolved  # np.squeeze(_convolved)

            if scale == 'after':
                dm[..., i] = standardize(dm[..., i])

        # remove the dummy 3rd dimension if it was added prior to scaling/convolution
        if dm.shape[-1] == 1:
            dm = dm.reshape(dm.shape[:2])

        with self.model:

            # Random effects
            if random:

                # User can pass sigma specification in sigma_kws.
                # If not provided, default to HalfCauchy with beta = 10.
                if sigma_kws is None:
                    sigma_kws = {'dist': 'HalfCauchy', 'beta': 10}

                if split_by is None:
                    sigma = self._build_dist('sigma_' + label, **sigma_kws)
                    if estimate_random_mean:
                        mu = self._build_dist('b_' + label, dist)
                    else:
                        mu = 0.
                    u = self._build_dist('u_' + label, dist, mu=mu, sd=sigma,
                                         shape=n_cols, **kwargs)
                    self.mu += pm.dot(dm, u)
                else:
                    # id_map is essentially a crosstab except each cell is either 0 or 1
                    id_map = self._get_membership_graph(variable, split_by)
                    for i in range(id_map.shape[1]):
                        # select just the factor levels that appear with the
                        # current level of split_by
                        group_items = id_map.iloc[:, i].astype(bool)
                        selected = dm[:, group_items.values, i]
                        # add the level effects to the model
                        name = '%s_%s' % (label, id_map.columns[i])
                        sigma = self._build_dist('sigma_' + name, **sigma_kws)
                        if yoke_random_mean:
                            mu = self.dists['b_' + split_by][i]
                        elif estimate_random_mean:
                            mu = self._build_dist('b_' + name, dist)
                        else:
                            mu = 0.
                        name, size = 'u_' + name, selected.shape[1]
                        u = self._build_dist(name, dist, mu=mu, sd=sigma,
                                             shape=size, **kwargs)
                        self.mu += pm.dot(selected, u)

                        # Update the level map
                        levels = group_items[group_items].index.tolist()
                        self.level_map[name] = OrderedDict(zip(levels, list(range(size))))

            # Fixed effects
            else:
                b = self._build_dist('b_' + label, dist, shape=dm.shape[-1],
                                         **kwargs)
                if split_by is not None:
                    dm = np.squeeze(dm)
                if not withhold:
                    self.mu += pm.dot(dm, b)
Beispiel #8
0
# random stimulus model
model = pm.Model()
with model:
    
    # Intercept
    mu = pm.Normal('intercept', 0, 10)
#     mu = 0
    
    # Categorical fixed effects
    betas = {}
#     cat_fe = ['Valence', 'RACE']
    cat_fe = ['Valence']
    for cfe in cat_fe:
        dummies = pd.get_dummies(X[cfe], drop_first=True).values
        _b = pm.Normal('b_%s' % cfe, 0, 10, shape=dummies.shape[1])
        mu += pm.dot(dummies, _b)
        betas[cfe] = _b

    # Continuous fixed effects
#     cont_fe = ['AGE', 'YRS_SCH', 'SEX']
    cont_fe = []
    for cfe in cont_fe:
        _b = pm.Normal('b_%s' % cfe, 0, 10)
        mu += _b * X[cfe].values
        betas[cfe] = _b
    
#     # Contrast between conditions
#     b_valence = betas['Valence']
#     c = pm.Deterministic('valence_contrast', b_valence[0] - b_valence[1])
    
    # Random effects
Beispiel #9
0
# random stimulus model
model = pm.Model()
with model:

    # Intercept
    mu = pm.Normal('intercept', 0, 10)
    #     mu = 0

    # Categorical fixed effects
    betas = {}
    #     cat_fe = ['Valence', 'RACE']
    cat_fe = ['Valence']
    for cfe in cat_fe:
        dummies = pd.get_dummies(X[cfe], drop_first=True).values
        _b = pm.Normal('b_%s' % cfe, 0, 10, shape=dummies.shape[1])
        mu += pm.dot(dummies, _b)
        betas[cfe] = _b

    # Continuous fixed effects
#     cont_fe = ['AGE', 'YRS_SCH', 'SEX']
    cont_fe = []
    for cfe in cont_fe:
        _b = pm.Normal('b_%s' % cfe, 0, 10)
        mu += _b * X[cfe].values
        betas[cfe] = _b

#     # Contrast between conditions
#     b_valence = betas['Valence']
#     c = pm.Deterministic('valence_contrast', b_valence[0] - b_valence[1])

# Random effects
Beispiel #10
0
def simulate(num_subs,
             num_stims,
             A_mean,
             B_mean,
             sub_A_sd,
             sub_B_sd,
             stim_A_sd,
             stim_B_sd,
             resid_sd,
             ar=None,
             block_size=None):
    # build stimulus list
    stims = np.random.normal(size=num_stims//2, loc=1, scale=stim_A_sd/A_mean).tolist() + \
            np.random.normal(size=num_stims//2, loc=1, scale=stim_B_sd/B_mean).tolist()
    stims = pd.DataFrame({
        'stim': range(num_stims),
        'condition': np.repeat([0, 1], num_stims // 2),
        'effect': np.array(stims)
    })

    # now build design matrix from stimulus list
    if block_size is None:
        # build event-related design
        data = pd.concat([
            build_seq(sub_num=i,
                      stims=stims,
                      sub_A_sd=sub_A_sd,
                      sub_B_sd=sub_B_sd) for i in range(num_subs)
        ])
    else:
        # build blocked design
        data = pd.concat([
            build_seq_block(sub_num=i,
                            stims=stims,
                            sub_A_sd=sub_A_sd,
                            sub_B_sd=sub_B_sd,
                            block_size=block_size) for i in range(num_subs)
        ])

    # add response variable and difference predictor
    if ar is None:
        # build y WITHOUT AR(2) errors
        data['y'] = (A_mean + data['sub_A'])*data.iloc[:,:(num_stims//2)].sum(axis=1).values + \
                    (B_mean + data['sub_B'])*data.iloc[:,(num_stims//2):num_stims].sum(axis=1).values + \
                    np.random.normal(size=len(data.index), scale=resid_sd)
    else:
        # build y WITH AR(2) errors
        data['y'] = np.empty(len(data.index))
        data['y_t-1'] = np.zeros(len(data.index))
        data['y_t-2'] = np.zeros(len(data.index))
        for t in range(len(pd.unique(data['time']))):
            data.loc[t,'y'] = pd.DataFrame(
                (A_mean + data.loc[t,'sub_A'])*data.loc[t, range(num_stims//2)].sum(axis=1).values + \
                (B_mean + data.loc[t,'sub_B'])*data.loc[t, range(num_stims//2, num_stims)].sum(axis=1).values + \
                np.random.normal(size=len(data.loc[t].index), scale=resid_sd)).values
            if t == 1:
                data.loc[t, 'y'] = pd.DataFrame(
                    data.loc[t, 'y'].values +
                    ar[0] * data.loc[t - 1, 'y'].values).values
                data.loc[t, 'y_t-1'] = pd.DataFrame(data.loc[t - 1,
                                                             'y']).values
            if t > 1:
                data.loc[t, 'y'] = pd.DataFrame(
                    data.loc[t, 'y'].values +
                    ar[0] * data.loc[t - 1, 'y'].values +
                    ar[1] * data.loc[t - 2, 'y'].values).values
                data.loc[t, 'y_t-1'] = pd.DataFrame(data.loc[t - 1,
                                                             'y']).values
                data.loc[t, 'y_t-2'] = pd.DataFrame(data.loc[t - 2,
                                                             'y']).values

    # remove random stimulus effects from regressors before fitting model
    data.iloc[:, :
              num_stims] = data.iloc[:, :num_stims] / stims['effect'].tolist()

    # build design DataFrame
    # create num_subs * num_stims DataFrame
    # where each cell is when that stim was presented for that sub
    # note that this depends on there being no repeated stimulus presentations
    gb = data.groupby('sub_num')
    pres = pd.DataFrame([[
        next(i - 1 for i, val in enumerate(df.iloc[:, stim])
             if abs(val) > .0001) for stim in range(num_stims)
    ] for sub_num, df in gb])
    # build the design DataFrame from pres
    design = pd.concat([
        pd.DataFrame({
            'onset': pres.iloc[sub, :].sort_values(),
            'run_onset': pres.iloc[sub, :].sort_values(),
            'stimulus': pres.iloc[sub, :].sort_values().index,
            'subject': sub,
            'duration': 1,
            'amplitude': 1,
            'run': 1,
            'index': range(pres.shape[1])
        }) for sub in range(num_subs)
    ])
    design['condition'] = stims['condition'][design['stimulus']]

    # build activation DataFrame
    activation = pd.DataFrame({
        'y': data['y'].values,
        'vol': data['time'],
        'run': 1,
        'subject': data['sub_num']
    })

    # build Dataset object
    dataset = nipymc.data.Dataset(design=design, activation=activation, TR=1)

    ####################################
    ############ FIT MODELS ############
    ####################################

    # SPM model
    def get_diff(df):
        X = pd.concat([
            df.iloc[:, :num_stims // 2].sum(axis=1),
            df.iloc[:, num_stims // 2:num_stims].sum(axis=1), df['y_t-1'],
            df['y_t-2']
        ],
                      axis=1)
        beta = pd.stats.api.ols(y=df['y'], x=X, intercept=False).beta
        return pd.Series(beta[1] - beta[0]).append(beta)

    sub_diffs = data.groupby('sub_num').apply(get_diff)

    # fit model with FIXED stim effects (LS-All model)
    with pm.Model():

        # Fixed effects
        b = pm.Normal('fixstim_b', mu=0, sd=10, shape=num_stims)
        if ar is not None:
            ar1 = pm.Cauchy('fixstim_AR1', alpha=0, beta=1)
            ar2 = pm.Cauchy('fixstim_AR2', alpha=0, beta=1)

        # random x1 & x2 slopes for participants
        sigma_sub_A = pm.HalfCauchy('fixstim_sigma_sub_A', beta=10)
        sigma_sub_B = pm.HalfCauchy('fixstim_sigma_sub_B', beta=10)
        u0 = pm.Normal('u0_sub_A_log',
                       mu=0.,
                       sd=sigma_sub_A,
                       shape=data['sub_num'].nunique())
        u1 = pm.Normal('u1_sub_B_log',
                       mu=0.,
                       sd=sigma_sub_B,
                       shape=data['sub_num'].nunique())

        # now write the mean model
        mu = u0[data['sub_num'].values]*data.iloc[:,:(num_stims//2)].sum(axis=1).values + \
             u1[data['sub_num'].values]*data.iloc[:,(num_stims//2):num_stims].sum(axis=1).values + \
             pm.dot(data.iloc[:, :num_stims].values, b)
        if ar is not None:
            mu += ar1 * data['y_t-1'].values + ar2 * data['y_t-2'].values

        # define the condition contrast
        cond_diff = Deterministic(
            'fixstim_cond_diff',
            T.mean(b[num_stims // 2:]) - T.mean(b[:num_stims // 2]))

        # model for the observed values
        Y_obs = pm.Normal('Y_obs',
                          mu=mu,
                          sd=pm.HalfCauchy('fixstim_sigma', beta=10),
                          observed=data['y'].values)

        # run the sampler
        step = pm.NUTS()
        print('fitting fixstim model...')
        trace0 = pm.sample(SAMPLES, step=step, progressbar=False)

    # fit model WITHOUT random stim effects
    with pm.Model():

        # Fixed effects
        b1 = pm.Normal('nostim_b_A', mu=0, sd=10)
        b2 = pm.Normal('nostim_b_B', mu=0, sd=10)
        if ar is not None:
            ar1 = pm.Cauchy('nostim_AR1', alpha=0, beta=1)
            ar2 = pm.Cauchy('nostim_AR2', alpha=0, beta=1)

        # random x1 & x2 slopes for participants
        sigma_sub_A = pm.HalfCauchy('nostim_sigma_sub_A', beta=10)
        sigma_sub_B = pm.HalfCauchy('nostim_sigma_sub_B', beta=10)
        u0 = pm.Normal('u0_sub_A_log',
                       mu=0.,
                       sd=sigma_sub_A,
                       shape=data['sub_num'].nunique())
        u1 = pm.Normal('u1_sub_B_log',
                       mu=0.,
                       sd=sigma_sub_B,
                       shape=data['sub_num'].nunique())

        # now write the mean model
        mu = (b1 + u0[data['sub_num'].values])*data.iloc[:,:(num_stims//2)].sum(axis=1).values + \
             (b2 + u1[data['sub_num'].values])*data.iloc[:,(num_stims//2):num_stims].sum(axis=1).values
        if ar is not None:
            mu += ar1 * data['y_t-1'].values + ar2 * data['y_t-2'].values

        # define the condition contrast
        cond_diff = Deterministic('nostim_cond_diff', b2 - b1)

        # model for the observed values
        Y_obs = pm.Normal('Y_obs',
                          mu=mu,
                          sd=pm.HalfCauchy('nostim_sigma', beta=10),
                          observed=data['y'].values)

        # run the sampler
        step = pm.NUTS()
        print('fitting nostim model...')
        trace1 = pm.sample(SAMPLES, step=step, progressbar=False)

    # fit model with separate dists + variances
    with pm.Model():

        # Fixed effects
        b1 = pm.Normal('randstim_b_A', mu=0, sd=10)
        b2 = pm.Normal('randstim_b_B', mu=0, sd=10)
        if ar is not None:
            ar1 = pm.Cauchy('randstim_AR1', alpha=0, beta=1)
            ar2 = pm.Cauchy('randstim_AR2', alpha=0, beta=1)

        # random x1 & x2 slopes for participants
        sigma_sub_A = pm.HalfCauchy('randstim_sigma_sub_A', beta=10)
        sigma_sub_B = pm.HalfCauchy('randstim_sigma_sub_B', beta=10)
        u0 = pm.Normal('u0_sub_A_log',
                       mu=0.,
                       sd=sigma_sub_A,
                       shape=data['sub_num'].nunique())
        u1 = pm.Normal('u1_sub_B_log',
                       mu=0.,
                       sd=sigma_sub_B,
                       shape=data['sub_num'].nunique())

        # random stim intercepts
        sigma_stim_A = pm.HalfCauchy('randstim_sigma_stim_A', beta=10)
        u2 = pm.Normal('randstim_stim_A',
                       mu=0.,
                       sd=sigma_stim_A,
                       shape=num_stims // 2)
        sigma_stim_B = pm.HalfCauchy('randstim_sigma_stim_B', beta=10)
        u3 = pm.Normal('randstim_stim_B',
                       mu=0.,
                       sd=sigma_stim_B,
                       shape=num_stims // 2)

        # now write the mean model
        mu = (b1 + u0[data['sub_num'].values])*data.iloc[:,:(num_stims//2)].sum(axis=1).values + \
             (b2 + u1[data['sub_num'].values])*data.iloc[:,(num_stims//2):num_stims].sum(axis=1).values + \
             pm.dot(data.iloc[:, :num_stims//2].values, u2) + pm.dot(data.iloc[:, (num_stims//2):num_stims].values, u3)
        if ar is not None:
            mu += ar1 * data['y_t-1'].values + ar2 * data['y_t-2'].values

        # define the condition contrast
        cond_diff = Deterministic('randstim_cond_diff', b2 - b1)

        # model for the observed values
        Y_obs = pm.Normal('Y_obs',
                          mu=mu,
                          sd=pm.HalfCauchy('randstim_sigma', beta=10),
                          observed=data['y'].values)

        # run the sampler
        step = pm.NUTS()
        print('fitting 2dist2var model...')
        trace2 = pm.sample(SAMPLES, step=step, progressbar=False)

    # fit FIX_STIM model using pymcwrap
    mod3 = nipymc.model.BayesianModel(dataset)
    mod3.add_term('subject',
                  label='nipymc_fixstim_subject',
                  split_by='condition',
                  categorical=True,
                  random=True)
    mod3.add_term('stimulus',
                  label='nipymc_fixstim_stimulus',
                  categorical=True)
    mod3.groupA = [
        mod3.level_map['nipymc_fixstim_stimulus'][i]
        for i in range(num_stims // 2)
    ]
    mod3.groupB = [
        mod3.level_map['nipymc_fixstim_stimulus'][i]
        for i in range(num_stims // 2, num_stims)
    ]
    mod3.add_deterministic(
        'nipymc_fixstim_cond_diff',
        "T.mean(self.dists['b_nipymc_fixstim_stimulus'][self.groupB]) - T.mean(self.dists['b_nipymc_fixstim_stimulus'][self.groupA])"
    )
    mod3.set_y('y', scale=None, detrend=False, ar=0 if ar is None else 2)
    print('fitting nipymc_fixstim model...')
    mod3_fitted = mod3.run(samples=SAMPLES, verbose=False, find_map=False)

    # fit NO_STIM model using pymcwrap
    mod4 = nipymc.model.BayesianModel(dataset)
    mod4.add_term('condition',
                  label='nipymc_nostim_condition',
                  categorical=True,
                  scale=False)
    mod4.add_term('subject',
                  label='nipymc_nostim_subject',
                  split_by='condition',
                  categorical=True,
                  random=True)
    groupA = str(mod4.level_map['nipymc_nostim_condition'][0])
    groupB = str(mod4.level_map['nipymc_nostim_condition'][1])
    mod4.add_deterministic(
        'nipymc_nostim_cond_diff', "self.dists['b_nipymc_nostim_condition'][" +
        groupB + "] - self.dists['b_nipymc_nostim_condition'][" + groupA + "]")
    mod4.set_y('y', scale=None, detrend=False, ar=0 if ar is None else 2)
    print('fitting nipymc_nostim model...')
    mod4_fitted = mod4.run(samples=SAMPLES, verbose=False, find_map=False)

    # fit 2dist2var model using pymcwrap
    mod5 = nipymc.model.BayesianModel(dataset)
    mod5.add_term('condition',
                  label='nipymc_randstim_condition',
                  categorical=True,
                  scale=False)
    mod5.add_term('stimulus',
                  label='nipymc_randstim_stimulus',
                  split_by='condition',
                  categorical=True,
                  random=True)
    mod5.add_term('subject',
                  label='nipymc_randstim_subject',
                  split_by='condition',
                  categorical=True,
                  random=True)
    groupA = str(mod5.level_map['nipymc_randstim_condition'][0])
    groupB = str(mod5.level_map['nipymc_randstim_condition'][1])
    mod5.add_deterministic(
        'nipymc_randstim_cond_diff',
        "self.dists['b_nipymc_randstim_condition'][" + groupB +
        "] - self.dists['b_nipymc_randstim_condition'][" + groupA + "]")
    mod5.set_y('y', scale=None, detrend=False, ar=0 if ar is None else 2)
    print('fitting nipymc_randstim model...')
    mod5_fitted = mod5.run(samples=SAMPLES, verbose=False, find_map=False)

    # # save PNG of traceplot
    # plt.figure()
    # pm.traceplot(trace2[BURN:])
    # plt.savefig('pymc3_randstim.png')
    # plt.close()

    # plt.figure()
    # pm.traceplot(mod5_fitted.trace[BURN:])
    # plt.savefig('nipymc_randstim.png')
    # plt.close()

    ######################################
    ########## SAVE RESULTS ##############
    ######################################

    # return parameter estimates
    print('computing and returning parameter estimates...')

    # lists of traces and names of their model parameters
    traces = [
        trace0,  # fixstim
        trace1,  # nostim
        trace2,  # randstim
        mod3_fitted.trace,  # nipymc_fixstim
        mod4_fitted.trace,  # nipymc_nostim
        mod5_fitted.trace
    ]  # nipymc_randstim
    parlists = [[
        x for x in trace.varnames if 'log' not in x and 'u_' not in x
    ] for trace in traces]

    # get posterior mean and SDs as lists of lists
    means = [[trace[param][BURN:].mean() for param in parlist]
             for trace, parlist in zip(traces, parlists)]
    SDs = [[trace[param][BURN:].std() for param in parlist]
           for trace, parlist in zip(traces, parlists)]

    # print list of summary statistics
    stats = sum([['posterior_mean'] * len(x) + ['posterior_SD'] * len(x)
                 for x in parlists], [])
    print(stats)
    print(len(stats))

    # print parameter names in the order in which they are saved
    parlists = [2 * parlist for parlist in parlists]
    extra_params = []
    params = [param for parlist in parlists
              for param in parlist] + extra_params
    print(params)

    # add SPM model results
    ans = [summary for model in zip(means, SDs) for summary in model]
    ans = [
        sub_diffs.mean(0).tolist(),
        (sub_diffs.std(0) / (len(sub_diffs.index)**.5)).tolist()
    ] + ans
    params = [
        'SPM_cond_diff', 'SPM_A_mean', 'SPM_B_mean', 'SPM_AR1', 'SPM_AR2'
    ] * 2 + params
    stats = ['posterior_mean'] * 5 + ['posterior_SD'] * 5 + stats

    # add test statistics for all models
    # grab all posterior means
    nums = [np.array(x) for x in ans][::2]
    # grab all posterior SDs
    denoms = [np.array(x) for x in ans][1::2]
    # divide them
    zs = [n / d for n, d in zip(nums, denoms)]
    zs = sum([x.tolist() for x in zs], [])
    # keep only the test statistics related to cond_diff
    labels = [
        params[i]
        for i in [j for j, x in enumerate(stats) if x == 'posterior_mean']
    ]
    zs = [(z, l) for z, l in zip(zs, labels) if 'cond_diff' in l]
    # add them to the results
    ans = [[x[0] for x in zs]] + ans
    params = [x[1] for x in zs] + params
    stats = ['test_statistic'] * 7 + stats

    # return the parameter values
    # for first instance only, also return param names and etc.
    if int(instance) == 0: ans = [ans, params, stats]
    return ans