econirl.CCP

class econirl.CCP(n_states=90, n_actions=2, discount=0.9999, utility='linear_cost', se_method='robust', verbose=False, num_policy_iterations=1)[source]

Bases: NFXP

Sklearn-style CCP estimator for dynamic discrete choice models.

The CCP (Conditional Choice Probability) approach estimates utility parameters using the Hotz-Miller inversion theorem. This is typically faster than NFXP because it avoids the nested fixed-point computation.

With num_policy_iterations=1, this is the classic Hotz-Miller estimator. With num_policy_iterations>1, this becomes the NPL (Nested Pseudo Likelihood) algorithm that iterates to the MLE.

Parameters:
  • n_states (int, default=90) – Number of discrete states (e.g., mileage bins).

  • n_actions (int, default=2) – Number of discrete actions (e.g., keep/replace).

  • discount (float, default=0.9999) – Time discount factor (beta).

  • utility (str or RewardSpec, default="linear_cost") – Utility specification. Pass "linear_cost" for the classic Rust bus model (u = -theta_c * s * (1-a) - RC * a), or a RewardSpec for custom features.

  • se_method (str, default="robust") – Method for computing standard errors. Options: “robust”, “asymptotic”.

  • verbose (bool, default=False) – Whether to print progress messages during estimation.

  • num_policy_iterations (int, default=1) – Number of policy iterations. K=1 is Hotz-Miller, K>1 is NPL. Set to -1 for convergence-based stopping.

Variables:
  • params (dict) – Estimated parameters after fitting. Keys are parameter names (e.g., “theta_c”, “RC”) and values are point estimates.

  • se (dict) – Standard errors for each parameter.

  • coef (numpy.ndarray) – Coefficients as a numpy array (sklearn convention).

  • log_likelihood (float) – Maximized log-likelihood value.

  • pvalues (dict) – P-values for each parameter (from Wald t-test).

  • policy (numpy.ndarray) – Estimated choice probabilities P(a|s) of shape (n_states, n_actions).

  • value (numpy.ndarray) – Estimated value function V(s) of shape (n_states,).

  • value_function (numpy.ndarray) – Alias for value_ (backward compatibility).

  • transitions (numpy.ndarray) – Transition probability matrix (n_states x n_states).

  • converged (bool) – Whether the optimization converged.

  • reward_spec (RewardSpec) – The reward specification used for estimation.

Examples

>>> from econirl.estimators import CCP
>>> import pandas as pd
>>>
>>> df = pd.DataFrame({
...     "bus_id": [0, 0, 1, 1],
...     "mileage": [10, 20, 15, 30],
...     "replaced": [0, 0, 0, 1],
... })
>>>
>>> # Hotz-Miller (fast, one-step)
>>> model = CCP(n_states=90)
>>> model.fit(df, state="mileage", action="replaced", id="bus_id")
>>>
>>> # NPL (iterates towards MLE)
>>> model_npl = CCP(n_states=90, num_policy_iterations=10)
>>> model_npl.fit(df, state="mileage", action="replaced", id="bus_id")

References

Hotz, V.J. and Miller, R.A. (1993). “Conditional Choice Probabilities

and the Estimation of Dynamic Models.” RES 60(3), 497-529.

Aguirregabiria, V. and Mira, P. (2002). “Swapping the Nested Fixed Point

Algorithm.” Econometrica 70(4), 1519-1543.

__init__(n_states=90, n_actions=2, discount=0.9999, utility='linear_cost', se_method='robust', verbose=False, num_policy_iterations=1)[source]

Initialize the CCP estimator.

Parameters:
  • n_states (int, default=90) – Number of discrete states.

  • n_actions (int, default=2) – Number of discrete actions.

  • discount (float, default=0.9999) – Time discount factor (beta).

  • utility (str or RewardSpec, default="linear_cost") – Utility specification to use. Pass "linear_cost" for the classic Rust bus model, or a RewardSpec for custom features.

  • se_method (str, default="robust") – Method for computing standard errors.

  • verbose (bool, default=False) – Whether to print progress messages.

  • num_policy_iterations (int, default=1) – Number of NPL iterations (K=1 is Hotz-Miller).

fit(data, state=None, action=None, id=None, transitions=None, reward=None)[source]

Fit the CCP estimator to data.

Parameters:
  • data (pandas.DataFrame or Panel or TrajectoryPanel) – Panel data with observations. When a DataFrame is passed, state, action, and id column names are required. When a Panel/TrajectoryPanel is passed, column names are ignored.

  • state (str, optional) – Column name for the state variable (required for DataFrame input).

  • action (str, optional) – Column name for the action variable (required for DataFrame input).

  • id (str, optional) – Column name for the individual identifier (required for DataFrame input).

  • transitions (numpy.ndarray, optional) – Pre-estimated transition matrix of shape (n_states, n_states). If None, transitions are estimated from the data.

  • reward (RewardSpec, optional) – Reward/utility specification. If provided, overrides the utility parameter passed at construction time.

Returns:

self – Returns self for method chaining.

Return type:

CCP

summary()[source]

Generate a formatted summary of estimation results.

Returns:

Human-readable summary of the estimation.

Return type:

str

conf_int(alpha=0.05)

Compute confidence intervals for parameters.

Parameters:

alpha (float, default=0.05) – Significance level. Returns (1 - alpha) confidence intervals.

Returns:

{param_name: (lower, upper)} confidence intervals.

Return type:

dict

Raises:

RuntimeError – If the model has not been fitted yet.

counterfactual(**param_changes)

Compute outcomes under different parameter values.

Performs counterfactual analysis by solving the dynamic programming problem under alternative parameter values. This enables “what if” questions like “what would the policy be if RC was 15 instead of 10?”

Parameters:

**param_changes (float) – Keyword arguments specifying parameter changes. Keys must be valid parameter names (e.g., “theta_c”, “RC”). Values are the counterfactual parameter values.

Returns:

Object containing: - params: Dictionary of all parameter values used - value_function: V(s) under new parameters - policy: P(a|s) under new parameters

Return type:

CounterfactualResult

Raises:
  • RuntimeError – If the model has not been fitted yet.

  • ValueError – If an unknown parameter name is provided.

Examples

>>> model = NFXP(n_states=90)
>>> model.fit(data, state="mileage", action="replaced", id="bus_id")
>>>
>>> # What if replacement cost was higher?
>>> cf = model.counterfactual(RC=15.0)
>>> print(f"Original RC: {model.params_['RC']:.2f}")
>>> print(f"Counterfactual RC: {cf.params['RC']:.2f}")
>>> print(f"P(replace|state=50) changes from "
...       f"{model.predict_proba(np.array([50]))[0,1]:.3f} to "
...       f"{cf.policy[50,1]:.3f}")
>>>
>>> # Multiple parameter changes
>>> cf2 = model.counterfactual(RC=15.0, theta_c=0.05)
predict_proba(states)

Predict choice probabilities for given states.

Parameters:

states (numpy.ndarray) – Array of state indices.

Returns:

Choice probabilities of shape (len(states), n_actions). Each row sums to 1.

Return type:

numpy.ndarray

property reward_matrix_: ndarray | None

Structural reward matrix R(s,a) of shape (n_states, n_actions).

Computes the utility matrix from the fitted parameters and the feature specification. Returns None if the model has not been fitted.

simulate(n_agents, n_periods, seed=None)

Simulate choices under the estimated policy.

Generates synthetic data by simulating agents making decisions according to the fitted model. Each agent starts at state 0 and evolves according to the estimated transition probabilities and choice probabilities.

Parameters:
  • n_agents (int) – Number of agents to simulate.

  • n_periods (int) – Number of time periods per agent.

  • seed (int, optional) – Random seed for reproducibility.

Returns:

DataFrame with columns: - agent_id: Identifier for each agent (0 to n_agents-1) - period: Time period (0 to n_periods-1) - state: State at the beginning of the period - action: Action taken (sampled from estimated policy)

Return type:

pandas.DataFrame

Raises:

RuntimeError – If the model has not been fitted yet.

Examples

>>> model = NFXP(n_states=90)
>>> model.fit(data, state="mileage", action="replaced", id="bus_id")
>>> sim_data = model.simulate(n_agents=100, n_periods=50, seed=42)
>>> print(sim_data.head())