bayesfit/psyFunction.py
def psyfunction(x, scale, slope, gamma, lambda_, sigmoid_type):
    with np.errstate(divide='ignore',invalid='ignore', over ='ignore'):
        if sigmoid_type == 'norm':
            return gamma + (1 - lambda_ - gamma) * _norm(x, scale, slope)
        elif sigmoid_type == 'logistic':
            return gamma + (1 - lambda_ - gamma) * _logistic(x, scale, slope)
        elif sigmoid_type == 'weibull':
            return gamma + (1 - lambda_ - gamma) * _weibull(x, scale, slope)
        elif sigmoid_type == 'gumbel':
            return gamma + (1 - lambda_ - gamma) * _gumbel(x, scale, slope)
        elif sigmoid_type == 'quick':
            return gamma + (1 - lambda_ - gamma) * _quick(x, scale, slope)
        elif sigmoid_type == 'log-quick':
            return gamma + (1 - lambda_ - gamma) * _quicklog(x, scale, slope)
        elif sigmoid_type == 'hyperbolic':
            return gamma + (1 - lambda_ - gamma) * _hyperbolic_secant(x, scale, slope)
bayesfit/checkParams.py
def check_params(data, param_ests, nafc, batch, sigmoid_type):
    """Performs that initial parameter estimates provided 
    by user is in proper format, and assigns default values 
    where necessary.

    Keyword arguments:
    data -- m x 3 numpy array
    param_ests -- list of user specified initial parameter estimates (list)
    nafc -- N-alternative forced choice experiment (integer)
    batch -- specifies whether batch fitting data (logical)
    sigmoid_type -- sigmoid type fit to data (string)
    """
    if param_ests is not None:
        # If batch, param_ests needs to be set to None to auto generate
        # values 
        if batch is True:
            raise Warning('''Warning: User cannot provide estimates for 
                            parameters when Batch options set to TRUE!''')
        # Check whether the variable for the parameter estimates provided is of type list
        if isinstance(param_ests, list) is False:
            raise ValueError('''User Error: Please provide a argument of type list for the 
            parameter estimates of the model.''')
        # Check number of arguments provided
        if len(param_ests) < 4:
            raise ValueError('''User Error: Please provide a list of four parameter
                            estimates for the model. Set estimate to NoneType if
                            want BayesFit to generate estimate for a parameter.''')
        elif len(param_ests) > 4:
            raise ValueError('''User Error: More than 4 estimates for parameters of the model provided!''')
        # Generate value estimates for initialization to fill in those not 
        # provided
        param_initial = [0, 0, 0, 0]
        param_guess = _param_guess(data, nafc, sigmoid_type)
        scale_guess = param_guess[0]
        slope_guess = param_guess[1]
        gamma_guess = 1/nafc
        lambda_guess = 0.001
        param_tmp = [scale_guess, slope_guess, gamma_guess, lambda_guess]
        # Check whether arguments in list are of type integer or float
        NoneType = type(None)
        for i in range(0,4):
            if isinstance(param_ests[i], (int, float, NoneType)) is False:
                raise ValueError('''User Error: Please provide numerical values or NoneType 
                                 for parameter estimates of the model.''')
            # Assign default value as needed
            if param_ests[i] is None:
                param_initial[i] = param_tmp[i]
                print('Setting parameter estimates for %s to default value!')
            elif param_ests[i] is not None:
                param_initial[i] = param_ests[i]
    elif param_ests is None:
        # Get initial estimate of scale and slope
        if batch is False:
            param_guess = _param_guess(data, nafc, sigmoid_type)
            scale_guess = param_guess[0]
            slope_guess = param_guess[1]
            gamma_guess = 1/nafc
            lambda_guess = 0.001
        elif batch is True:
            # Iterate over datasets in dictionary and generate scale estimate
            # for each set
            scale_guess = dict()
            slope_guess = dict()
            gamma_guess = dict()
            lambda_guess = dict()
            for n_datasets in data:
                param_guess = _param_guess(data[n_datasets], nafc, sigmoid_type)
                scale_guess[n_datasets] = param_guess[0]
                slope_guess[n_datasets] = param_guess[1]           
                gamma_guess[n_datasets] = 1/nafc
                lambda_guess[n_datasets] = 0.001
        # Set initial parameter estimates
        param_initial = [scale_guess, slope_guess, gamma_guess, lambda_guess]

    return param_initial
bayesfit/calc_integral.py
def calc_integral(posterior, options, metrics):
    """Compute integral of posterior surface using numerical
    integration.

    Keyword arguments:
    posterior -- 
    options -- 
    metrics --
    """
    
    # Generate list of free parameters to integrate
    list_free = []
    for i in range(0,4):
        if options['param_free'][i] is True:
            if i == 0:
                list_free.append('scale')
            elif i ==1:
                list_free.append('slope')
            elif i ==2:
                list_free.append('gamma') 
            elif i ==2:
                list_free.append('lambda')

    # Calculate step size and mass
    list_step = []
    for key in list_free:
        if metrics['Marginals_X'][key].shape[0] > 1:
            step = (metrics['Marginals_X'][key].max() - metrics['Marginals_X'][key].min()) / (len(metrics['Marginals_X'][key]) - 1)
            list_step.append(step)
    
    # Compute mass
    mass = np.product(list_step)
    
    # COmpute Integral
    integral = np.sum(posterior * mass)
    
    return integral
bayesfit/checkOptions.py
def check_options(options):
    """Check options provided ny user, or assigned by defaults
    are valid arguments.
    
    Keyword arguments:
    options -- contains all options used to fit model (dictionary)
    """
    if options['batch'] not in (True, False):
        raise ValueError('User Error: Invalid argument provided for batch type.')
    if options['sigmoid_type'] not in ('norm', 'logistic', 'gumbel', 'weibull', 'quick', 'log-quick', 'hyperbolic'):
        raise ValueError('User Error: Sigmoid type specified is not one made available by the module.')
    if options['logspace'] not in (True, False):
        raise ValueError('User/Internal Error: Invalid argument for log-spacing of x-values provided.')
    if options['nafc'] not in (1,2,3,4,5,6,7,8,9,10):
        raise ValueError('Warning: Value provided for nAFC is greater than 10, or less than zero!')
    if isinstance(options['threshold'], (int, float, complex)) is False:
        raise ValueError('Please provide a numerical argument for proportion correct estimate of threshold.')
    if not 0 <= options['threshold'] <= 1:
        raise ValueError('User Error: Value provided for threshold estimate not between zero and one!')
    if isinstance(options['nafc'], int) is False:
        raise ValueError('User Error: Please provide a integer for nAFC argument.')
    if isinstance(options['param_ests'], list) is False:
        raise ValueError('User Error: Please provide a argument of type list for the parameter estimates of the model.')
    if isinstance(options['param_free'], list) is False:
        raise ValueError('''User Error: Please provide a argument of type list for the 
                           parameter constraints of the model.''')
    if isinstance(options['density'], int) is False:
        raise ValueError('User Error: Please provide a integer for density of grid.')
bayesfit/checkPriors.py
def check_priors(options): 
    """Checks whether priors provided by user, and default
    values assigned by extract_metrics function are valid 
    assignments.

    Keyword arguments:
    options -- contains all options used to fit model (dictionary)
    """
    # Check whether distribution types provided are valid
    for keys in options['priors_definitions']:
        if options['priors_definitions'][keys] not in ('Unif', 'Norm', 'Log-Norm', 'Beta', 'Gamma'): 
            raise Exception('''One of the prior distributions defined is not 
                            a valid argument from options available!''')
    # Check whether parameters for distributions within acceptable bounds
    # Note: Does NOT check whether parameters for each distribution are valid.
    for values in options['priors_params']:
        for i in range(0,len(options['priors_params'][values])):
            if options['priors_params'][values][i] > 10000 or options['priors_params'][values][i] < -10000:
                    raise Exception('''One of the parameters that define a prior
                    distribution is greater than 10000 or less than -10000!''')
bayesfit/genGrid.py
def gen_grid(data, options):
    """Generates grid of values over which to compute the likelihood
    for each parameter to be estimated.
    
    Keyword arguments:
    data -- m x 3 numpy array
    options -- contains all options used to fit model (dictionary)
    """
    # Determine grid spacing and assignment for each parameter
    # Scale (typically alpha)
    if options['param_free'][0] is True:
        scale = np.linspace(data[:,0].min(), data[:,0].max(), options['density'])
    else:
        scale = options['param_ests'][0]
    
    # Slope (typically beta)
    if options['param_free'][1] is True:
        slope = np.linspace(options['param_ests'][1]*0.5, options['param_ests'][1]*1.5, options['density'])
    else: 
        slope = options['param_ests'][1]
    
    # Gamma (Guess rate)
    if options['param_free'][2] is True:
        lower_bound_gamma = options['param_ests'][2] - 0.25
        if lower_bound_gamma < 0:
            lower_bound_gamma = 0.0001
        upper_bound_gamma = options['param_ests'][2] + 0.25
        if upper_bound_gamma > 1:
            upper_bound_gamma = 0.9999
        gamma = np.linspace(lower_bound_gamma, upper_bound_gamma, options['density'])
    else:
        gamma = options['param_ests'][2]
        
    # Lambda (Lapse rate)
    if options['param_free'][3] is True:
        lambda_ = np.linspace(0.0001, 0.50, options['density'])
    else:
        lambda_ = options['param_ests'][3]

    # Determine meshgrid for posterior
    grid = dict()
    grid['A'], grid['B'], grid['G'], grid['L'] = np.meshgrid(scale, slope, gamma, lambda_)    
    
    # Save variables for defining and applying priors
    grid['scale'] = scale
    grid['slope'] = slope
    grid['gamma'] = gamma
    grid['lambda'] = lambda_
    
    # Output ditionary with grid
    return grid
bayesfit/applyPriors.py
def apply_priors(data, options, posterior, grid):
    """Applies prior to likelihood surface resulting in
    use of Bayesian inference to produce posterior surface.

    Keyword arguments:
    data -- m x 3 numpy array
    options -- contains all options used to fit model (dictionary)
    posterior -- posterior surface (ndarray)
    grid -- grid of values over which likelihood was computed over (ndarray)
    """
    # Apply prior for scale
    if 'scale' in options['priors_params'].keys():
        prior_scale = _define_distribution(grid['scale'], 
                                               options['priors_definitions']['scale'],
                                               options['priors_params']['scale'])
                
        # Log transform to prevent underflow 
        prior_scale = np.log(prior_scale)
        
        # Reshape to ensure broadcast works correctly
        prior_scale = prior_scale.reshape((-1,1,1,1))
        
        # Compute new posterior based on priors
        posterior = posterior + prior_scale
        
    # Apply prior for slope 
    if 'slope' in options['priors_params'].keys():
        prior_slope = _define_distribution(grid['slope'], 
                                              options['priors_definitions']['slope'],
                                              options['priors_params']['slope'])
        
        prior_slope = np.log(prior_slope)
        prior_slope = prior_slope.reshape((1,-1,1,1))
        posterior = posterior + prior_slope

    # Apply prior for gamma 
    if 'gamma' in options['priors_params'].keys():
        prior_gamma = _define_distribution(grid['gamma'], 
                                           options['priors_definitions']['gamma'],
                                           options['priors_params']['gamma'])

        prior_gamma = np.log(prior_gamma)
        prior_gamma = prior_gamma.reshape((1,1,-1,1))
        posterior = posterior + prior_gamma

    # Apply prior for lambda
    if 'lambda' in options['priors_params'].keys():
        prior_lambda = _define_distribution(grid['lambda'], 
                                            options['priors_definitions']['lambda'],
                                            options['priors_params']['lambda'])
    
        prior_lambda = np.log(prior_lambda)
        prior_lambda = prior_lambda.reshape((1,1,1,-1))
        posterior = posterior + prior_lambda
    
    return posterior
bayesfit/plot_Marginals.py
def plot_marginals(metrics):
    """Plots marginal distributions for each parameter of 
    the fitted model.
    
    Keyword arguments:
    metrics -- contain important metrics about fitted model (dictionary)
    """
    # Generate basic plot of marginal distributions
    fig, axes = plt.subplots(2, 2, 
                             subplot_kw=dict(polar=False), 
                             figsize = (7,6))
    # Scale parameter
    axes[0,0].set_xlabel('Scale')
    axes[0,1].set_xlabel('Slope')
    axes[1,0].set_xlabel('Gamma')
    axes[1,1].set_xlabel('Lambda')
    # Loop through and plot marginals that exist
    counter = 0
    idx = np.array([[0,0], [0,1], [1,0], [1,1]])
    for keys in ['scale', 'slope', 'gamma', 'lambda']:
        axes[idx[counter,0],idx[counter,1]].set_ylabel('Probability')
        if metrics['Marginals'][keys] is not np.nan and metrics['Marginals'][keys].size > 1:
            axes[idx[counter,0],idx[counter,1]].plot(metrics['Marginals_X'][keys],
                metrics['Marginals'][keys], 
                lw=3, 
                color='#5998ff')
            axes[idx[counter,0],idx[counter,1]].fill_between(metrics['Marginals_X'][keys],
                metrics['Marginals'][keys], color='#5998ff', alpha = .4)
        elif metrics['Marginals'][keys].size == 1:
            axes[idx[counter,0],idx[counter,1]].text(0.5,0.5, "None",
                horizontalalignment='center', 
                verticalalignment='center', 
                transform=axes[idx[counter,0],idx[counter,1]].transAxes)
        # Update counter
        counter += 1
    plt.tight_layout()
    plt.show()
bayesfit/plot_Posterior.py
def plot_posterior(metrics):
    """Plots posterior surface for scale and slope parameters
    collapsing across guess and lapse rates.
    
    Keyword arguments:
    metrics -- contain important metrics about fitted model (dictionary)
    """
    # If other method other than numerical integration used, 
    # raise error 
    try: 
        metrics['posterior'] 
    except NameError: 
        raise ValueError('Posterior can only be plotted for numerical integration method (i.e., grid)')
    # Compute joint marginal posterior collapsing across nusiance 
    # parameters 
    posterior = np.sum(metrics['posterior'], axis = (2,3))
    # Generate plot of posterior with scale and 
    plt.contourf(metrics['Marginals_X']['scale'], 
                 metrics['Marginals_X']['slope'], 
                 posterior)
    plt.xlabel('scale', fontsize = 16)
    plt.ylabel('slope', fontsize = 16)
    plt.colorbar()
    plt.tight_layout()
    plt.show()
bayesfit/checkData.py
def check_data(data, batch):
    """Performs a check of the data provided by the user
    to ensure to meets the formatting requirements used by
    Bayesfit

    Keyword arguments:
    data -- m x 3 numpy array
    batch -- specifies whether batch fitting data (logical)
    """
    if batch is False:
        if isinstance(data, np.ndarray) is False:
            raise Exception('Single dataset provided is not of type: np.ndarray. Please change data type provided.') 
        elif isinstance(data, np.ndarray) is True:
            if data.shape[1] != 3:
                raise Exception('Data provided does not contain the number of columns required! (i.e., [x, y, N])')
            for n in data[:,2]:
                if n < 1:
                    raise ValueError('''The number of trials for a stimulus intensity is less than one!''')
    elif batch is True:
        if isinstance(data, dict) is False:
            raise Exception('Batch dataset provided is not of type: dictionary. Please change data type provided.')
        elif isinstance(data, dict) is True:
            for keys in data:
                if data[keys].shape[1] != 3:
                    raise Exception('''A dataset provided in batch does not contain the number of columns required! 
                     (i.e., [x, y, N])''')
                for n in data[keys][:,2]:
                    if n < 1:
                        raise ValueError('''The number of trials for a stimulus intensity is less than one!''')
bayesfit/extractPriors.py
def extract_priors(options): 
    """Extract priors from user definitions using regular
    expressions, and store under new keys in options.

    Keyword arguments:
    options -- contains all options used to fit model (dictionary)
    """
    # Initialize empty dictionary to store priors 
    options['priors_definitions'] = dict()
    options['priors_params'] = dict()

    if len(options['priors']) < 4:
        raise ValueError('''Please provide list of four arguments for 
                            priors. Less than four arguments provided.''')
    elif len(options['priors']) > 4:
        raise ValueError('''Please provide list of four arguments for 
                            priors. More than four arguments provided.''')
    elif len(options['priors']) == 4: 
        NoneType = type(None)
        # Check whether arguments in list are of type integer or float
        for i in range(0,4):
            if isinstance(options['priors'][i], (str, NoneType)) is False:
                raise ValueError('''User Error: Please provide numerical values or NoneType 
                                 for priors of parameters of the model.''')
        
            if options['priors'][i] is not None:
                if i == 0:
                    # Extract prior for alpha parameter
                    options['priors_definitions']['scale'] = " ".join(re.findall("[a-zA-Z]+", 
                                                                      options['priors'][0]))
                    
                    alpha_prior_values = re.findall(r'[-+]?\d*\.\d+|\d+', options['priors'][0])
                    options['priors_params']['scale'] = [np.float(x) for x in alpha_prior_values]
                elif i == 1:
                    # Extract prior for beta parameter
                    options['priors_definitions']['slope'] = " ".join(re.findall("[a-zA-Z]+", 
                                                                     options['priors'][1]))
                    
                    beta_prior_values = re.findall(r'[-+]?\d*\.\d+|\d+', options['priors'][1])
                    options['priors_params']['slope'] = [np.float(x) for x in beta_prior_values]    
                elif i == 2:
                    # Extract prior for gamma parameter
                    options['priors_definitions']['gamma']  = " ".join(re.findall("[a-zA-Z]+", 
                                                                       options['priors'][2]))  
                    gamma_prior_values = re.findall(r'[-+]?\d*\.\d+|\d+', options['priors'][2])
                    options['priors_params']['gamma'] = [np.float(x) for x in gamma_prior_values]
                elif i == 3:
                    # Extract prior for lambda parameter
                    options['priors_definitions']['lambda']  = " ".join(re.findall("[a-zA-Z]+", 
                                                                        options['priors'][3]))
            
                    lambda_prior_values = re.findall(r'[-+]?\d*\.\d+|\d+', options['priors'][3])
                    options['priors_params']['lambda'] = [np.float(x) for x in lambda_prior_values]
            
        return options
bayesfit/extractMetrics.py
def extract_metrics_grid(data, options, metrics, posterior, grid):
    """Computes relevant metrics from fitted model.
    
    Keyword arguments:
    data -- m x 3 numpy array
    options -- contains all options used to fit model (dictionary)
    metrics -- contain important metrics about fitted model (dictionary)
    posterior -- posterior surface (ndarray)
    grid -- grid of values over which likelihood was computed over (ndarray) 
    """
    
    #################################################################
    #  COMPUTE MARGINAL POSTERIOR DISTRIBUTIONS
    #################################################################
    
    # Compute marginal posterior distributions
    metrics['Marginals'] = dict()
    metrics['Marginals']['scale'] = _compute_marginal(posterior, (1,2,3))
    metrics['Marginals']['slope'] = _compute_marginal(posterior, (0,2,3))
    metrics['Marginals']['gamma'] = _compute_marginal(posterior, (0,1,3))
    metrics['Marginals']['lambda'] = _compute_marginal(posterior, (0,1,2))
    
    #################################################################
    #  GET MAP ESTIMATE FROM POSTERIOR 
    #################################################################
    
    # Get index of MAP estimate
    map_index = np.where(posterior == np.max(posterior.ravel()))
    
    # Form vector for MAP estimate of each parameter if free parameter
    metrics['MAP'] = np.zeros(4)
    for i in range(0,4):
        if options['param_free'][i] is True:
            if i == 0:
                metrics['MAP'][i] = grid['A'][map_index]
            elif i == 1: 
                metrics['MAP'][i] = grid['B'][map_index]
            elif i == 2: 
                metrics['MAP'][i] = grid['G'][map_index]   
            elif i == 3: 
                metrics['MAP'][i] = grid['L'][map_index]
        elif options['param_free'][i] is False:
            metrics['MAP'][i] = np.nan
    
    #################################################################
    #  GET MEAN ESTIMATE FROM MARGINALS 
    #################################################################
    
    # Form vector for MAP estimate of each parameter if free parameter
    metrics['Mean'] = np.zeros(4)
    for i in range(0,4):
        if options['param_free'][i] is True:
            if i == 0:
                metrics['Mean'][i] = np.sum(metrics['Marginals']['scale']*grid['scale']) 
            elif i == 1:                 
                metrics['Mean'][i] = np.sum(metrics['Marginals']['slope']*grid['slope']) 
            elif i == 2:                 
                metrics['Mean'][i] = np.sum(metrics['Marginals']['gamma']*grid['gamma']) 
            elif i == 3:                 
                metrics['Mean'][i] = np.sum(metrics['Marginals']['lambda']*grid['lambda']) 
        elif options['param_free'][i] is False:
            metrics['Mean'][i] = np.nan
    
    #################################################################
    #  CALCULATE SD FOR EACH PARAMETER
    #################################################################
    
    # Form vector for MAP estimate of each parameter if free parameter
    metrics['SD'] = np.zeros(4)
    for i in range(0,4):
        if options['param_free'][i] is True:
            if i == 0:
                metrics['SD'][i] = _std_marginal(grid['scale'], 
                                                   metrics['Marginals']['scale'], 
                                                   metrics['Mean'][0])
            elif i == 1:                 
                metrics['SD'][i] = _std_marginal(grid['slope'], 
                                                   metrics['Marginals']['slope'], 
                                                   metrics['Mean'][1])
            elif i == 2:                 
                metrics['SD'][i] = _std_marginal(grid['gamma'], 
                                                   metrics['Marginals']['gamma'], 
                                                   metrics['Mean'][2])
            elif i == 3:                 
                metrics['SD'][i] = _std_marginal(grid['lambda'], 
                                                   metrics['Marginals']['lambda'], 
                                                   metrics['Mean'][3])
        elif options['param_free'][i] is False:
            metrics['SD'][i] = np.nan

    #################################################################
    #  CALCULATE BOUNDS FOR HIGHEST POSTERIOR DENSITY
    #################################################################
    
    # Form vector for MAP estimate of each parameter if free parameter
    metrics['BCI'] = np.zeros(shape=(2,4))
    for i in range(0,4):
        if options['param_free'][i] is True:
            if i == 0:
                metrics['BCI'][0,i], metrics['BCI'][1,i] = bci(metrics['Marginals']['scale'], 0.05)
            elif i == 1:                 
                metrics['BCI'][0,i], metrics['BCI'][1,i] = bci(metrics['Marginals']['slope'], 0.05)
            elif i == 2:                 
                metrics['BCI'][0,i], metrics['BCI'][1,i] = bci(metrics['Marginals']['gamma'], 0.05)
            elif i == 3:                 
                metrics['BCI'][0,i], metrics['BCI'][1,i] = bci(metrics['Marginals']['lambda'], 0.05)
        elif options['param_free'][i] is False:
            metrics['BCI'][0,i] = np.nan
            metrics['BCI'][1,i] = np.nan
            
    #################################################################
    #  CALCULATE DEVIANCE
    #################################################################
    metrics['deviance'] = deviance(data, options, metrics, 'MAP')
    
    #################################################################
    #  CALCULATE DEVIANCE
    #################################################################
    metrics['pearsons_chisqr'] = pearson_chisqr(data, options, metrics, 'MAP')
    
    return metrics
bayesfit/plot_psyFcn.py
def plot_psyfcn(data, 
                options,
                metrics,
                log_xscale = False, 
                scale_pnts = True,
                estimate_type = 'MAP'):
    """Plots psychometric function from fitted model.
    
    Keyword arguments:
    data -- m x 3 numpy array
    options -- contains all options used to fit model (dictionary)
    metrics -- contain important metrics about fitted model (dictionary)
    log_xscale -- log-transform x-scale (logical)
    scale_pnts -- scale size of points by N-trial number (logical)
    estimate_type -- determines which metric to use for parameters of model (string)
    """
    
    if estimate_type not in ('MAP', 'Mean'):
        raise ValueError('Please provide appropriate argument for estimate type (e.g., MAP, Mean)')
    
    # Determine which values to use for vector of parameters 
    param_guess = np.zeros(4)
    counter = 0
    for keys in options['param_free']:
        if keys is True:
            param_guess[counter] = metrics[estimate_type][counter]
        elif keys is False: 
            param_guess[counter] = options['param_ests'][counter]
        counter += 1

    # Generate copy of data
    data_copy = data
    # Check whether x-values need to be log-spaced
    data_copy, logspace = _check_logspace(data, 
                                          options['logspace'], 
                                          options['sigmoid_type'],
                                          options['batch'])
    
    fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(5, 5))
    
    # Generate smooth curve from fitted function
    x_max = data_copy[:, 0].max()
    x_min = data_copy[:, 0].min()
    x_est = np.linspace(x_min, x_max, 1000)
    
    y_pred = _psyfunction(x_est, 
                          param_guess[0],
                          param_guess[1],
                          param_guess[2],
                          param_guess[3],
                          options['sigmoid_type'])

    if log_xscale is True:
        ax.set_xscale('log')
    ax.plot([x_min, metrics['threshold']],
            [options['threshold'], options['threshold']],
            color='black',
            linestyle='dotted',
            linewidth=2,
            zorder=1)
    ax.plot([metrics['threshold'], metrics['threshold']], 
            [0, options['threshold']],
            color='black',
            linestyle='dotted',
            linewidth=2,
            zorder=1)
    ax.set_ylim(-0.05, 1.05)
    ax.axhline(y=param_guess[2], color='r', linestyle='dashed', linewidth=2, zorder=1)
    # Plot remainder of curve            
    if scale_pnts is True:
        for i in range(data[:,0].shape[0]): 
            ax.scatter(data_copy[i,0], 
                       data_copy[i,1]/data_copy[i,2], 
                       color='#5998ff', 
                       s=data_copy[i,2]*2, 
                       alpha=0.5, 
                       zorder=5, 
                       marker='o')
    elif scale_pnts is False: 
        ax.scatter(data_copy[:,0], 
                   data_copy[:,1]/data_copy[:,2], 
                   color='#5998ff', 
                   s=135, 
                   alpha=0.5, 
                   zorder=5, 
                   marker='o')    
    ax.plot(x_est, y_pred, linestyle='-', color='black', linewidth=2, alpha=0.85, zorder=10)
    ax.set_xlabel('Stimulus Intensity', fontsize=16)
    ax.set_ylabel('Proportion correct', fontsize=16)
    ax.xaxis.set_tick_params(labelsize=13)
    ax.yaxis.set_tick_params(labelsize=13)
    plt.tight_layout()
    plt.show()
bayesfit/gridLikelihood.py
def grid_likelihood(data, options, grid):
    """Computes likelihood across of parameters defined by grid
    given the data. 
    
    Keyword arguments:
    data -- m x 3 numpy array
    options -- contains all options used to fit model (dictionary)
    grid -- grid of values over which to compute likelihood over (ndarray) 
    """
    # Initialize posterior
    likelihood = np.zeros(shape = grid['A'].shape)
    
    # Compute likelihood
    for i in range(0,len(data[:,0])):
        y = _psyfunction(data[i,0], 
                         grid['A'], 
                         grid['B'], 
                         grid['G'], 
                         grid['L'], 
                         options['sigmoid_type'])*.99 + 0.005
                         
        likelihood += data[i,1]*np.log(y) + (data[i,2] - data[i,1])*np.log(1-y)
        
    return likelihood
bayesfit/checkParams.py
def check_constraints(param_constraints):
    """Checks whether user provided list specifying which parameters 
    are to be free versus fixed is in proper format, and assigns
    default values where necessary.
    
    Keyword arguments:
    param_constraints -- specifies which parameters to estimate (list)
    """
    if param_constraints is None:
        # If no parameter constraints provided, set default values to
        # [alpha = True, beta = True, gamma = False, lambda = True]
        param_constraints = [True, True, False, False]
    elif param_constraints is not None:
        # Check whether the variable for the parameter constraints provided is of type list
        if isinstance(param_constraints, list) is False:
            raise ValueError('''User Error: Please provide a argument of type list indicating
            which parameters of the model are free (i.e., TRUE) versus fixed (i.e., FALSE).''')
        # Check whether arguments in list are of type boolean
        for i in param_constraints:
            if isinstance(i, bool) is False:
                raise ValueError('''User Error: Please provide boolean arguments for parameter '
                                constraints (i.e., free vs fixed) of the model.''')
            # Check number of arguments provided and set the remaining arguments to default values.
            if len(param_constraints) < 2:
                raise ValueError('''User Error: Please provide at least two constraints (scale; slope)
                                for parameters of the model.''')
            elif len(param_constraints) == 2:
                print('Setting parameter constraints for gamma and lambda to default values (i.e., fixed)!')
                param_constraints.append(False)
                param_constraints.append(True)
            elif len(param_constraints) == 3:
                print('Setting parameter constraints for lambda to default value (i.e., fixed)!')
                param_constraints.append(True)
            elif len(param_constraints) > 4:
                raise ValueError('''User Error: More than 4 constraints for parameters of the model provided!''')
    return param_constraints
bayesfit/get_Threshold.py
def get_threshold(data, options, metrics, threshold_pc, estimate_type = 'MAP'):
    """Check options provided ny user, or assigned by defaults
    are valid arguments.
    
    Keyword arguments:
    data -- m x 3 numpy array
    metrics -- contain important metrics about fitted model (dictionary)
    options -- contains all options used to fit model (dictionary)
    threshold_pc -- proportion correct to define threshold (float)
    estimate_type -- determines which metric to use for parameters of model (string)
    """
    # Extract threshold at specified level of propertion correct
    # using numerical approximation 
    def _find_nearest(y, x, value):
        idx = (np.abs(y-value)).argmin()
        return x[idx]
    
    x_est = np.linspace(data[:, 0].min(), data[:, 0].max(), int(1e4))
    
    if estimate_type not in ('MAP', 'Mean'):
        raise ValueError('Please provide appropriate argument for estimate type (e.g., MAP, Mean)')
        
    # Determine which values to use for vector of parameters 
    param_guess = np.zeros(4)
    counter = 0
    for keys in options['param_free']:
        if keys is True:
            param_guess[counter] = metrics[estimate_type][counter]
        elif keys is False: 
            param_guess[counter] = options['param_ests'][counter]
        counter += 1
    
    y_pred = _psyfunction(x_est, 
                          param_guess[0],
                          param_guess[1],
                          param_guess[2],
                          param_guess[3],
                          options['sigmoid_type'])
    
    threshold = _find_nearest(y_pred, x_est, threshold_pc)
        
    return threshold
bayesfit/plot_Priors.py
def plot_priors(options, metrics):
    """Plots prior distribution defined by user when fitting model.
    
    Keyword arguments:
    options -- contains all options used to fit model (dictionary)
    metrics -- contain important metrics about fitted model (dictionary)
    """
    # Generate basic plot of marginal distributions
    fig, axes = plt.subplots(2, 2, 
                             subplot_kw=dict(polar=False), 
                             figsize = (7,6))
    # Label axes
    axes[0,0].set_xlabel('Scale')
    axes[0,1].set_xlabel('Slope')
    axes[1,0].set_xlabel('Gamma')
    axes[1,1].set_xlabel('Lambda')
    axes[0,0].set_ylabel('Probability')
    axes[0,1].set_ylabel('Probability')
    axes[1,0].set_ylabel('Probability')
    axes[1,1].set_ylabel('Probability')
    # Scale parameter
    if 'scale' in options['priors_params'].keys():
        prior = _define_distribution(metrics['Marginals_X']['scale'], 
                                       options['priors_definitions']['scale'],
                                       options['priors_params']['scale'])
        axes[0,0].plot(metrics['Marginals_X']['scale'],
            prior, 
            lw=3, 
            color='#5998ff')
        axes[0,0].fill_between(metrics['Marginals_X']['scale'],
            prior, color='#5998ff', alpha = .4)
    else:
        axes[0,0].text(0.5,0.5, "None",
            horizontalalignment='center', 
            verticalalignment='center', 
            transform=axes[0,0].transAxes)       
    # Slope parameter
    if 'slope' in options['priors_params'].keys():
        prior = _define_distribution(metrics['Marginals_X']['slope'], 
                                       options['priors_definitions']['slope'],
                                       options['priors_params']['slope'])
        axes[0,1].plot(metrics['Marginals_X']['slope'],
            prior, 
            lw=3, 
            color='#5998ff')
        axes[0,1].fill_between(metrics['Marginals_X']['slope'],
            prior, color='#5998ff', alpha = .4)
    else:
        axes[0,1].text(0.5,0.5, "None",
            horizontalalignment='center', 
            verticalalignment='center', 
            transform=axes[0,1].transAxes)     
    # Gamma 
    if 'gamma' in options['priors_params'].keys():
        prior = _define_distribution(metrics['Marginals_X']['gamma'], 
                                       options['priors_definitions']['gamma'],
                                       options['priors_params']['gamma'])
        axes[1,0].plot(metrics['Marginals_X']['gamma'],
            prior, 
            lw=3, 
            color='#5998ff')
        axes[1,0].fill_between(metrics['Marginals_X']['gamma'],
            prior, color='#5998ff', alpha = .4)
    else:
        axes[1,0].text(0.5,0.5, "None",
            horizontalalignment='center', 
            verticalalignment='center', 
            transform=axes[1,0].transAxes)     
    # Lambda
    if 'lambda' in options['priors_params'].keys():
        prior = _define_distribution(metrics['Marginals_X']['lambda'], 
                                       options['priors_definitions']['lambda'],
                                       options['priors_params']['lambda'])
        axes[1,1].plot(metrics['Marginals_X']['lambda'],
            prior, 
            lw=3, 
            color='#5998ff')
        axes[1,1].fill_between(metrics['Marginals_X']['lambda'],
            prior, color='#5998ff', alpha = .4)
    else:
        axes[1,1].text(0.5,0.5, "None",
            horizontalalignment='center', 
            verticalalignment='center', 
            transform=axes[1,1].transAxes)     
    
    plt.tight_layout()
    plt.show()
bayesfit/checkLogspace.py
def check_logspace(data, logspace, sigmoid_type, batch):
    """Performs a check of whether data need to be logspaced
    based on user input via logspace option and sigmoid type
    chosen (i.e, Weibull).

    Keyword arguments:
    data -- m x 3 numpy array
    logspace -- specifies whether to logspace data (logical)
    sigmoid_type -- sigmoid type fit to data (string)
    """
    data_copy = data
    # Ensure argument provided for logspace is valid
    if logspace not in (None, True, False):
        raise Exception('User/Internal Error: Invalid argument for log-spacing of x-values provided.')
    # Run through options and logspace x-values if needed
    if logspace is None:
        if sigmoid_type in ('weibull', 'log-quick'):
            if batch is False:
                logspace = True
                if data[:, 0].min() < 0:
                    raise Exception('Cannot convert negative data points to log-spaced values!')
                # Convert x-values in data
                data_copy = convert_logspace(data)
            elif batch is True:
                logspace = True
                for keys in data:
                    if data[keys][:, 0].min() < 0:
                        raise Exception('Cannot convert negative data points to log-spaced values!')
                    # Convert x-values in data
                    data_copy[keys] = convert_logspace(data[keys])
        else:
            logspace = False
            data_copy = data
    elif logspace is True:
        if batch is False:
            logspace = True
            if data[:, 0].min() < 0:
                raise Exception('Cannot convert negative data points to log-spaced values!')
            # Convert x-values in data
            data_copy = convert_logspace(data)
        elif batch is True:
            logspace = True
            for keys in data:
                if data[keys][:, 0].min() < 0:
                    raise Exception('Cannot convert negative data points to log-spaced values!')
                # Convert x-values in data
                data_copy[keys] = convert_logspace(data[keys])
    elif logspace is False:
        data_copy = data
    return data_copy, logspace


Question: import numpy as np
import re
import os 
import warnings
import matplotlib
import warnings
from .checkData import check_data as _check_data
from .checkLogspace import check_logspace as _check_logspace
from .checkParams import check_params as _check_params
from .checkParams import check_constraints as _check_constraints
from .checkOptions import check_options as _check_options 
from .checkPriors import check_priors as _check_priors
from .extractPriors import extract_priors as _extract_priors
from .psyFunction import psyfunction as _psyfunction
from .genGrid import gen_grid as _gen_grid
from .gridLikelihood import grid_likelihood as _grid_likelihood
from .applyPriors import apply_priors as _apply_priors
from .calc_integral import calc_integral as _calc_integral
from .extractMetrics import extract_metrics_grid as _extract_metrics_grid 
from .get_Threshold import get_threshold
from .plot_psyFcn import plot_psyfcn
from .plot_Posterior import plot_posterior
from .plot_Marginals import plot_marginals
from .plot_Priors import plot_priors

#################################################################
#  IMPORT MODULES 
#################################################################
# Core modules 
with warnings.catch_warnings():
    warnings.simplefilter("ignore", UserWarning)
    if os.environ.get('DISPLAY', '') == '':
       print('no display found. Using non-interactive Agg backend')
       matplotlib.use('Agg')
# Functions designed to check ser input
# Core functions for computing likelihood/posterior
# Functions for extracting relevant metrics from posterior
# Plotting functions


#################################################################
#  BAYESFIT CORE FUNCTION THAT ACCEPTS ARGUMENTS FROM USE 
#################################################################
def fitmodel(data,
             batch=False,
             logspace=None,
             nafc=2,
             sigmoid_type='logistic',
             param_ests=None,
             param_free=None,
             priors=None,
             threshold=0.75,
             density=100,
             ):
    """Main function used to fit psychometric functions 
    to data provided by the user.
    
    Keyword arguments:
    data -- m x 3 numpy array
    batch -- specifies whether batch fitting data (logical)
    logspace -- specifies whether to logspace data (logical)
    nafc -- N-alternative forced choice experiment (integer)
    sigmoid_type -- sigmoid type fit to data (string)
    param_ests -- estimates for each parameter (list)
    param_free -- specifies which parameters to estimate (list)
    priors -- define distributions to use as priors (list)
    threshold -- proportion correct to define threshold (float)
    density -- defines grid size across free parameters (int)
    """
    
    # Check that user provided data meets structure expected
    _check_data(data, batch)

    # Check whether x-values need to be log-spaced
    data_copy, logspace = _check_logspace(data, logspace, sigmoid_type, batch)

    # Check that parameter estimates/constraints are reasonable
    param_ests = _check_params(data_copy, param_ests, nafc, batch, sigmoid_type)

    # Check parameter constraints provided
    param_free = _check_constraints(param_free)

    # Save options used to fit functions in dictionary for reference
    options = dict()
    options['batch'] = batch
    options['logspace'] = logspace
    options['nafc'] = nafc
    options['sigmoid_type'] = sigmoid_type
    options['param_ests'] = param_ests
    options['param_free'] = param_free
    options['priors'] = priors
    options['threshold'] = threshold
    options['density'] = density

    if options['priors'] is not None:
        # Extract priors defined by user 
        options = _extract_priors(options)
        # Check priors defined by user 
        _check_priors(options)

    # Check that all options provided by user or defaults assigned are acceptable
    _check_options(options)

    # Fit models to data
    if batch is True:
        # Raise warning to inform user that only select parameters will be 
        # saved.
        warnings.warn('''\nWhen Batch mode is TRUE, Bayesfit outputs only select metrics to prevent memory issues that can arise from saving posterior and marginal distributions for a large nuber of datasets.''')
        tmp_metrics = dict()
        metrics = dict()
        for keys in data_copy:
            tmp_options = options.copy()
            tmp_options['param_ests'] = list()
            for i in range(0,4):
                tmp_options['param_ests'].append(options['param_ests'][i][keys])
            # Fit model
            tmp_metrics = _fit_Grid(data_copy[keys], tmp_options)
            tmp_metrics['threshold'] = get_threshold(data_copy[keys], tmp_options, tmp_metrics, tmp_options['threshold'])
            # Record only minimal values about fit to 
            # ensure memory issues are not encountered
            del tmp_metrics['posterior'], tmp_metrics['Marginals'], tmp_metrics['Marginals_X']
            metrics[keys] = tmp_metrics
    elif batch is False:
        metrics = _fit_Grid(data_copy, options)
        metrics['threshold'] = get_threshold(data_copy, options, metrics, options['threshold'])
    # Return tuple of objects
    return metrics, options


#################################################################
#  FITTING FUNCTION USING NUMERICAL INTEGRATION
#################################################################
def _fit_Grid(data, options):
    """Function used to run scripts associated with using 
    grid approximation.
    
    Keyword arguments:
    data -- m x 3 numpy array
    options -- contains all options used to fit model (dictionary)
    """    
    # Initialize metrics dictionary
    metrics = dict()
    
    # Generate grid of values for parameters (force to be equal length)

Answer:     grid = _gen_grid(data, options)