<jupyter_start><jupyter_text>Mixed Logit The following examples provide step-by-step instructions to estimate mixed logit models using the xlogit package. You can interactively execute the code in this guide by opening it Google Colab using the following link:[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/arteagac/xlogit/blob/master/examples/mixed_logit_model.ipynb)  Install and import `xlogit` package Install `xlogit` using `pip` as shown below. In addition, import the package and check if GPU processing is available.<jupyter_code>!pip install xlogit
from xlogit import MixedLogit
MixedLogit.check_if_gpu_available()<jupyter_output>Collecting xlogit
  Downloading https://files.pythonhosted.org/packages/60/5f/9bc576d180c366af77bc04e268536e9e34be23c52a520918aa0cb56b438e/xlogit-0.1.3-py3-none-any.whl
Requirement already satisfied: scipy>=1.0.0 in /usr/local/lib/python3.7/dist-packages (from xlogit) (1.4.1)
Requirement already satisfied: numpy>=1.13.1 in /usr/local/lib/python3.7/dist-packages (from xlogit) (1.19.5)
Installing collected packages: xlogit
Successfully installed xlogit-0.1.3
1 GPU device(s) available. xlogit will use GPU processing<jupyter_text>Swissmetro Dataset The swissmetro dataset contains stated-preferences for three alternative transportation modes that include car, train and a newly introduced mode: the swissmetro. This dataset is commonly used for estimation examples with the `Biogeme` and `PyLogit` packages. The dataset is available at http://transp-or.epfl.ch/data/swissmetro.dat and [Bierlaire et. al., (2001)](https://transp-or.epfl.ch/documents/proceedings/BierAxhaAbay01.pdf) provides a detailed discussion of the data as wells as its context and collection process. The explanatory variables in this example include the travel time (`TT`) and cost `CO` for each of the three alternative modes.  Read data The dataset is imported to the Python environment using `pandas`. Then, two types of samples, ones with a trip purpose different to commute or business and ones with an unknown choice, are filtered out. The original dataset contains 10,729 records, but after filtering, 6,768 records remain for following analysis. Finally, a new column that uniquely identifies each sample is added to the dataframe and the `CHOICE` column, which originally contains a numerical coding of the choices, is mapped to a description that is consistent with the alternatives in the column names.<jupyter_code>import pandas as pd
import numpy as np

df_wide = pd.read_table("http://transp-or.epfl.ch/data/swissmetro.dat", sep='\t')

# Keep only observations for commute and business purposes that contain known choices
df_wide = df_wide[(df_wide['PURPOSE'].isin([1, 3]) & (df_wide['CHOICE'] != 0))]

df_wide['custom_id'] = np.arange(len(df_wide))  # Add unique identifier
df_wide['CHOICE'] = df_wide['CHOICE'].map({1: 'TRAIN', 2:'SM', 3: 'CAR'})
df_wide<jupyter_output><empty_output><jupyter_text>Reshape data The imported dataframe is in wide format, and it needs to be reshaped to long format for processing by `xlogit`, which offers the convenient `wide_to_long` utility for this reshaping process. The user needs to specify the column that uniquely identifies each sample, the names of the alternatives, the columns that vary across alternatives, and whether the alternative names are a prefix or suffix of the column names. Additionally, the user can specify a value (`empty_val`) to be used by default when an alternative is not available for a certain variable. Additional usage examples for the `wide_to_long` function are available in xlogit's documentation at https://xlogit.readthedocs.io/en/latest/notebooks/convert_data_wide_to_long.html. Also, details about the function parameters are available at the [API reference ](https://xlogit.readthedocs.io/en/latest/api/utils.htmlxlogit.utils.wide_to_long).<jupyter_code>from xlogit.utils import wide_to_long

df = wide_to_long(df_wide, id_col='custom_id', alt_name='alt', sep='_',
                  alt_list=['TRAIN', 'SM', 'CAR'], empty_val=0,
                  varying=['TT', 'CO', 'HE', 'AV', 'SEATS'], alt_is_prefix=True)
df<jupyter_output><empty_output><jupyter_text>Create model specification Following the reshaping, users can create or update the dataset's columns in order to accommodate their model specification needs, if necessary. The code below shows how the columns `ASC_TRAIN` and `ASC_CAR` were created to incorporate alternative-specific constants in the model. In addition, the example illustrates an effective way of establishing variable interactions (e.g., trip costs for commuters with an annual pass) by updating existing columns conditional on values of other columns. Although apparently simple, column operations provide users with an intuitive and highly-flexible mechanism to incorporate model specification aspects, such as variable transformations, interactions, and alternative specific coefficients and constants. By operating the dataframe columns, any utility specification can be accommodated in `xlogit`. As shown in [this specification example](https://xlogit.readthedocs.io/en/latest/notebooks/multinomial_model.htmlCreate-model-specification), highly-flexible utility specifications can be modeled in `xlogit` by operating the dataframe columns.<jupyter_code>df['ASC_TRAIN'] = np.ones(len(df))*(df['alt'] == 'TRAIN')
df['ASC_CAR'] = np.ones(len(df))*(df['alt'] == 'CAR')
df['TT'], df['CO'] = df['TT']/100, df['CO']/100  # Scale variables
annual_pass = (df['GA'] == 1) & (df['alt'].isin(['TRAIN', 'SM']))
df.loc[annual_pass, 'CO'] = 0  # Cost zero for pass holders<jupyter_output><empty_output><jupyter_text>Estimate model parameters The `fit` method estimates the model by taking as input the data from the previous step along with additional specification criteria, such as the distribution of the random parameters (`randvars`), the number of random draws (`n_draws`), and the availability of alternatives for the choice situations (`avail`). Once the estimation routine is completed, the `summary` method can be used to display the estimation results.<jupyter_code>from xlogit import MixedLogit
varnames=['ASC_CAR', 'ASC_TRAIN', 'CO', 'TT']
model = MixedLogit()
model.fit(X=df[varnames], y=df['CHOICE'], varnames=varnames,
          alts=df['alt'], ids=df['custom_id'], avail=df['AV'],
          randvars={'TT': 'n'}, n_draws=2000)
model.summary()<jupyter_output>GPU processing enabled.
Optimization terminated successfully.
         Current function value: 5214.927430
         Iterations: 19
         Function evaluations: 25
         Gradient evaluations: 25
Estimation time= 6.1 seconds
---------------------------------------------------------------------------
Coefficient              Estimate      Std.Err.         z-val         P>|z|
---------------------------------------------------------------------------
ASC_CAR                 0.1371110     0.0489429     2.8014468        0.0158 *  
ASC_TRAIN              -0.4018231     0.0586071    -6.8562141      5.35e-11 ***
CO                     -1.2854009     0.0614645   -20.9128918      7.27e-93 ***
TT                     -2.2598763     0.1111418   -20.3332649      5.58e-88 ***
sd.TT                   1.6576641     0.1308694    12.6665510      2.91e-35 ***
---------------------------------------------------------------------------
Significance:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Log-L[...]<jupyter_text>The negative signs for the cost and time coefficients suggest that decision makers experience a general disutility with alternatives that have higher waiting times and costs, which conforms to the underlying decision making theory. Note that these estimates are highly consistent with those returned by Biogeme (https://biogeme.epfl.ch/examples/swissmetro/05normalMixtureIntegral.html)  Electricity Dataset The electricity dataset contains 4,308 choices among four electricity suppliers based on the attributes of the offered plans, which include prices(pf), contract lengths(cl), time of day rates (tod), seasonal rates(seas), as well as attributes of the suppliers, which include whether the supplier is local (loc) and well-known (wk). The data was collected through a survey where 12 different choice situations were presented to each participant. The multiple responses per participants were organized into panels. Given that some participants answered less than 12 of the choice situations, some panels are unbalanced, which `xlogit` is able to handle. [Revelt and Train (1999)](https://escholarship.org/content/qt1900p96t/qt1900p96t.pdf) provide a detailed description of this dataset.   Read data The dataset is already in long format so no reshaping is necessary, it can be used directly in xlogit.<jupyter_code>import pandas as pd

df = pd.read_csv("https://raw.github.com/arteagac/xlogit/master/examples/data/electricity_long.csv")
df<jupyter_output><empty_output><jupyter_text>Fit the model Note that the parameter `panels` was included in the `fit` function in order to take into account panel structure of this dataset during estimation.<jupyter_code>from xlogit import MixedLogit

varnames = ['pf', 'cl', 'loc', 'wk', 'tod', 'seas']
model = MixedLogit()
model.fit(X=df[varnames],
          y=df['choice'],
          varnames=varnames,
          ids=df['chid'],
          panels=df['id'],
          alts=df['alt'],
          n_draws=600,
          randvars={'pf': 'n', 'cl': 'n', 'loc': 'n',
                    'wk': 'n', 'tod': 'n', 'seas': 'n'})
model.summary()<jupyter_output>GPU processing enabled.
Optimization terminated successfully.
         Current function value: 3888.413414
         Iterations: 46
         Function evaluations: 51
         Gradient evaluations: 51
Estimation time= 2.5 seconds
---------------------------------------------------------------------------
Coefficient              Estimate      Std.Err.         z-val         P>|z|
---------------------------------------------------------------------------
pf                     -0.9996286     0.0331488   -30.1557541     9.98e-100 ***
cl                     -0.2355334     0.0220401   -10.6865870      1.97e-22 ***
loc                     2.2307891     0.1164263    19.1605300      5.64e-56 ***
wk                      1.6251657     0.0918755    17.6887855      6.85e-50 ***
tod                    -9.6067367     0.3112721   -30.8628296     2.36e-102 ***
seas                   -9.7892800     0.2913063   -33.6047603     2.81e-112 ***
sd.pf                   0.2357813     0.0181892    12.9627201   [...]<jupyter_text>The xlogit estimates are similar to those estimated using R's mlogit package (https://cran.r-project.org/web/packages/mlogit/vignettes/e3mxlogit.html). With GPU-enabled estimations, xlogit estimates the model in less than 10 seconds, significantly faster than open-source pacakges such as mlogit and pylogit. This feature can be beneficial while fitting models for large datasets with multiple explanatory variables to be estimated with random coefficients.  Fishing Dataset This example illustrates the estimation of a Mixed Logit model for choices of 1,182 individuals for sport fishing modes using `xlogit`. The goal is to analyze the market shares of four alternatives (i.e., beach, pier, boat, and charter) based on their cost and fish catch rate. [Cameron (2005)](http://cameron.econ.ucdavis.edu/mmabook/mma.html) provides additional details about this dataset. The following code illustrates how to use `xlogit` to estimate the model parameters.  Read data The data to be analyzed can be imported to Python using any preferred method. In this example, the data in CSV format was imported using the popular `pandas` Python package. However, it is worth highlighting that `xlogit` does not depend on the `pandas` package, as `xlogit` can take any array-like structure as input. This represents an additional advantage because `xlogit` can be used with any preferred dataframe library, and not only with `pandas`.<jupyter_code>import pandas as pd
df = pd.read_csv("https://raw.github.com/arteagac/xlogit/master/examples/data/fishing_long.csv")
df<jupyter_output><empty_output><jupyter_text>Fit model Once the data is in the `Python` environment, `xlogit` can be used to fit the model, as shown below. The `MultinomialLogit` class is imported from `xlogit`, and its constructor is used to initialize a new model. The `fit` method estimates the model using the input data and estimation criteria provided as arguments to the method's call. The arguments of the `fit` methods are described in [`xlogit`'s documentation](https://https://xlogit.readthedocs.io/en/latest/api/).<jupyter_code>from xlogit import MixedLogit
varnames = ['price', 'catch']
model = MixedLogit()
model.fit(X=df[varnames],
          y=df['choice'],
          varnames=varnames,
          alts=df['alt'],
          ids=df['id'],
          n_draws=1000,
          randvars={'price': 'n', 'catch': 'n'})
model.summary()<jupyter_output>GPU processing enabled.
Optimization terminated successfully.
         Current function value: 1300.511342
         Iterations: 42
         Function evaluations: 55
         Gradient evaluations: 55
Estimation time= 1.3 seconds
---------------------------------------------------------------------------
Coefficient              Estimate      Std.Err.         z-val         P>|z|
---------------------------------------------------------------------------
price                  -0.0272460     0.0022980   -11.8561596      1.09e-29 ***
catch                   1.3271142     0.1743120     7.6134421      4.01e-13 ***
sd.price                0.0102129     0.0021859     4.6722783      1.59e-05 ***
sd.catch               -1.5706821     0.3762555    -4.1745090      0.000139 ***
---------------------------------------------------------------------------
Significance:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Log-Likelihood= -1300.511
AIC= 2609.023
BIC= 2629.323<jupyter_text>Prediction `xlogit` also provides a convenient set of post-estimation tools for prediction or forecasting. The `predict` function uses estimated parameters and a new or updated dataset to compute predicted choices. By including the `return_proba` and `return_freq` parameters in the function's call, users can also obtain the predicted probabilities and frequency of the chosen alternatives. The following code illustrates the prediction functionality to forecast changes in market shares (choice frequency) for fishing modes caused by an increase in price for the "boat" mode. First, base market shares are computed by running `predict` on the original dataset. Then, an increase of 20% in the price for the "boat" alternative is applied to the dataset and the updated shares are predicted.<jupyter_code>choices, freq = model.predict(X=df[varnames], varnames=varnames, ids=df['id'],
                              alts=df['alt'], return_freq=True)
print(f"base: {freq}")

df.loc[df['alt']=='boat', 'price'] *= 1.2  # 20 percent price increase
choices, freq = model.predict(X=df[varnames], varnames=varnames, ids=df['id'],
                              alts=df['alt'], return_freq=True)
print(f"updated: {freq}")<jupyter_output>GPU processing enabled.
base: {'beach': 0.223, 'boat': 0.461, 'charter': 0.228, 'pier': 0.089}
GPU processing enabled.
updated: {'beach': 0.239, 'boat': 0.377, 'charter': 0.278, 'pier': 0.106}<jupyter_text>The output shows that the 20% price increase would result in a decrease of almost 10% in market share for the "boat" alternative.  Car Dataset The fourth example uses a stated preference panel dataset for choice of car. Three alternatives are considered, with upto 6 choice situations per individual. This again is an unbalanced panel with responses of some individuals less than 6 situations. The dataset contains 8 explanaotry variables: price, operating cost, range, and binary indicators to indicate whether the car is electric, hybrid, and if performance is high or medium respectively. This dataset was taken from Kenneth Train's MATLAB codes for estimation of Mixed Logit models as shown in this link: https://eml.berkeley.edu/Software/abstracts/train1006mxlmsl.html  Read data<jupyter_code>import pandas as pd
import numpy as np

df = pd.read_csv("https://raw.github.com/arteagac/xlogit/master/examples/data/car100_long.csv")<jupyter_output><empty_output><jupyter_text>Since price and operating cost need to be estimated with negative coefficients, we reverse the variable signs in the dataframe.<jupyter_code>df['price'] = -df['price']/10000
df['opcost'] = -df['opcost']
df<jupyter_output><empty_output><jupyter_text>Fit the model<jupyter_code>from xlogit import MixedLogit

varnames = ['hiperf', 'medhiperf', 'price', 'opcost', 'range', 'ev', 'hybrid'] 
model = MixedLogit()
model.fit(X=df[varnames],
          y=df['choice'],
          varnames=varnames,
          alts=df['alt'],
          ids=df['choice_id'],
          panels=df['person_id'],
          randvars = {'price': 'ln', 'opcost': 'n', 
                      'range': 'ln', 'ev':'n', 'hybrid': 'n'}, 
          n_draws = 100) 
model.summary()<jupyter_output>GPU processing enabled.
Optimization terminated successfully.
         Current function value: 1297.937510
         Iterations: 52
         Function evaluations: 63
         Gradient evaluations: 63
Estimation time= 0.5 seconds
---------------------------------------------------------------------------
Coefficient              Estimate      Std.Err.         z-val         P>|z|
---------------------------------------------------------------------------
hiperf                  0.1059300     0.0923764     1.1467216         0.411    
medhiperf               0.5660352     0.0961273     5.8883953      2.36e-07 ***
price                  -0.7861318     0.1358040    -5.7887229      3.65e-07 ***
opcost                  0.0120780     0.0057487     2.1009905        0.0898 .  
range                  -0.5886938     0.3564083    -1.6517401         0.204    
ev                     -1.6330363     0.3232833    -5.0514085      8.25e-06 ***
hybrid                  0.6902022     0.1474823     4.6798989   [...]
{'fraction_non_alphanumeric': 0.1338474249518039, 'fraction_numerical': 0.05497108234646103, 'mean_word_length': 3.0168141592920352, 'pattern_counts': {'":': 0, '<': 47, '<?xml version=': 0, '>': 53, 'https://': 16, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 1, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '13', 'dir': 'jupyter-structured-clean-dedup', 'id': '187911', 'license': 'MIT', 'n_tokens_mistral': 5739, 'n_tokens_neox': 5016, 'n_words': 1815, 'path': 'docs/notebooks/mixed_logit_model.ipynb', 'repo_name': 'arteagac/pymlogit', 'size': '18155'}