numDraws for mixed logit models increased from 50 to 500. Draw counts in the tens (the historical convention: {mlogit} and {gmnl} default to 40, Stata's mixlogit to 50) produce noticeably unstable simulated log-likelihoods, especially with many random parameters -- large enough to change model-selection decisions. With the compiled backend the extra cost is a very low overhead on typical models, so a statistically sound default is now affordable. Models estimated with the default will produce (better, but) different results than under previous versions; set numDraws = 50 explicitly to reproduce old results.parallel::mclapply()/PSOCK clusters, which gives consistent behavior across platforms (no separate Windows code path). Behavior is otherwise unchanged: the cores are used to run the multistart iterations in parallel, and each model is estimated single-threaded (benchmarking shows this is more efficient than using threads to speed up individual models). The message printed at the start of a multistart now says so. Advanced users can nest the two forms of parallelism by explicitly setting numThreads alongside a multistart. logitr runs its daemons on a dedicated {mirai} compute profile, so it will not interfere with (or tear down) any daemons you have set up yourself.sd = s and sd = -s describe the same model), so only the magnitude is meaningful. This avoids the confusion of occasionally seeing a negative standard deviation. The sign change is implemented as an exact relabeling of the converged model (the standard draw columns of flipped parameters are negated along with the coefficient), so the reported log-likelihood, gradient, standard errors, and predictions are exactly those of the converged solution. Correlated models are unaffected, since there the parameters are Cholesky factors whose signs jointly determine the covariance matrix.backend argument. This is now the default for mixed logit (MXL) models (backend = "cpp"), and is roughly 4x faster than the previous R implementation (and faster still with multiple threads; see numThreads) while producing the same results to floating-point precision. Multinomial logit (MNL) models continue to use the R implementation. All mixed logit model types are supported: preference and willingness-to-pay (WTP) space, uncorrelated and correlated heterogeneity, and normal, log-normal, and censored-normal parameter distributions. Set backend = "cpu" to use the native R implementation instead (for example if you need exactly bit-reproducible results). Note that installing the development version from source now requires a C++ compiler (see the installation instructions); the released CRAN version does not.numThreads argument (used by backend = "cpp"). The random draws are processed in parallel, giving further speedups on top of the compiled backend that scale with the number of cores. Defaults to using a single thread when running a parallel multistart (numMultiStarts > 1) and all but one of the available cores otherwise.drawType for mixed logit models from "halton" to "sobol". Sobol draws are better distributed than Halton draws in higher dimensions, where Halton sequences become correlated (roughly when there are more than 5 random parameters). Both are deterministic, so results remain reproducible.drawType = "mlhs" (Hess, Train, and Polak 2006). MLHS draws typically reach a given level of accuracy with fewer draws than Halton draws in higher dimensions. Unlike Halton and Sobol draws, MLHS draws are randomized and are therefore controlled by set.seed().ftol_rel and ftol_abs changed from 1e-6 to 1e-10 (the parameter tolerances xtol_rel / xtol_abs are unchanged at 1e-6). With the looser value, runs could stop as soon as the log-likelihood stalled between iterations, sometimes at points with clearly non-zero gradients -- which is what occasionally produced NaN standard errors (an indefinite hessian at a non-converged point). The tighter default costs only a few percent more iterations (negligible with the compiled backend) and returns solutions with gradients near zero. Set options = list(ftol_rel = 1e-6, ftol_abs = 1e-6, ...) to reproduce old behavior.randScale = "cn"). Draws where the scale censors to zero previously produced NaN entries in the gradient (a 0/0 in the scale parameter's partials), which caused optimization failures.numDrawsBatch argument to stream the random draws in batches when evaluating the mixed logit log-likelihood with backend = "cpu". This keeps peak memory bounded by the batch size rather than the total number of draws, which makes large draw counts (e.g. 10,000+) feasible on the R backend. By default logitr streams automatically only when the draws would otherwise exceed an internal memory budget, so typical models are unaffected. The default "cpp" backend is memory-flat in the number of draws and ignores this argument.broom.helpers (>= 1.15.0) and gtsummary (>= 2.0.0) in DESCRIPTION to accord with an update in {gtsummary}.convergence.Rmd vignette to not run any actual code using other packages to fix issue on CRAN where not all packages are available on all platforms. Now the results are hard-coded in place.makeObsID() where table() was sorting the results stored in reps, which has to be manually undone. Fixes #50.predict() method to use the interval and level arguments of more standard predict() methods.ci() function.logit_probs() function.adjustFactorLevels() where the levels_orig object was being accidentally overwritten.adjustFactorLevels() to checkFactorLevels().predict() method would error if factor levels were missing in newdata.tidy(), glance(), and augment() methods for use with the {broom} package.model.matrix.logitr(), terms.logitr(), and model.frame.logitr() methods for use with {broom.helpers}.panelID and clusterUD variables a sequentially increasing numeric vector and to stop the program if there are any repeated IDs in these variables.obsID variable a sequentially increasing numeric vector (this was previously done but was accidentally removed in prior updates).modelSpace variable along inside the vcov() method.runtimes, was included, which is exported from the colab notebook used for benchmarking here: https://colab.research.google.com/drive/1vYlBdJd4xCV43UwJ33XXpO3Ys8xWkuxx?usp=sharingdrawType argument.price to scalePar to be more general.randPrice to randScale to be more general.modelSpace argument is no longer required for specifying a WTP space model as it is redundant. Including a scalePar argument is enough to determine that it is a WTP space model.correlation argument to include correlated heterogeneity.obsID and outcome arguments.vcov.logitr() method now returns object$vcov if the user set vcov = TRUE during estimation (avoids a redundant calculation of vcov, which is more efficient).apolloModeChoiceData, electricityfquantile() function, which is a faster implementation of the stats::quantile() function.predict.logitr() method was added for making probability and choice predictions from logitr class objects.predictProbs() and predictChoices() functions were depreciated.fitted.logitr() and residuals.logitr() methods.predict argument to the main logitr() function which controls whether predicted probabilities, fitted.values, and residuals are included in the returned object. Default setting is TRUE.se.logitr() method.vcov argument to the logitr() function.options argument and are now passed directly as arguments to logitr(). These include: numMultiStarts, useAnalyticGrad, scaleInputs, startParBounds, standardDraws, numDraws, startVals. The options argument is now only used for options to control the optimization handled by nloptr().logitr() function were changed to make them easier to understand: choiceName became choice, obsIDName became obsID, parNames became pars, priceName became price, weightsName became weights, clusterName became cluster. If used, old names will be passed to the new argument names and a warning will be displayed.print.logitr(), logLik.logitr(), coef.summary.logitr(), vcov.logitr(), terms.logitr()summary.logitr() and coef.logitr() methods for better printing, now using printCoefmat().wtp() and wtpCompare() functionsaltIDName argument to predictChoices() and predictProbs() to preserve the row order of predictions for each alternative in each set of alternatives. Closes issue #13.Added support for panel data in the log-likelihood function and gradients
Major changes were made to the gradient functions, which dramatically improved computational efficiency. MNL and MXL models in either preference or WTP spaces now use the faster implementation of the logit calculations.
This version was the first implementation of an alternative approach for computing the logit probabilities, which increased computational speed. Specifically, the formulation was to compute P = 1 / (1 + sum(exp(V - V_chosen)))
The vcov() method was modified such that it computes the covariance post model estimation. Previously, the covariance matrix was being computed internally in the logitr() function, and vcov() just returned this value, which was computationally much slower.
Several breaking changes in this version.
choiceName --> choice, obsIDName --> obsID, parNames --> pars, priceName --> price, weightsName --> weights, clusterName --> cluster.options argument and are now passed directly as arguments to logitr(). These include: numMultiStarts, useAnalyticGrad, scaleInputs, startParBounds, standardDraws, numDraws, startVals.summary.logitr() and coef.logitr() methods for better printing, using printCoefmat()print.logitr(), logLik.logitr(), coef.summary.logitr(), vcov.logitr()wtp() and wtpCompare() functionsaltIDName argument to predictChoices() and predictProbs() to preserve the row order of predictions for each alternative in each set of alternatives. Closes issue #13.predictChoices() and predictProbs(), and , depreciated simulateShares().recodeData() function to improve encoding efficiency.dummyCode()predictChoices() function.predictShares() function, depreciating simulateShares().recodeData() and dummyCode() functions for improved speed.simulateShares() to work with the automatic dummy coding from the revised recodeData() and dummyCode() functions.simulateShares() to compute shares for multiple sets of alternatives.simulateShares(), the shares were not correctly computed with a WTP space model because price was still being multiplied by -1. This has been corrected.standardDraws() functiongetCatVarDummyNames() - previously used string matching, which can accidentally match with other similarly-named variables.rowsum() where the reorder argument was set to TRUE, which resulted in wrong logit calculations unless the obsID happened to be already sorted.algorithm to the options input, with the default being set to "NLOPT_LD_LBFGS".getParTypes() function - previously was not returning the correct parNames for continuous vs. discrete variables."pref" or "wtp".priceName argument is only used when the modelSpace argument is set to "wtp".getCoefTable() functionWeighted models, new dataset, new encoding features
yogurt, cars_china, cars_usdummyCode() function for automatically creating dummy-coded variables in a data frame.modelInputs where obsID was not a vector for tibble inputsNew simulation functionality
simulateShares(). This is similar to the predict() function in mlogit.options() function. I found this just far too confusing, and instead encourage users to supply a WTP space model with the computed WTP from a preference space model as starting parameters.summary() and main logitr() functions to keep the basic information (run #, log-likelihood value, number of iterations, and output status) whenever numMultistarts > 1. Previously this information was only kept if keepAllRuns was set to TRUE.Updates to options and a few small bug fixes
logitr.summary() function and instead added the logitr class to all the models and renamed the summary function to summary.logitr(). Now you can just use the standard summary() function to summarize model results.TRUE.Full reboot of logitr!
Long overdue, I decided to give the logitr program a full overhaul. This is the first version that is compiled as a proper R package that can be directly installed from GitHub. This version is much more robust and flexible than the prior, clunky collection of R files that I had previously been using to estimate logit models.