Mlogit Benchmark 2: Kenneth Train's Heating Data

The purpose of this notebook is to:

  1. Demonstrate the use of the pyLogit to estimate conditional logit models.
  2. Benchmark the results reported pyLogit against those reported by the mlogit package.

The models estimated in this notebook will be the same models detailed in "Kenneth Train’s exercises using the mlogit package for R." In particular, the following models will be estimated:

  1. The model with installation cost and operating cost, without intercepts (p.2).
     mlogit(depvar~ic+oc|0, H) 
  2. The model that imposes the constraint that r = 0.12 (such that wtp = 8.33) (p. 4).
     H$lcc=H$ic+H$oc/0.12
     mlcc <- mlogit(depvar~lcc|0, H)
            
  3. The model with installation cost, operating cost, and all intercepts except that of the "hp" alternative (p.5).
     mc <- mlogit(depvar~ic+oc, H, reflevel = 'hp')
        
  4. The model with installation cost divided by income, operating cost, and all intercepts except that of the "hp" alternative (p. 7).
     mi <- mlogit(depvar~oc+I(ic/income), H, reflevel = 'hp')
        
  5. The model with intallation costs, operating costs, alternative specific coefficients for income, and all intercepts except that of the "hp" alternative (p.7).
     mi2 <- mlogit(depvar~oc+ic|income, H, reflevel="hp")
        

1. Import Needed libraries


In [1]:
from collections import OrderedDict    # For recording the model specification 

import pandas as pd                    # For file input/output
import numpy as np                     # For vectorized math operations

import pylogit as pl                   # For MNL model estimation and
                                       # conversion from wide to long format

2. Load and look at the required datasets


In [2]:
# Load the Heating data, noting that the data is in wide data format
wide_heating_df = pd.read_csv("../data/heating_data_r.csv")

In [3]:
# Look at the raw Heating data
wide_heating_df.head().T


Out[3]:
0 1 2 3 4
idcase 1 2 3 4 5
depvar gc gc gc er er
ic.gc 866 727.93 599.48 835.17 755.59
ic.gr 962.64 758.89 783.05 793.06 846.29
ic.ec 859.9 796.82 719.86 761.25 858.86
ic.er 995.76 894.69 900.11 831.04 985.64
ic.hp 1135.5 968.9 1048.3 1048.7 883.05
oc.gc 199.69 168.66 165.58 180.88 174.91
oc.gr 151.72 168.66 137.8 147.14 138.9
oc.ec 553.34 520.24 439.06 483 404.41
oc.er 505.6 486.49 404.74 425.22 389.52
oc.hp 237.88 199.19 171.47 222.95 178.49
income 7 5 4 2 2
agehed 25 60 65 50 25
rooms 6 5 2 4 6
region ncostl scostl ncostl scostl valley

3. Convert the wide format dataframes to long format

3a. Perform needed data cleaning

Noting that the column denoting the choice (depvar) contains string objects, we need to convert the choice column into an integer based column.


In [4]:
# Convert the choice column for the Train data into integers
# Note that we will use a 1 to denote 'choice1' and a 2 to 
# represent 'choice2'
wide_heating_df["choice"] = wide_heating_df["depvar"].map(dict(zip(['gc', 'gr',
                                                                    'ec', 'er',
                                                                    'hp'],
                                                                   range(1,6))))

For the Heating data, all of the alternatives are available in all choice situations. Note that, in general, this is not the case for choice data. As such we need to have columns that denote the availability of each alternative for each individual.

These columns will all be filled with ones for each row in the wide format dataframes because all of the alternatives are always available for each individual.


In [5]:
# Create the needed availability columns for the Heating data
for i in range(1, 6):
    wide_heating_df["availability_{}".format(i)] = 1

3b. Convert the Heating dataset to long format


In [6]:
# Look at the columns that we need to account for when converting from
# the wide data format to the long data format.
wide_heating_df.columns


Out[6]:
Index([u'idcase', u'depvar', u'ic.gc', u'ic.gr', u'ic.ec', u'ic.er', u'ic.hp',
       u'oc.gc', u'oc.gr', u'oc.ec', u'oc.er', u'oc.hp', u'income', u'agehed',
       u'rooms', u'region', u'choice', u'availability_1', u'availability_2',
       u'availability_3', u'availability_4', u'availability_5'],
      dtype='object')

In [7]:
##########
# Define lists of the variables pertaining to each variable type
# that we need to account for in the data format transformation
##########
# Determine the name for the alternative ids in the long format 
# data frame
heating_alt_id = "alt_id"
# Determine the column that denotes the id of what we're treating
# as individual observations, i.e. the choice situations.
heating_obs_id_col = "idcase"
# Determine what column denotes the choice that was made
heating_choice_column = "choice"


# Create the list of observation specific variables
heating_ind_variables = ["depvar", "income", "agehed", "rooms", "region"]

# Specify the variables that vary across individuals and some or all alternatives
# Note that each "main" key should be the desired name of the column in the long
# data format. The inner keys shoud be the alternative ids that that have some
# value for the "main" key variable.
heating_alt_varying_variables = {"installation_costs": {1: "ic.gc",
                                                        2: "ic.gr",
                                                        3: "ic.ec",
                                                        4: "ic.er",
                                                        5: "ic.hp"},
                                 "operating_costs": {1: "oc.gc",
                                                     2: "oc.gr",
                                                     3: "oc.ec",
                                                     4: "oc.er",
                                                     5: "oc.hp"},
                                  }

# Specify the availability variables
heating_availability_variables = OrderedDict()
for alt_id, var in zip(range(1, 6),
                       ["availability_{}".format(i) for i in range(1, 6)]):
    heating_availability_variables[alt_id] = var

In [8]:
##########
# Actually perform the conversion to long format
##########
long_heating_df = pl.convert_wide_to_long(wide_data=wide_heating_df,
                                          ind_vars=heating_ind_variables,
                                          alt_specific_vars=heating_alt_varying_variables,
                                          availability_vars=heating_availability_variables,
                                          obs_id_col=heating_obs_id_col,
                                          choice_col=heating_choice_column,
                                          new_alt_id_name=heating_alt_id)

# Look at the long format Heating data
long_heating_df.head()


Out[8]:
idcase alt_id choice depvar income agehed rooms region installation_costs operating_costs
0 1 1 1 gc 7 25 6 ncostl 866.00 199.69
1 1 2 0 gc 7 25 6 ncostl 962.64 151.72
2 1 3 0 gc 7 25 6 ncostl 859.90 553.34
3 1 4 0 gc 7 25 6 ncostl 995.76 505.60
4 1 5 0 gc 7 25 6 ncostl 1135.50 237.88

4. Create desired variables


In [9]:
# Create the life-cycle cost variable needed for model 2 where
# we assume the discount rate, r, is 0.12.
long_heating_df["life_cycle_cost"] = (long_heating_df["installation_costs"] + 
                                      long_heating_df["operating_costs"] / 0.12)

# Create the installation cost divided by income variable
long_heating_df["installation_cost_burden"] = (long_heating_df["installation_costs"] /
                                               long_heating_df["income"])

For numeric stability reasons, it is advised that one scale one's variables so that the estimated coefficients are similar in absolute magnitude, and if possible so that the estimated coefficients are close to 1 in absolute value (in other words, not terribly tiny or extremely large). This is done for the fishing data below

5. Specify and estimate the desired models needed for benchmarking

5a. The model with installation cost and operating cost, without intercepts


In [10]:
# Create the model specification
model_1_spec = OrderedDict()
model_1_names = OrderedDict()

# Note that for the specification dictionary, the
# keys should be the column names from the long format
# dataframe and the values should be a list with a combination
# of alternative id's and/or lists of alternative id's. There 
# should be one element for each beta that will be estimated 
# in relation to the given column. Lists of alternative id's
# mean that all of the alternatives in the list will get a
# single beta for them, for the given variable.
# The names dictionary should contain one name for each
# element (that is each alternative id or list of alternative 
# ids) in the specification dictionary value for the same 
# variable

model_1_spec["installation_costs"] = [range(1, 6)]
model_1_names["installation_costs"] = ["installation_costs"]

model_1_spec["operating_costs"] = [range(1, 6)]
model_1_names["operating_costs"] = ["operating_costs"]

In [11]:
# Create an instance of the MNL model class
model_1 = pl.create_choice_model(data=long_heating_df,
                                 alt_id_col=heating_alt_id,
                                 obs_id_col=heating_obs_id_col,
                                 choice_col=heating_choice_column,
                                 specification=model_1_spec,
                                 model_type="MNL",
                                 names=model_1_names)

# Estimate the given model, starting from a point of all zeros
# as the initial values.
model_1.fit_mle(np.zeros(2), method='newton-cg')

# Look at the estimation summaries
model_1.get_statsmodels_summary()


Log-likelihood at zero: -1,448.4941
Initial Log-likelihood: -1,448.4941
Estimation Time: 0.37 seconds.
Final log-likelihood: -1,095.2371
/Users/timothyb0912/anaconda/lib/python2.7/site-packages/pylogit/conditional_logit.py:482: OptimizeWarning: Unknown solver options: gtol
  "maxiter": maxiter})
Out[11]:
Multinomial Logit Model Regression Results
Dep. Variable: choice No. Observations: 900
Model: Multinomial Logit Model Df Residuals: 898
Method: MLE Df Model: 2
Date: Mon, 14 Mar 2016 Pseudo R-squ.: 0.244
Time: 18:51:13 Pseudo R-bar-squ.: 0.242
converged: True Log-Likelihood: -1,095.237
LL-Null: -1,448.494
coef std err z P>|z| [95.0% Conf. Int.]
installation_costs -0.0062 0.000 -17.665 0.000 -0.007 -0.006
operating_costs -0.0046 0.000 -14.217 0.000 -0.005 -0.004

In [12]:
# Look at the 'standard summary' since it includes robust p-values
model_1.print_summaries()



Number of Parameters                                          2
Number of Observations                                      900
Null Log-Likelihood                                   -1448.494
Fitted Log-Likelihood                                 -1095.237
Rho-Squared                                           0.2438788
Rho-Bar-Squared                                        0.242498
Estimation Message        Optimization terminated successfully.
dtype: object
==============================
                    parameters   std_err    t_stats      p_values  \
installation_costs   -0.006232  0.000353 -17.665275  7.763377e-70   
operating_costs      -0.004580  0.000322 -14.216562  7.231945e-46   

                    robust_std_err  robust_t_stats  robust_p_values  
installation_costs        0.002489       -2.503814     1.228625e-02  
operating_costs           0.000535       -8.560322     1.125527e-17  

Compare with mlogit

The call from mlogit was as follows:

Call:
mlogit(formula = depvar ~ ic + oc | 0, data = H, method = "nr", 
    print.level = 0)

Frequencies of alternatives: ec er gc gr hp 0.071111 0.093333 0.636667 0.143333 0.055556

nr method 4 iterations, 0h:0m:0s g'(-H)^-1g = 1.56E-07 gradient close to zero

Coefficients : Estimate Std. Error t-value Pr(>|t|)
ic -0.00623187 0.00035277 -17.665 < 2.2e-16 *** oc -0.00458008 0.00032216 -14.217 < 2.2e-16 *** --- Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Log-Likelihood: -1095.2 </pre> As can be seen, the estimates, standard errors, t-values, and log-likelihood agree. The p-values differ but this is because mlogit calculates its p-values based on a t-distribution whereas pyLogit uses an asymptotic normal distribution.

5b. The model that imposes the constraint that the discount rate, r = 0.12, still without intercepts.


In [13]:
# Create the model specification
model_2_spec = OrderedDict()
model_2_names = OrderedDict()

model_2_spec["life_cycle_cost"] = [range(1, 6)]
model_2_names["life_cycle_cost"] = ["installation_costs"]

# Create an instance of the MNL model class
model_2 = pl.create_choice_model(data=long_heating_df,
                                 alt_id_col=heating_alt_id,
                                 obs_id_col=heating_obs_id_col,
                                 choice_col=heating_choice_column,
                                 specification=model_2_spec,
                                 model_type="MNL",
                                 names=model_2_names)

# Estimate the given model, starting from a point of all zeros
# as the initial values.
model_2.fit_mle(np.zeros(1), method='newton-cg')

# Look at the estimation summaries
model_2.get_statsmodels_summary()


Log-likelihood at zero: -1,448.4941
Initial Log-likelihood: -1,448.4941
Estimation Time: 0.26 seconds.
Final log-likelihood: -1,248.7019
Out[13]:
Multinomial Logit Model Regression Results
Dep. Variable: choice No. Observations: 900
Model: Multinomial Logit Model Df Residuals: 899
Method: MLE Df Model: 1
Date: Mon, 14 Mar 2016 Pseudo R-squ.: 0.138
Time: 18:51:13 Pseudo R-bar-squ.: 0.137
converged: True Log-Likelihood: -1,248.702
LL-Null: -1,448.494
coef std err z P>|z| [95.0% Conf. Int.]
installation_costs -0.0007 4.28e-05 -16.741 0.000 -0.001 -0.001

Compare with mlogit

Look at the corresponding results from mlogit:

Call:
mlogit(formula = depvar ~ lcc | 0, data = H, method = "nr", print.level = 0)

Frequencies of alternatives:
      ec       er       gc       gr       hp 
0.071111 0.093333 0.636667 0.143333 0.055556 

nr method
5 iterations, 0h:0m:0s 
g'(-H)^-1g = 9.32E-05 
successive function values within tolerance limits 

Coefficients :
       Estimate  Std. Error t-value  Pr(>|t|)    
lcc -7.1585e-04  4.2761e-05 -16.741 < 2.2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Log-Likelihood: -1248.7

As before, all computed values agree except for the p-values, which we already know to be different due to the distribution being used to compute the p-values (t-distribution vs normal distribution).

5c. The model with installation cost, operating cost, and all intercepts except that of the "hp" alternative


In [14]:
# Create the model specification
model_3_spec = OrderedDict()
model_3_names = OrderedDict()

model_3_spec["intercept"] = range(1, 5)
model_3_names["intercept"] = ["ASC: {}".format(x) 
                              for x in ["gc", "gr", "ec", "er"]]

model_3_spec["installation_costs"] = [range(1, 6)]
model_3_names["installation_costs"] = ["installation_costs"]

model_3_spec["operating_costs"] = [range(1, 6)]
model_3_names["operating_costs"] = ["operating_costs"]

# Create an instance of the MNL model class
model_3 = pl.create_choice_model(data=long_heating_df,
                                 alt_id_col=heating_alt_id,
                                 obs_id_col=heating_obs_id_col,
                                 choice_col=heating_choice_column,
                                 specification=model_3_spec,
                                 model_type="MNL",
                                 names=model_3_names)

# Estimate the given model, starting from a point of all zeros
# as the initial values.
model_3.fit_mle(np.zeros(6))

# Look at the estimation summaries
model_3.get_statsmodels_summary()


Log-likelihood at zero: -1,448.4941
Initial Log-likelihood: -1,448.4941
Estimation Time: 0.02 seconds.
Final log-likelihood: -1,008.2287
/Users/timothyb0912/anaconda/lib/python2.7/site-packages/scipy/optimize/_minimize.py:382: RuntimeWarning: Method BFGS does not use Hessian information (hess).
  RuntimeWarning)
Out[14]:
Multinomial Logit Model Regression Results
Dep. Variable: choice No. Observations: 900
Model: Multinomial Logit Model Df Residuals: 894
Method: MLE Df Model: 6
Date: Mon, 14 Mar 2016 Pseudo R-squ.: 0.304
Time: 18:51:14 Pseudo R-bar-squ.: 0.300
converged: False Log-Likelihood: -1,008.229
LL-Null: -1,448.494
coef std err z P>|z| [95.0% Conf. Int.]
ASC: gc 1.7110 0.227 7.546 0.000 1.267 2.155
ASC: gr 0.3083 0.207 1.492 0.136 -0.097 0.713
ASC: ec 1.6588 0.448 3.699 0.000 0.780 2.538
ASC: er 1.8534 0.362 5.121 0.000 1.144 2.563
installation_costs -0.0015 0.001 -2.469 0.014 -0.003 -0.000
operating_costs -0.0070 0.002 -4.502 0.000 -0.010 -0.004

Compare with mlogit

Look at the corresponding results from mlogit:

Call:
mlogit(formula = depvar ~ ic + oc, data = H, reflevel = "hp", 
    method = "nr", print.level = 0)

Frequencies of alternatives:
      hp       ec       er       gc       gr 
0.055556 0.071111 0.093333 0.636667 0.143333 

nr method
6 iterations, 0h:0m:0s 
g'(-H)^-1g = 9.58E-06 
successive function values within tolerance limits 

Coefficients :
                  Estimate  Std. Error t-value  Pr(>|t|)    
ec:(intercept)  1.65884594  0.44841936  3.6993 0.0002162 ***
er:(intercept)  1.85343697  0.36195509  5.1206 3.045e-07 ***
gc:(intercept)  1.71097930  0.22674214  7.5459 4.485e-14 ***
gr:(intercept)  0.30826328  0.20659222  1.4921 0.1356640    
ic             -0.00153315  0.00062086 -2.4694 0.0135333 *  
oc             -0.00699637  0.00155408 -4.5019 6.734e-06 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Log-Likelihood: -1008.2
McFadden R^2:  0.013691 
Likelihood ratio test : chisq = 27.99 (p.value = 8.3572e-07)

Again, all calculated values except for the p-values and McFadden's $R^2$ seem to agree.

As noted in the mlogit benchmark # 1 notebook, the mlogit values for McFadden's $R^2$ seem to be incorrect. Based off of the formula: $$\begin{aligned} \textrm{McFadden's }R^2 &= 1 - \frac{\mathscr{L}_M}{\mathscr{L}_0} \\ \textrm{where } \mathscr{L}_M &= \textrm{the fitted log-likelihood} \\ \mathscr{L}_0 &= \textrm{the null log-likelihood}\end{aligned}$$ from "Coefficients of Determination for Multiple Logistic Regression Analysis" by Scott Menard (2000), The American Statistician, 54:1, 17-24, the calculated value of McFadden's R^2 should be 0.303947 as reported by pyLogit.

Note that the initial log-likelihood and McFadden $R^2$ are recomputed below for verification of its correctnes.


In [15]:
# Note that every observation in the Heating dataset
# has 5 available alternatives, therefore the null 
# probability is 0.20
null_prob = 0.20

# Calculate how many observations are in the Heating 
# dataset
num_heating_obs = wide_heating_df.shape[0]

# Calculate the Fishing dataset's null log-likelihood 
null_heating_log_likelihood = (num_heating_obs * 
                               np.log(null_prob))

# Determine whether pyLogit's null log-likelihood is correct
correct_null_ll = np.allclose(null_heating_log_likelihood,
                              model_3.null_log_likelihood)
print "pyLogit's null log-likelihood is correct:", correct_null_ll

# Calculate McFadden's R^2
mcfaddens_r2 = 1 - (model_3.log_likelihood / model_3.null_log_likelihood)
print "McFadden's R^2 is {:.5f}".format(mcfaddens_r2)


pyLogit's null log-likelihood is correct: True
McFadden's R^2 is 0.30395

5d. The model with installation cost divided by income, operating cost, and all intercepts except that for "hp"


In [16]:
# Create the model specification
model_4_spec = OrderedDict()
model_4_names = OrderedDict()

model_4_spec["intercept"] = range(1, 5)
model_4_names["intercept"] = ["ASC: {}".format(x) 
                              for x in ["gc", "gr", "ec", "er"]]

model_4_spec["installation_cost_burden"] = [range(1, 6)]
model_4_names["installation_cost_burden"] = ["installation_cost_burden"]

model_4_spec["operating_costs"] = [range(1, 6)]
model_4_names["operating_costs"] = ["operating_costs"]

# Create an instance of the MNL model class
model_4 = pl.create_choice_model(data=long_heating_df,
                                 alt_id_col=heating_alt_id,
                                 obs_id_col=heating_obs_id_col,
                                 choice_col=heating_choice_column,
                                 specification=model_4_spec,
                                 model_type="MNL",
                                 names=model_4_names)

# Estimate the given model, starting from a point of all zeros
# as the initial values.
model_4.fit_mle(np.zeros(6))

# Look at the estimation summaries
model_4.get_statsmodels_summary()


Log-likelihood at zero: -1,448.4941
Initial Log-likelihood: -1,448.4941
Estimation Time: 0.04 seconds.
Final log-likelihood: -1,010.1975
Out[16]:
Multinomial Logit Model Regression Results
Dep. Variable: choice No. Observations: 900
Model: Multinomial Logit Model Df Residuals: 894
Method: MLE Df Model: 6
Date: Mon, 14 Mar 2016 Pseudo R-squ.: 0.303
Time: 18:51:14 Pseudo R-bar-squ.: 0.298
converged: False Log-Likelihood: -1,010.198
LL-Null: -1,448.494
coef std err z P>|z| [95.0% Conf. Int.]
ASC: gc 1.9264 0.203 9.471 0.000 1.528 2.325
ASC: gr 0.4048 0.201 2.012 0.044 0.010 0.799
ASC: ec 1.8701 0.436 4.285 0.000 1.015 2.725
ASC: er 1.9341 0.360 5.372 0.000 1.228 2.640
installation_cost_burden -0.0028 0.002 -1.460 0.144 -0.006 0.001
operating_costs -0.0071 0.002 -4.580 0.000 -0.010 -0.004

Compare with mlogit

Look at the results from mlogit:

Call:
mlogit(formula = depvar ~ oc + I(ic/income), data = H, reflevel = "hp", 
    method = "nr", print.level = 0)

Frequencies of alternatives:
      hp       ec       er       gc       gr 
0.055556 0.071111 0.093333 0.636667 0.143333 

nr method
6 iterations, 0h:0m:0s 
g'(-H)^-1g = 1.03E-05 
successive function values within tolerance limits 

Coefficients :
                 Estimate Std. Error t-value  Pr(>|t|)    
ec:(intercept)  1.8700773  0.4364248  4.2850 1.827e-05 ***
er:(intercept)  1.9340707  0.3599991  5.3724 7.768e-08 ***
gc:(intercept)  1.9264254  0.2034031  9.4710 < 2.2e-16 ***
gr:(intercept)  0.4047710  0.2011694  2.0121   0.04421 *  
oc             -0.0071066  0.0015518 -4.5797 4.657e-06 ***
I(ic/income)   -0.0027658  0.0018944 -1.4600   0.14430    
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Log-Likelihood: -1010.2
McFadden R^2:  0.011765 
Likelihood ratio test : chisq = 24.052 (p.value = 5.9854e-06)

Again, all calculated values except for the p-values and McFadden's $R^2$ seem to agree.

5e. The model with intallation costs, operating costs, alternative specific income, and all intercepts for "hp."


In [17]:
# Create the model specification
model_5_spec = OrderedDict()
model_5_names = OrderedDict()

model_5_spec["intercept"] = range(1, 5)
model_5_names["intercept"] = ["ASC: {}".format(x) 
                              for x in ["gc", "gr", "ec", "er"]]

model_5_spec["installation_costs"] = [range(1, 6)]
model_5_names["installation_costs"] = ["installation_costs"]

model_5_spec["operating_costs"] = [range(1, 6)]
model_5_names["operating_costs"] = ["operating_costs"]

model_5_spec["income"] = range(1, 5)
model_5_names["income"] = ["income_{}".format(x)
                           for x in ["gc", "gr", "ec", "er"]]

# Create an instance of the MNL model class
model_5 = pl.create_choice_model(data=long_heating_df,
                                 alt_id_col=heating_alt_id,
                                 obs_id_col=heating_obs_id_col,
                                 choice_col=heating_choice_column,
                                 specification=model_5_spec,
                                 model_type="MNL",
                                 names=model_5_names)

# Estimate the given model, starting from a point of all zeros
# as the initial values.
model_5.fit_mle(np.zeros(10))

# Look at the estimation summaries
model_5.get_statsmodels_summary()


Log-likelihood at zero: -1,448.4941
Initial Log-likelihood: -1,448.4941
Estimation Time: 0.03 seconds.
Final log-likelihood: -1,005.8885
Out[17]:
Multinomial Logit Model Regression Results
Dep. Variable: choice No. Observations: 900
Model: Multinomial Logit Model Df Residuals: 890
Method: MLE Df Model: 10
Date: Mon, 14 Mar 2016 Pseudo R-squ.: 0.306
Time: 18:51:14 Pseudo R-bar-squ.: 0.299
converged: False Log-Likelihood: -1,005.889
LL-Null: -1,448.494
coef std err z P>|z| [95.0% Conf. Int.]
ASC: gc 2.0552 0.486 4.225 0.000 1.102 3.008
ASC: gr 1.1416 0.518 2.203 0.028 0.126 2.157
ASC: ec 1.9545 0.704 2.778 0.005 0.576 3.333
ASC: er 2.3056 0.624 3.695 0.000 1.083 3.528
installation_costs -0.0015 0.001 -2.466 0.014 -0.003 -0.000
operating_costs -0.0070 0.002 -4.479 0.000 -0.010 -0.004
income_gc -0.0718 0.089 -0.809 0.419 -0.246 0.102
income_gr -0.1798 0.100 -1.796 0.073 -0.376 0.016
income_ec -0.0636 0.113 -0.562 0.574 -0.286 0.158
income_er -0.0969 0.108 -0.901 0.368 -0.308 0.114

Compare with mlogit

Look at the output from mlogit:

Call:
mlogit(formula = depvar ~ oc + ic | income, data = H, reflevel = "hp", 
    method = "nr", print.level = 0)

Frequencies of alternatives:
      hp       ec       er       gc       gr 
0.055556 0.071111 0.093333 0.636667 0.143333 

nr method
6 iterations, 0h:0m:0s 
g'(-H)^-1g = 6.27E-06 
successive function values within tolerance limits 

Coefficients :
                  Estimate  Std. Error t-value  Pr(>|t|)    
ec:(intercept)  1.95445797  0.70353833  2.7780 0.0054688 ** 
er:(intercept)  2.30560852  0.62390478  3.6954 0.0002195 ***
gc:(intercept)  2.05517018  0.48639682  4.2253 2.386e-05 ***
gr:(intercept)  1.14158139  0.51828845  2.2026 0.0276231 *  
oc             -0.00696000  0.00155383 -4.4792 7.491e-06 ***
ic             -0.00153534  0.00062251 -2.4664 0.0136486 *  
ec:income      -0.06362917  0.11329865 -0.5616 0.5743846    
er:income      -0.09685787  0.10755423 -0.9005 0.3678281    
gc:income      -0.07178917  0.08878777 -0.8085 0.4187752    
gr:income      -0.17981159  0.10012691 -1.7958 0.0725205 .  
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Log-Likelihood: -1005.9
McFadden R^2:  0.01598 
Likelihood ratio test : chisq = 32.67 (p.value = 1.2134e-05)

As before, pyLogit and mlogit agree on all calculated values except for the p-values and McFadden's $R^2$.