Library of Models

[1]:
import pyRVtest
pyRVtest.__version__
[1]:
'0.2.0'

Here, we document the library of models that is currently supported by pyRVtest and how the user can specify them as a ModelFormulation. The current library of models includes:

  • Bertrand Competition with Differentiated Products

  • Cournot Competition with Differentiated Products

  • Monopoly (i.e., Perfect Collusion)

  • Bertrand and Cournot Competition with Profit Weights

  • Non-Profit Conduct Models

  • Marginal Cost Pricing (i.e., zero markup models)

  • Rule-of-Thumb Models (i.e., markups as a fixed percentage of price or cost)

  • Bertrand with Scaled Costs

  • Constant Markup Models (that do not vary with demand or cost)

  • Vertical Models

We also detail two options for how a user can test models outside this class.

Preliminaries and notation

Throughout, we consider settings in which potentially multi-product firms, indexed by \(f\) compete across markets, indexed by \(t\). In each market, there are \(J_t\) products offered. Each firm \(f\) offers a distinct subset of those products, \(\mathcal{J}_{ft}\). We use the \(jt\) index to denote observations at the product-market level, and we use the \(t\) index to denote the vector which stacks all \(J_t\) observations in market \(t\). Demand for product \(j\) in market \(t\), which depends on \(p_t\), the price of all products in the market, is denoted \(s_{jt}(p_t)\). Realizations of market shares across products in market \(t\) at the equilibrium prices \(p_t\) are denoted \(s_t\). We denote the \(J_t \times J_t\) matrix of own- and cross-price derivatives as \(\frac{\partial s_t}{\partial p_t}\), so that the \((j,k)\)-th element denotes the marginal effect of an increase in the price of product \(k\) on the market share of product \(j\).

The researcher observes, in each market \(t\), realizations of equilibrium prices and shares for a true model of conduct which generated the data: \(p_t\) and \(s_t\). The researcher does not know the true model, but wishes to test a menu of models, \(M\). For each model \(m\) in the menu, the stacked first order condition in market \(t\) can be expressed as:

\begin{equation} p_t - c_{mt} = \Delta_{mt} \end{equation}

Here, \(\Delta_{mt}\) are the stacked markups implied by model \(m\) in market \(t\). For the models we consider, the markups \(\Delta_{mt}\) can be expressed as known functions of prices and exogenous variables and typically depend on the demand system. Thus, given equilibrium outcomes, the known market structure (i.e., which firm sells which products), and the demand system, \(\Delta_{mt}\) can be computed. \(c_{mt}\) are the marginal costs implied by the model which satisfy the system of first order conditions.

To specify a menu of models in pyRVtest, the researcher creates a ModelFormulation for each of the models in the menu when defining the testing problem. In what follows, we show how to specify the ModelFormulation for the class of models that the code can currently handle.

Bertrand Competition: \(m=B\)

Suppose the researcher wants to include in the menu of models the Bertrand-Nash model of competition in prices. In market \(t\), firm \(f\) sets prices for all \(j\in\mathcal{J}_{ft}\) to maximize the sum of its profits across those products. Letting \(p_{ft}\) be the vector of those prices, the firm solves:

\begin{equation} \max_{p_{ft}} \sum_{j\in\mathcal{J}_{ft}} (p_{jt} - c_{jt}) s_{jt}(p_{t}) \end{equation}

The stacked first-order conditions across firms in market \(t\) can be written as \((p_{t} - c_{t}) = \Delta_{Bt}\) where \(\Delta_{Bt}\) is:

\begin{equation} \Delta_{Bt} \quad = \quad \left(\Omega_t \odot \frac{\partial s_t}{\partial p_t} \right)^{-1}s_t. \end{equation}

Here, \(\Omega_t\) is the standard \(J_t\times J_t\) ownership matrix so that the \((j,k)\)-th element is 1 if products \(j\) and \(k\) are sold by the same firm, and 0 otherwise. Furthermore, \(\odot\) denotes element-by-element multiplication.

To include Bertrand as one of the models to test with pyRVtest, the researcher must include in the product_data a column for which each row indicates the identity of the firm selling the corresponding product. If that column is named firm_ids, then the Bertrand model can be specified with the following ModelFormulation.

[2]:
model_formulation = pyRVtest.ModelFormulation(model_downstream='bertrand', ownership_downstream='firm_ids')

Note that we label the options model_downstream and ownership_downstream as the code also accommodates vertical models. See the example below.

Monopoly (i.e., Perfect Collusion): \(m=M\)

Now suppose that in market \(t\), prices \(p_{jt}\) are chosen to maximize the sum of its profits across all products in market \(t\), or:

\begin{equation} \max_{p_{t}} \sum_{j\in \mathcal{J}_{t}} (p_{jt} - c_{jt}) s_{jt}(p_{t}) \end{equation}

This can arise in settings with more than one firm if the firms are perfectly colluding. The stacked first-order conditions across firms in market \(t\) can be written as \((p_{t} - c_{t}) = \Delta_{Mt}\) where \(\Delta_{Mt}\) is:

\begin{equation} \Delta_{Mt} \quad = \quad \left(1_t \odot \frac{\partial s_t}{\partial p_t} \right)^{-1}s_t. \end{equation}

Here, \(1_t\) is a \(J_t\times J_t\) matrix of ones.

The researcher can specify Monopoly as one of the models to test with pyRVtest, using the following ModelFormulation. Note that here, if the researcher was to include a ownership_downstream option, the package will override this and build the ownership matrix in each market as a matrix of ones.

[3]:
model_formulation = pyRVtest.ModelFormulation(model_downstream='monopoly')

Cournot Competition with Differentiated Products: \(m=C\)

Suppose the researcher wants to include in the menu of models Cournot competition (quantity-setting) with differentiated products, in which each firm simultaneously chooses market shares for its \(\mathcal{J}_{ft}\) products, \(s_{ft}\), to maximize:

\begin{equation} \max_{s_{ft}} \sum_{j\in \mathcal{J}_{ft}} s_{jt} (p_{jt}(s_t) - c_{jt}) \end{equation}

where \(p_{jt}(s_t)\) represents inverse demand for product \(j\) in market \(t\). The stacked first-order conditions are \(p_{jt} - c_{jt} = \Delta_{Ct}\) where \(\Delta_{Ct}\) is:

\begin{equation} \Delta_{Ct} \quad = \quad \left(\Omega_t \odot \left(\frac{\partial s_t}{\partial p_t} \right)^{-1}\right)s_t. \end{equation}

Here, \(\Omega_t\) is the standard \(J_t\times J_t\) ownership matrix so that the \((j,k)\)-th element is 1 if product \(j\) and \(k\) are sold by the same firm, and 0 otherwise. Furthermore, \(\odot\) denotes element-by-element multiplication.

To include Cournot as one of the models to test with pyRVtest, the researcher must include in the product_data a column for which the i-th row indicates the identity of the firm selling the corresponding product. If that column is named firm_ids, then the Cournot model can be specified with the following ModelFormulation.

[4]:
model_formulation = pyRVtest.ModelFormulation(model_downstream='cournot', ownership_downstream='firm_ids')

Bertrand and Cournot with Profit Weights: \(m=PW(\lambda)\)

Suppose the researcher wants to include in the menu of models either price or quantity competition in which the firms partially internalize the effect of their actions on their rivals’ profits. This can occur, for example, in a model of common ownership (e.g., Backus, Conlon, and Sinkinson (2022)) or imperfect collusion (e.g., Miller and Weinberg (2017)). Now, the first order conditions of the Bertrand and Cournot models contain an ownership matrix specified by the user for which the \((j,k)\)-th element is \(\lambda_{jk}\).

For example, to include Bertrand with a given set of profit weights as one of the models to test with pyRVtest, the researcher specifies within the ModelFormulation, a kappa_specification as used in the build_ownership function in PyBLP:

[5]:
kappa_specification = 1
model_formulation = pyRVtest.ModelFormulation(model_downstream='bertrand', ownership_downstream='firm_ids', kappa_specification_downstream = kappa_specification)

Likewise, to include Cournot with the given profit weights, the ModelFormulation is:

[6]:
model_formulation = pyRVtest.ModelFormulation(model_downstream='cournot', ownership_downstream='firm_ids', kappa_specification_downstream = kappa_specification)

Non-profit Conduct: \(m=N(\lambda)\)

Suppose the researcher wants to model non-profit conduct where firms choose their own prices to maximize a weighted sum of profit and consumer surplus as in (Duarte, Magnolfi, and Roncoroni (2022)) where non-profit firms place a weight of \(1-\lambda \in (0,1)\) on welfare relative to profit. This can be achieved by augmenting the first order conditions of the Bertrand model above. Specifically, one adjusts \(\Omega_t\) by setting the \((j,k)\)-th element of the ownership matrix to \(1/\lambda_{jk}\) if the firm selling products \(j\) and \(k\) is a non-profit.

For example, to test a model in which non-profit firms have given welfare weights with pyRVtest, the researcher specifies within the ModelFormulation, a kappa_specification encoding those weights as used in the build_ownership function in PyBLP:

[7]:
model_formulation = pyRVtest.ModelFormulation(model_downstream='bertrand', ownership_downstream='firm_ids', kappa_specification_downstream = kappa_specification)

Marginal Cost Pricing (Perfect Competition / Zero Markup): \(m=PC\)

Consider a class of models where firms selling differentiated products set prices equal to marginal costs so that markups are zero, \(\Delta_{PCt} = 0\).

For example, to include marginal cost pricing in the menu of models to test with pyRVtest, the researcher includes the following ModelFormulation. Note that here, if the researcher was to include a ownership_downstream option, the package will override this set markups to zero.

[8]:
model_formulation = pyRVtest.ModelFormulation(model_downstream='perfect_competition')

Rule of Thumb Models: \(m=R(\lambda)\)

Consider a class of models where markups are a fraction \(\lambda\) of price or cost. For example, suppose the firm sets prices according to the rule-of-thumb: \(p_t = (1+\lambda) c_t\). In this case, the stacked first order conditions are \(p_t - c_t = \Delta_{Rt}\) where

\begin{equation} \Delta_{Rt} = \lambda c_t \end{equation}

Equivalently, markups can be equivalently expressed as a function of prices, or

\begin{equation} \Delta_{Rt} = \frac{\lambda}{1+\lambda}p_t \end{equation}

For now, pyRVtest only accommodates models where \(\lambda\) is constant across firms and markets.

For example, to include model of rule-of-thumb \(\lambda\), the researcher specifies within the ModelFormulation, a cost_scaling option equal to a column in the product_data containing the scalar value lmbda (If markups are equal to cost or equivalently 50% of price, then lmbda = 1. Instead, if markups are equal to 50% of cost or equivalently 1/3 of price, then lmbda = 0.5)

[9]:
lmbda = 'cost_scaling_column'
model_formulation = pyRVtest.ModelFormulation(model_downstream='perfect_competition', cost_scaling=lmbda)

Bertrand with Scaled Costs: \(m = SC(\lambda)\)

Next, consider a class of models where firms choose their own prices in market \(t\) to solve:

\begin{equation} \max_{p_{ft}} \sum_{j\in\mathcal{J}_{ft}} (p_{jt} - \lambda c_{jt}) s_{jt}(p_{t}) \end{equation}

The stacked first-order conditions across firms in market \(t\) can be written as \((p_{t} - c_{t}) = \Delta_{SCt}\) where

\begin{equation} \Delta_{SCt} = \Delta_{Bt} + (1-\lambda) c_{t} \end{equation}

and \(\Delta_{Bt}\) are the Bertrand markups.

Markups of this form arise in the model of collusion considered in Harrington (2023), where firms collude via cost coordination. These markups also arise in settings where two firms compete a la Bertrand in prices, but each one maximizes a weighted sum of profits and revenues, where \(\frac{1-\lambda}{\lambda}\) is the weight put on revenue relative to profit (e.g., Baumol (1958)).

For now, pyRVtest only accommodates models where \(\lambda\) is constant across firms and markets.

For example, to include model with cost-scaling \(\lambda\), the researcher specifies within the ModelFormulation, a cost_scaling option equal to the scalar value lmbda:

[10]:
model_formulation = pyRVtest.ModelFormulation(model_downstream='bertrand', ownership_downstream='firm_ids', cost_scaling=lmbda)

Constant Markup Models: \(m = CM(\eta)\)

Next, consider a class of models where the markup for each product \(j\) in each market \(t\) is equal to a constant \(\eta_{jt}\) which does not depend on demand or cost, or

\begin{equation} \Delta_{CMjt} = \eta_{jt} \end{equation}

The researcher can include a constant markup model in the menu to be tested by using within ModelFormulation the user_specified_markups option. Here, the user creates a column in product_data where each row is the value \(\eta_{jt}\) corresponding to product \(j\) in market \(t\). If this column is named \(\texttt{eta}\), the ModelFormulation is:

[11]:
model_formulation = pyRVtest.ModelFormulation(user_supplied_markups='eta')

Vertical Models with unobserved wholesale costs

Now we consider models with vertical interactions between retailers and wholesalers (we denote each with superscript \(r\) and \(w\) respectively). Consider the simple linear pricing model from Villas-Boas (2007) where wholesalers individually set wholesale prices \(p^w_t\) to maximize their profits in market \(t\), and, given \(p^w_t\), retailers choose retailer prices \(p^r_t\) to maximize their profits in the same market. Assuming that wholesale prices are unobserved, we can sum the stacked first order conditions for wholesalers and retailers to obtain:

\begin{equation} p^r_{t} - c^r_t - c^w_t = \Delta^r_{Bt} + \Delta^w_{Bt} \end{equation}

where \(\Delta^r_{Bt}\), the vector of retailer Bertrand markups, are:

\begin{equation} \Delta^r_{Bt} = -\left(\Omega^r_t \odot \frac{\partial s_t}{\partial p^r_t} \right)^{-1}s_t \end{equation}

and \(\Delta^w_{Bt}\), the vector of wholesaler Bertrand markups, are:

\begin{equation} \Delta^w_{Bt}-\left(\Omega^w_t \odot \frac{\partial s_t}{\partial p^w_t} \right)^{-1}s_t \end{equation}

If a researcher wants to include linear pricing in the menu of models to be tested, she must specify as columns of product_data both retailer ids and wholesaler ids. Assuming these are called, respectively, retailer_ids and wholesaler_ids, the following ModelFormulation is used:

[12]:
model_formulation = pyRVtest.ModelFormulation(model_downstream='bertrand', ownership_downstream='retailer_ids', model_upstream='bertrand', ownership_upstream='wholesaler_ids')

One could allow for a different model upstream or downstream by changing the name of model_downstream and model_upstream (for example, in the context of consumer packaged goods market, if each store is a market, then the downstream model is monopoly). One can also allow for profit weights or non-profit conduct in \(\Omega^r_t\) and \(\Omega^w_t\) by specifying kappa_specification_downstream and kappa_specification_upstream. A ModelFormulation can also accommodate partial vertical integration in a market using the vertical_integration option. In this case, the product_data must contain a column which equals one if the product is vertically integrated in the given market and zero otherwise. Supposing this column is named vi_id, the linear pricing model with partial vertical integration can be specified with the following ModelFormulation

[13]:
model_formulation = pyRVtest.ModelFormulation(model_downstream='bertrand', ownership_downstream='retailer_ids', model_upstream='bertrand', ownership_upstream='wholesaler_ids', vertical_integration='vi_id')

Options to Test Other Models

If the user wants to include in the menu models not included in the current library, there are two options.

First, if the model of conduct implies a vector of markups which are a known function of the ownership matrix, the response matrix, or shares, then the user can pass that function using the custom_model_specification input for a ModelFormulation. This input takes a dictionary where the key is the custom model name, and the value is a string formula to be evaluated. For example, if we wanted to test the Bertrand markups using this option (assuming Bertrand was not already part of our menu of models), we would write:

[14]:
model_formulation = pyRVtest.ModelFormulation(model_downstream='other', custom_model_specification={'bertrand': '-inv(ownership_matrix * response_matrix) @ shares'})

Otherwise, if the markups the user wishes to test are not a known function of the ownership matrix, the response matrix, or shares, the user can pass an arbitrary vector using the user_specified_markups option as illustrated for the Constant Markup Models above. For example, if the user creates a column in the product_data called my_markups, the user can test these markups by defining the following ModelFormulation:

[15]:
model_formulation = pyRVtest.ModelFormulation(user_supplied_markups='my_markups')

Please note, when using the option user_specified_markups, \(\textbf{the code is not able to adjust}\) the pairwise RV test statistics, the model confidence set, and the pairwise F-statistics for the errors coming from demand estimation or for clustering.