<NME> nested_logit.py
<BEF> ADDFILE
<MSG> Add nested logit and mixed logit model files.

<DFF> @@ -0,0 +1,760 @@
+# -*- coding: utf-8 -*-
+"""
+Created on Sun Jul  3 11:26:33 2016
+
+@module:    nested_logit.py
+@name:      Nested Logit Model
+@author:    Timothy Brathwaite
+@summary:   Contains functions necessary for estimating nested logit models
+            (with the help of the "base_multinomial_cm.py" file).
+"""
+import time
+import sys
+import numpy as np
+from scipy.optimize import minimize
+
+import nested_choice_calcs as nc
+import base_multinomial_cm_v2 as base_mcm
+
+# Alias necessary functions from the base multinomial choice model module
+general_log_likelihood = nc.calc_nested_log_likelihood
+general_gradient = nc.calc_nested_gradient
+general_calc_probabilities = nc.calc_nested_probs
+bhhh_approx = nc.calc_bhhh_hessian_approximation
+
+
+# Define a functionto split the combined parameter array into nest coefficients
+# and index coefficients.
+def split_params(all_params, rows_to_nests):
+    """
+    Parameters
+    ----------
+    all_params : 1D ndarray.
+        Should contain all of the parameters being estimated (i.e. all the
+        nest coefficients and all of the index coefficients). All elements
+        should be ints, floats, or longs.
+    rows_to_nests : 2D scipy sparse array.
+        There should be one row per observation per available alternative and
+        one column per nest. This matrix maps the rows of the design matrix to
+        the unique nests (on the columns).
+
+    Returns
+    -------
+    orig_nest_coefs : 1D ndarray.
+        The nest coefficients being used for estimation. Note that these values
+        are the logit of the inverse of the scale parameters for each lower
+        level nest.
+    index_coefs : 1D ndarray.
+        The coefficients of the index being used for this nested logit model.
+    """
+    # Split the array of all coefficients
+    num_nests = rows_to_nests.shape[1]
+    orig_nest_coefs = all_params[:num_nests]
+    index_coefs = all_params[num_nests:]
+    return orig_nest_coefs, index_coefs
+
+
+# Define function for calculating the log likelihood
+def convenient_nested_log_likelihood(all_coefs,
+                                     design,
+                                     rows_to_obs,
+                                     rows_to_nests,
+                                     choice_vec,
+                                     ridge=None):
+    """
+    Parameters
+    ----------
+    all_coefs : 1D ndarray.
+        Should contain all of the parameters being estimated (i.e. all the
+        nest coefficients and all of the index coefficients). All elements
+        should be ints, floats, or longs.
+    design : 2D ndarray.
+        There should be one row per observation per available alternative.
+        There should be one column per utility coefficient being estimated.
+        All elements should be ints, floats, or longs.
+    rows_to_obs : 2D scipy sparse array.
+        There should be one row per observation per available alternative and
+        one column per observation. This matrix maps the rows of the design
+        matrix to the unique observations (on the columns).
+    rows_to_nests : 2D scipy sparse array.
+        There should be one row per observation per available alternative and
+        one column per nest. This matrix maps the rows of the design matrix to
+        the unique nests (on the columns).
+    choice_vec : 1D ndarray.
+        All elements should be either ones or zeros. There should be one row
+        per observation per available alternative for the given observation.
+        Elements denote the alternative which is chosen by the given
+        observation with a `1` and a zero otherwise.
+    ridge : int, float, long, or None, optional.
+        Determines whether or not ridge regression is performed. If a scalar is
+        passed, then that scalar determines the ridge penalty for the
+        optimization. Default `== None`.
+
+    Returns
+    -------
+    float. The log-likelihood of the nested logit model.
+    """
+    # Split the array of all coefficients
+    orig_nest_coefs, index_coefs = split_params(all_coefs, rows_to_nests)
+    # Get the
+    nest_coefs = nc.naturalize_nest_coefs(orig_nest_coefs)
+
+    return general_log_likelihood(nest_coefs,
+                                  index_coefs,
+                                  design,
+                                  rows_to_obs,
+                                  rows_to_nests,
+                                  choice_vec,
+                                  ridge=ridge)
+
+
+# Create a function for calculating the gradient within a
+# scipy.optimize.minimize estimation routine
+def convenient_nested_gradient(all_coefs,
+                               design,
+                               choice_vec,
+                               rows_to_obs,
+                               rows_to_nests,
+                               ridge=None):
+    """
+    Parameters
+    ----------
+    all_coefs : 1D ndarray.
+        Should contain all of the parameters being estimated (i.e. all the
+        nest coefficients and all of the index coefficients). All elements
+        should be ints, floats, or longs.
+    design : 2D ndarray.
+        There should be one row per observation per available alternative.
+        There should be one column per utility coefficient being estimated.
+        All elements should be ints, floats, or longs.
+    rows_to_obs : 2D scipy sparse array.
+        There should be one row per observation per available alternative and
+        one column per observation. This matrix maps the rows of the design
+        matrix to the unique observations (on the columns).
+    rows_to_nests : 2D scipy sparse array.
+        There should be one row per observation per available alternative and
+        one column per nest. This matrix maps the rows of the design matrix to
+        the unique nests (on the columns).
+    ridge : int, float, long, or None, optional.
+        Determines whether or not ridge regression is performed. If a scalar is
+        passed, then that scalar determines the ridge penalty for the
+        optimization. Default `== None`.
+
+    Returns
+    -------
+    1D ndarray. The gradient of the nested logit's log-likelihood with respect
+    to `all_coefs`.
+    """
+    # Split the array of all coefficients
+    orig_nest_coefs, index_coefs = split_params(all_coefs, rows_to_nests)
+
+    return general_gradient(orig_nest_coefs,
+                            index_coefs,
+                            design,
+                            choice_vec,
+                            rows_to_obs,
+                            rows_to_nests,
+                            ridge=ridge)
+
+
+# Create an objective function for a
+# scipy.optimize.minimize estimation routine
+def calc_neg_nested_log_likelihood_and_gradient(all_coefs,
+                                                design,
+                                                choice_vec,
+                                                rows_to_obs,
+                                                rows_to_nests,
+                                                constrained_pos,
+                                                ridge):
+    """
+    Parameters
+    ----------
+    all_coefs : 1D ndarray.
+        Should contain all of the parameters being estimated (i.e. all the
+        nest coefficients and all of the index coefficients). All elements
+        should be ints, floats, or longs.
+    design : 2D ndarray.
+        There should be one row per observation per available alternative.
+        There should be one column per utility coefficient being estimated.
+        All elements should be ints, floats, or longs.
+    choice_vec : 1D ndarray.
+        All elements should be either ones or zeros. There should be one row
+        per observation per available alternative for the given observation.
+        Elements denote the alternative which is chosen by the given
+        observation with a `1` and a zero otherwise.
+    rows_to_obs : 2D scipy sparse array.
+        There should be one row per observation per available alternative and
+        one column per observation. This matrix maps the rows of the design
+        matrix to the unique observations (on the columns).
+    rows_to_nests : 2D scipy sparse array.
+        There should be one row per observation per available alternative and
+        one column per nest. This matrix maps the rows of the design matrix to
+        the unique nests (on the columns).
+    constrained_pos : list, or None.
+        Denotes the positions of the array of estimated parameters that are not
+        to change from their initial values. If a list is passed, the elements
+        are to be integers where no such integer is is greater than
+        `init_values.size`.
+    ridge : int, float, long, or None.
+        Determines whether or not ridge regression is performed. If a scalar is
+        passed, then that scalar determines the ridge penalty for the
+        optimization.
+
+    Returns
+    -------
+    neg_log_likelihood : float.
+        The negative of the log-likelihood for the nested logit model given
+        the passed arguments.
+    neg_gradient : 1D ndarray.
+        The negative of the gradient of the log-likelilhood of the nested logit
+        model given the passed arguments.
+    """
+    log_likelihood = convenient_nested_log_likelihood(all_coefs,
+                                                      design,
+                                                      rows_to_obs,
+                                                      rows_to_nests,
+                                                      choice_vec,
+                                                      ridge=ridge)
+
+    gradient = convenient_nested_gradient(all_coefs,
+                                          design,
+                                          choice_vec,
+                                          rows_to_obs,
+                                          rows_to_nests,
+                                          ridge=ridge)
+    if constrained_pos is not None:
+        gradient[constrained_pos] = 0
+
+    return -1 * log_likelihood, -1 * gradient
+
+
+def _estimate(init_values,
+              design_matrix,
+              alt_id_vector,
+              choice_vector,
+              rows_to_obs,
+              rows_to_alts,
+              rows_to_nests,
+              chosen_row_to_obs,
+              constrained_pos,
+              print_results=True,
+              method='BFGS',
+              loss_tol=1e-06,
+              gradient_tol=1e-06,
+              maxiter=1000,
+              ridge=False,
+              **kwargs):
+    """
+    Parameters
+    ----------
+    init_values : 1D ndarray.
+        Should contain the initial values to start the optimizatin process
+        with. There should be one value for each nest parameter and utility
+        coefficient in the model.
+    design_matrix : 2D ndarray.
+        There should be one row per observation per available alternative.
+        There should be one column per utility coefficient being estimated.
+        All elements should be ints, floats, or longs.
+    alt_id_vector : 1D ndarray.
+        All elements should be ints. There should be one row per observation
+        per available alternative for the given observation. Elements denote
+        the alternative corresponding to the given row of the design matrix.
+    choice_vector : 1D ndarray.
+        All elements should be either ones or zeros. There should be one row
+        per observation per available alternative for the given observation.
+        Elements denote the alternative which is chosen by the given
+        observation with a `1` and a zero otherwise.
+    rows_to_obs : 2D scipy sparse array.
+        There should be one row per observation per available alternative and
+        one column per observation. This matrix maps the rows of the design
+        matrix to the unique observations (on the columns).
+    rows_to_alts : 2D ndarray.
+        There should be one row per observation per available alternative and
+        one column per possible alternative. This matrix maps the rows of the
+        design matrix to the possible alternatives for this dataset. Included
+        simply for conformity with other models.
+    rows_to_nests : 2D scipy sparse array.
+        There should be one row per observation per available alternative and
+        one column per nest. This matrix maps the rows of the design matrix to
+        the unique nests (on the columns).
+    chosen_row_to_obs : 2D scipy sparse array.
+        There should be one row per observation per available alternative and
+        one column per observation. This matrix indicates, for each observation
+        (on the columns), which rows of the design matrix were the realized
+        outcome.
+    constrained_pos : list, or None, optional.
+        Denotes the positions of the array of estimated parameters that are not
+        to change from their initial values. If a list is passed, the elements
+        are to be integers where no such integer is is greater than
+        `init_values.size`.
+    print_res : bool, optional.
+        Determines whether the timing and initial and final log likelihood
+        results will be printed as they they are determined. Default = True.
+    method : string, optional.
+        Should be a valid string which can be passed to
+        `scipy.optimize.minimize`. Determines the optimization algorithm which
+        is used for this problem. Default == 'BFGS'.
+    loss_tol : float, optional.
+        Determines the tolerance on the difference in objective function values
+        from one iteration to the next that is needed to determine convergence.
+        Default `== 1e-06`.
+    gradient_tol : float, optional.
+        Determines the tolerance on the difference in gradient values from one
+        iteration to the next which is needed to determine convergence.
+        Default `== 1e-06`.
+    maxiter : int, optional.
+        Determines the maximum number of iterations used by the optimizer.
+        Default `== 1000`.
+    ridge : int, float, long, or None, optional.
+        Determines whether or not ridge regression is performed. If a scalar is
+        passed, then that scalar determines the ridge penalty for the
+        optimization. Default `== None`.
+
+    Returns
+    -------
+    results : dict.
+        Contains the estimation results dictionary from scipy.optimize.minimize
+        as well as various calculated quantities. Custom added keys will be:
+        `["utility_coefs", "intercept_params", "shape_params", "nest_params",
+          "log_likelihood_null", "rho_squared", "rho_bar_squared",
+          "final_gradient", "final_hessian", "fisher_info", "constrained_pos",
+          "final_log_likelihood", "chosen_probs", "long_probs, "residuals",
+          "ind_chi_squareds"]`
+    """
+    ##########
+    # Make sure we have the correct dimensions for the initial parameter values
+    ##########
+    # Figure out how many shape parameters we should have and how many index
+    # coefficients we should have
+    num_nests = rows_to_nests.shape[1]
+    num_index_coefs = design_matrix.shape[1]
+
+    try:
+        assumed_param_dimensions = num_index_coefs + num_nests
+        assert init_values.shape[0] == assumed_param_dimensions
+    except AssertionError as e:
+        print("The initial values are of the wrong dimension")
+        print("It should be of dimension {}".format(assumed_param_dimensions))
+        print("But instead it has dimension {}".format(init_values.shape[0]))
+        raise e
+
+    ##########
+    # Check other function arguments for 'correctness'
+    ##########
+    # Make sure the ridge regression parameter is None or a real scalar
+    try:
+        assert ridge is None or isinstance(ridge, (int, float, long))
+    except AssertionError as e:
+        print("ridge should be None or an int, float, or long.")
+        print("The passed value of ridge had type: {}".format(type(ridge)))
+        raise e
+
+    ##########
+    # Begin the model estimation process.
+    ##########
+    # Isolate the initial shape, intercept, and beta parameters.
+    init_nest_params, init_betas = split_params(init_values, rows_to_nests)
+
+    # Get the log-likelihood at zero and the initial log likelihood
+    # Note, we use intercept_params=None since this will cause the function
+    # to think there are no intercepts being added to the transformation
+    # vector, which is the same as adding zero to the transformation vector
+    basic_nest_params = np.ones(init_nest_params.shape[0])
+    zero_beta_vals = np.zeros(init_betas.shape[0])
+    log_likelihood_at_zero = general_log_likelihood(basic_nest_params,
+                                                    zero_beta_vals,
+                                                    design_matrix,
+                                                    rows_to_obs,
+                                                    rows_to_nests,
+                                                    choice_vector,
+                                                    ridge=ridge)
+
+    # Note the clog-log model has no shape parameters so there is no such
+    # parameter included in the log-likelihood calculations below or above
+    natural_init_nest_params = nc.naturalize_nest_coefs(init_nest_params)
+    initial_log_likelihood = general_log_likelihood(natural_init_nest_params,
+                                                    init_betas,
+                                                    design_matrix,
+                                                    rows_to_obs,
+                                                    rows_to_nests,
+                                                    choice_vector,
+                                                    ridge=ridge)
+    ##########
+    # Print initial model conditions
+    # i.e., log-likelihoods at 'zero' and at initial values
+    ##########
+    if print_results:
+        # Print the log-likelihood at zero
+        print("Log-likelihood at zero: {:,.4f}".format(log_likelihood_at_zero))
+
+        # Print the log-likelihood at the starting values
+        print("Initial Log-likelihood: {:,.4f}".format(initial_log_likelihood))
+        sys.stdout.flush()
+
+    ##########
+    # Perform the minimization to estimate the nested logit model
+    ##########
+    # Start timing the estimation process
+    start_time = time.time()
+
+    results = minimize(calc_neg_nested_log_likelihood_and_gradient,
+                       init_values,
+                       args=(design_matrix,
+                             choice_vector,
+                             rows_to_obs,
+                             rows_to_nests,
+                             constrained_pos,
+                             ridge),
+                       method=method,
+                       jac=True,
+                       tol=loss_tol,
+                       options={'gtol': gradient_tol,
+                                "maxiter": maxiter},
+                       **kwargs)
+
+    #########
+    # Store the raw and processed outputs of the estimation outputs
+    #########
+    # Calculate the final log-likelihood. Note the '-1' is because we minimized
+    # the negative log-likelihood but we want the actual log-likelihood
+    final_log_likelihood = -1 * results["fun"]
+
+    # Stop timing the estimation process and report the timing results
+    end_time = time.time()
+    if print_results:
+        elapsed_sec = (end_time - start_time)
+        elapsed_min = elapsed_sec / 60.0
+        if elapsed_min > 1.0:
+            print("Estimation Time: {:.2f} minutes.".format(elapsed_min))
+        else:
+            print("Estimation Time: {:.2f} seconds.".format(elapsed_sec))
+        print("Final log-likelihood: {:,.4f}".format(final_log_likelihood))
+        sys.stdout.flush()
+
+    # Isolate the final shape, intercept, and beta parameters
+    split_res = split_params(results.x, rows_to_nests)
+    final_nest_params, final_utility_coefs = split_res
+    natural_final_nest_params = nc.naturalize_nest_coefs(final_nest_params)
+
+    # Store the separate values of the nest, shape, intercept, and beta
+    # parameters in the estimation results dict
+    results["utility_coefs"] = final_utility_coefs
+    results["intercept_params"] = None
+    results["shape_params"] = None
+    results["nest_params"] = final_nest_params
+
+    # Calculate the predicted probabilities
+    # Note we don't include shape parameters because the clog-log model doesn't
+    # have shape parameters
+    c2obs = chosen_row_to_obs
+    desired_res = "long_and_chosen_probs"
+    probability_results = general_calc_probabilities(natural_final_nest_params,
+                                                     final_utility_coefs,
+                                                     design_matrix,
+                                                     rows_to_obs,
+                                                     rows_to_nests,
+                                                     chosen_row_to_obs=c2obs,
+                                                     return_type=desired_res)
+
+    prob_of_chosen_alternatives, long_probs = probability_results
+
+    # Calculate the residual vector
+    residuals = choice_vector - long_probs
+
+    # Calculate the observation specific chi-squared components
+    chi_squared_terms = np.square(residuals) / long_probs
+    individual_chi_squareds = rows_to_obs.T.dot(chi_squared_terms)
+
+    # Store the log-likelihood at zero
+    results["log_likelihood_null"] = log_likelihood_at_zero
+
+    # Calculate and store the rho-squared and rho-bar-squared
+    results["rho_squared"] = 1.0 - (final_log_likelihood /
+                                    log_likelihood_at_zero)
+    results["rho_bar_squared"] = 1.0 - ((final_log_likelihood -
+                                         results.x.shape[0]) /
+                                        log_likelihood_at_zero)
+
+    # Calculate and store the final gradient
+    results["final_gradient"] = general_gradient(final_nest_params,
+                                                 final_utility_coefs,
+                                                 design_matrix,
+                                                 choice_vector,
+                                                 rows_to_obs,
+                                                 rows_to_nests,
+                                                 ridge=ridge)
+
+    # Calculate and store the final hessian
+#    results["final_hessian"] = general_hessian(final_utility_coefs,
+#                                               design_matrix,
+#                                               alt_id_vector,
+#                                               rows_to_obs,
+#                                               rows_to_alts,
+#                                               easy_utility_transform,
+#                                               _cloglog_transform_deriv_c,
+#                                               easy_calc_dh_dv,
+#                                               calc_dh_d_alpha,
+#                                               block_matrix_indices,
+#                                               final_intercept_params,
+#                                               final_shape_params,
+#                                               ridge)
+    # Use the BHHH approximation for now, since the analytic gradient seems
+    # hard to compute.
+    results["final_hessian"] = bhhh_approx(final_nest_params,
+                                           final_utility_coefs,
+                                           design_matrix,
+                                           choice_vector,
+                                           rows_to_obs,
+                                           rows_to_nests,
+                                           ridge=ridge)
+    # Account for the 'constrained' parameters
+    for idx_val in constrained_pos:
+        results["final_hessian"][idx_val, :] = 0
+        results["final_hessian"][:, idx_val] = 0
+        results["final_hessian"][idx_val, idx_val] = -1
+
+    # Calculate and store the final fisher information matrix
+    # Use a placeholder value for now, especially since what I was using as
+    # the fisher information matrix I'm now calling the bhhh_approximation
+    # and I'm directly using it to approximate the hessian, as opposed to
+    # simply using this to compute the sandwhich estimator
+    results["fisher_info"] = np.diag(-1 * np.ones(results.x.shape[0]))
+#    results["fisher_info"] = cc.calc_fischer_info_matrix(
+#                                                          final_utility_coefs,
+#                                                                design_matrix,
+#                                                                alt_id_vector,
+#                                                                  rows_to_obs,
+#                                                                 rows_to_alts,
+#                                                                choice_vector,
+#                                                       easy_utility_transform,
+#                                                   _cloglog_transform_deriv_c,
+#                                                              easy_calc_dh_dv,
+#                                                              calc_dh_d_alpha,
+#                                                       final_intercept_params,
+#                                                           final_shape_params,
+#                                                                        ridge)
+
+    # Record which parameters were constrained during estimation
+    results["constrained_pos"] = constrained_pos
+
+    # Add all miscellaneous objects that we need to store to the results dict
+    results["final_log_likelihood"] = final_log_likelihood
+    results["chosen_probs"] = prob_of_chosen_alternatives
+    results["long_probs"] = long_probs
+    results["residuals"] = residuals
+    results["ind_chi_squareds"] = individual_chi_squareds
+
+    return results
+
+
+# Define the nested logit model class
+class NestedLogit(base_mcm.MNDC_Model):
+    """
+    Parameters
+    ----------
+    data : string or pandas dataframe.
+        If string, data should be an absolute or relative path to a CSV file
+        containing the long format data for this choice model. Note long format
+        is has one row per available alternative for each observation. If
+        pandas dataframe, the dataframe should be the long format data for the
+        choice model.
+    alt_id_col : string.
+        Should denote the column in `data` that contains the identifiers of
+        each row's corresponding alternative.
+    obs_id_col : string.
+        Should denote the column in `data` that contains the identifiers of
+        each row's corresponding observation.
+    choice_col : string.
+        Should denote the column in data which contains the ones and zeros that
+        denote whether or not the given row corresponds to the chosen
+        alternative for the given individual.
+    specification : OrderedDict.
+        Keys are a proper subset of the columns in `long_form_df`. Values are
+        either a list or a single string, `'all_diff' or 'all_same'`. If a
+        list, the elements should be:
+
+            - single objects that are within the alternative ID column of
+              `long_form_df`
+            - lists of objects that are within the alternative ID column of
+              `long_form_df`.
+
+        For each single object in the list, a unique column will be created
+        (i.e. there will be a unique coefficient for that variable in the
+        corresponding utility equation of the corresponding alternative). For
+        lists within the specification_dict values, a single column will be
+        created for all the alternatives within iterable (i.e. there will be
+        one common coefficient for the variables in the iterable).
+    names : OrderedDict, optional.
+        Should have the same keys as `specification`. For each key:
+
+            - if the corresponding value in `specification` is `'all_same'`,
+              then there should be a single string as the value in names.
+            - if the corresponding value in `specification` is `'all_diff'`,
+              then there should be a list of strings as the value in names.
+              There should be one string in the value in names for each
+              possible alternative.
+            - if the corresponding value in `specification` is a list, then
+              there should be a list of strings as the value in names. There
+              should be one string the value in names per item in the value in
+              `specification`.
+
+        Default == None.
+    nest_spec : OrderedDict, optional.
+        This kwarg MUST be passed for nested logit models. It is optional only
+        for api compatibility. Keys are strings that define the name of the
+        nests. Values are lists of alternative ids, denoting which alternatives
+        belong to which nests. Each alternative id must only be associated with
+        a single nest! Default `== None`.
+    model_type : str, optional.
+        Denotes the model type of the choice model being instantiated.
+        Default == "".
+    """
+    def __init__(self,
+                 data,
+                 alt_id_col,
+                 obs_id_col,
+                 choice_col,
+                 specification,
+                 names=None,
+                 nest_spec=None,
+                 **kwargs):
+        ##########
+        # Print a helpful message if nest_spec has not been included
+        ##########
+        missing_nest_spec = (nest_spec is None and
+                             "nest_spec" not in kwargs and
+                             kwargs["nest_spec"] is None)
+        if missing_nest_spec:
+            msg = "The Nested Logit Model REQUIRES a nest specification dict."
+            raise ValueError(msg)
+
+        ##########
+        # Carry out the common instantiation process for all choice models
+        ##########
+        super(NestedLogit, self).__init__(data,
+                                          alt_id_col,
+                                          obs_id_col,
+                                          choice_col,
+                                          specification,
+                                          names=names,
+                                          nest_spec=nest_spec,
+                                          model_type="Nested Logit Model")
+
+        ##########
+        # Store the utility transform function
+        ##########
+        self.utility_transform = lambda x, *args, **kwargs: x
+
+        return None
+
+    def fit_mle(self,
+                init_vals,
+                constrained_pos=None,
+                print_res=True,
+                method="BFGS",
+                loss_tol=1e-06,
+                gradient_tol=1e-06,
+                maxiter=1000,
+                ridge=None,
+                **kwargs):
+        """
+        Parameters
+        ----------
+        init_vals : 1D ndarray.
+            Should containn the initial values to start the optimization
+            process with. There should be one value for each utility
+            coefficient and shape parameter being estimated.
+        constrained_pos : list, or None, optional.
+            Denotes the positions of the array of estimated parameters that are
+            not to change from their initial values. If a list is passed, the
+            elements are to be integers where no such integer is greater than
+            `init_values.size`.
+        print_res : bool, optional.
+            Determines whether the timing and initial and final log likelihood
+            results will be printed as they they are determined.
+        method : str, optional.
+            Should be a valid string which can be passed to
+            `scipy.optimize.minimize`. Determines the optimization algorithm
+            which is used for this problem.
+        loss_tol : float, optional.
+            Determines the tolerance on the difference in objective function
+            values from one iteration to the next which is needed to determine
+            convergence. Default `== 1e-06`.
+        gradient_tol : float, optional.
+            Determines the tolerance on the difference in gradient values from
+            one iteration to the next which is needed to determine convergence.
+            Default `== 1e-06`.
+        ridge : int, float, long, or None, optional.
+            Determines whether ridge regression is performed. If a scalar is
+            passed, then that scalar determines the ridge penalty for the
+            optimization. Default `== None`.
+
+        Returns
+        -------
+        None. Estimation results are saved to the model instance.
+        """
+        # Check integrity of passed arguments
+        kwargs_to_be_ignored = ["init_shapes", "init_intercepts", "init_index"]
+        if any([x in kwargs for x in kwargs_to_be_ignored]):
+            msg = "MNL model does not use of any of the following kwargs:\n{}"
+            msg_2 = "Remove such kwargs and pass a single init_vals argument"
+            raise ValueError(msg.format(kwargs_to_be_ignored) + msg_2)
+
+        # Store the optimization method
+        self.optimization_method = method
+
+        # Store the ridge parameter
+        self.ridge_param = ridge
+
+        if ridge is not None:
+            msg = "NOTE: An L2-penalized regression is being performed. The "
+            msg_2 = "reported standard errors and robust standard errors "
+            msg_3 = "***WILL BE INCORRECT***."
+
+            print("=" * 30)
+            print(msg + msg_2 + msg_3)
+            print("=" * 30)
+            print("\n")
+
+        # Construct the mappings from alternatives to observations and from
+        # chosen alternatives to observations
+        mapping_res = self.get_mappings_for_fit()
+        rows_to_obs = mapping_res["rows_to_obs"]
+        rows_to_alts = mapping_res["rows_to_alts"]
+        rows_to_nests = mapping_res["rows_to_nests"]
+        chosen_row_to_obs = mapping_res["chosen_row_to_obs"]
+
+        # Determine the degenerate nests whose nesting parameters are to be
+        # constrained to one
+        fixed_params = []
+        for pos, key in enumerate(self.nest_spec):
+            if len(self.nest_spec[key]) == 1:
+                fixed_params.append(pos)
+        # Include the user specified parameters that are to be constrained to
+        # their initial values
+        if constrained_pos is not None:
+            fixed_params.extend(constrained_pos)
+        final_constrained_pos = sorted(list(set(fixed_params)))
+
+        # Get the estimation results
+        estimation_res = _estimate(init_vals,
+                                   self.design,
+                                   self.alt_IDs,
+                                   self.choices,
+                                   rows_to_obs,
+                                   rows_to_alts,
+                                   rows_to_nests,
+                                   chosen_row_to_obs,
+                                   final_constrained_pos,
+                                   print_results=print_res,
+                                   method=method,
+                                   loss_tol=loss_tol,
+                                   gradient_tol=gradient_tol,
+                                   maxiter=maxiter,
+                                   ridge=ridge,
+                                   **kwargs)
+
+        # Store the estimation results
+        self.store_fit_results(estimation_res)
+
+        return None

Add nested logit and mixed logit model files.