Python: merger simulations

In this notebook we introduce a simple model to do merger simulations. The analysis here is based on Epstein and Rubinfeld (2001).

The advantage of this approach is that the model can be calibrated with relatively little information. In particular, we need to know

  • the market shares of the firms in the relevant market,

  • elasticity of market demand,

  • own price elasticity for one firm and

  • efficiency gains (claimed by firms).

Once, we know these things, we can simulate the market outcome before and after the merger. If we do not know one or more parameters, we can do sensitivity analysis: simply let the parameter run over a vector of values.

what is the use of merger simulation?

Merger simulation is not a crystal ball! Economists cannot predict the future. By applying a model to the data that we have, we can think about the problem (effects of the merger, in this case) in a more structured way. In particular, we can think about the merger in a way that others can replicate.

However, every model makes assumptions and if these assumptions do not apply, the outcome of the model will be incorrect. In other words, the predictions of the model need to be handled with care.

The model that we use here, makes the following assumptions:

    1. Proportionality: if a firm raises its price, it loses market share; this lost market share is allocated to the other firms in the industry proportionally to these firms’ market shares

    1. Homogeneity: if all firms in the market raise their price by the same percentage, market shares are unaffected

    1. Adding-up: market shares of all firms (brands) in the market add up to 1.

Although these assumptions are not unreasonable, they are not “innocent” either.

First, we import some libraries that we will use:

from scipy import optimize,arange
from numpy import array
import matplotlib.pyplot as plt
from math import log
%matplotlib inline

demand

In this notebook we assume that there are 3 firms in the market, named “firm_1, firm_2, firm_3”. If you use this notebook for a real case, make sure you call the firms by their name, like “microsoft, google, facebook” etc. This makes the code more readable than working with firms 1,2,3 and forcing the reader at each step to remember that firm 1 was supposed to be microsoft.

The market share of firm \(i\) is defined as \(s_i = \frac{p_i q_i}{P Q}\), where \(p_i\) denotes \(i\)’s price, \(q_i\) its quantity, \(Q\) total output on the market and \(P\) the aggregate industry price index. We assume that this price index is given by \(\ln(P)=\sum_{i=1}^3 s_i \ln(p_i)\).

Firm \(i\)’s demand in this model is written in terms of its market share:

\[(D) \hspace{5mm} s_i = a_i + b_{ii} \ln(p_i) + b_{ij} \ln(p_j) + b_{ik} \ln(p_k)\]

calibration

Although this demand structure is a bit unusual, it has the following useful property:

  • if we know

    • market demand elasticity \(\varepsilon = \frac{d \ln(Q)}{d \ln (P)}\)

    • one firm’s own price elasticity \(\varepsilon_{ii} = \frac{d \ln(q_i)}{d \ln (p_i)}\)

    • market shares \(s_i \geq 0\) for \(i=1,...,n\) with \(\sum_{i=1}^n s_i =1\)

  • if we are willing to assume (P), (H) and (A)

  • then we know all coefficients \(b_{ij}\) and demand elasticities \(\varepsilon_{ij}\)

See the paper for a proof.

One way to find firm \(i\)’s own price elasticity is to estimate \(i\)’s price cost margin, \(\mu_i\). Since firms set their price (cost margin) satisfying \(\mu_i = -1/\varepsilon_{ii}\), we know \(\varepsilon_{ii}\) if we know the price cost margin.

If you do not know a parameter very well, then define an interval for it and do the merger simulation for each value in the interval. This will give you a good idea of how sensitive the simulation is for the value of this parameter.

illustration

As an illustration of how a merger simulation works, we will replicate the example in table 1 on page 895 of Epstein and Rubinfeld (2001).

Don’t worry about this class definition, we use it below to get decent tables with results in the notebook.

class ListTable(list):
    """ Overridden list class which takes a 2-dimensional list of
        the form [[1,2,3],[4,5,6]], and renders an HTML Table in
        IPython Notebook. """

    def _repr_html_(self):
        html = ["<table>"]
        for row in self:
            html.append("<tr>")

            for col in row:
                html.append("<td>{0}</td>".format(col))

            html.append("</tr>")
        html.append("</table>")
        return ''.join(html)

We create python dictionaries for firms’ market shares and elasticities. In this way, we can refer to firm 1’s market share as market_share['firm1']. We check that the market shares are all between 0 and 1. Further, a firm’s own price elasticity is always larger (in absolute value) than the demand elasticity. Indeed, when a firm raises its price by 1%, a consumer can either not buy the product at all or buy it from another firm.

The equations for the price elasticities are derived in the paper:

\[\begin{split}(E) \hspace{5mm} e_{ij} = \begin{cases} -1 + \frac{b_{ii}}{s_i} + s_i*(\varepsilon +1) & \text{ if } i=j \\ \frac{b_{ij}}{s_i} + s_j* (\varepsilon +1) & \text{ otherwise } \end{cases}\end{split}\]

If you use this file to do your own merger simulation, adjust the values below.

market_share = {} # creates a dictionary with market shares
elasticity = {}   # dictionaries of firm level own and cross elasticities

firms = ['firm1','firm2','firm3'] # names of the firms in a python list
market_share['firm2'] = 0.3
market_share['firm3'] = 0.5
market_share['firm1'] = 1 - sum(market_share[firm] for firm in ['firm2','firm3'])

for firm in firms:
    if (market_share[firm] >= 0 and market_share[firm] <= 1):
        print "market share of firm: "+ firm + " is given by %1.2f" % market_share[firm]
    else: print "something wrong with the market share of the firm: "+firm

market_elasticity = -1.0 # market demand elasticity
elasticity['firm1','firm1'] = -3.0 # own elasticity of firm1

if market_elasticity < elasticity['firm1','firm1']:
    print "error: a firm's own elasticity exceeds (in absolute value) the market elasticity"
market share of firm: firm1 is given by 0.20
market share of firm: firm2 is given by 0.30
market share of firm: firm3 is given by 0.50

Now we can calculate the relevant parameters of the demand structure. See the paper for the derivation of these expressions.

We create a dictionary to store the \(b_{ij}\)-coefficients in demand system (D) above. Using the \(b_{ij}\)’s, we calculate the firm level (own and cross) elasticities \(e_{ij}\). The assumption is that the \(b_{ij}\)-coefficients are exogenous, i.e. do not change after the merger.

As the merger affects firms’ market shares, it does change the firm elasticities.

b = {} # dictionary of b-coefficients of demand system (D) above
b['firm1','firm1'] = market_share['firm1']*(elasticity['firm1','firm1']+1-market_share['firm1']*(market_elasticity+1))

for firm in firms:
    b[firm,firm]=(market_share[firm]*(1-market_share[firm]))/(market_share['firm1']*(1-market_share['firm1']))*b['firm1','firm1']

for one_firm in firms:
    for other_firm in firms:
        if one_firm == other_firm:
            b[one_firm,one_firm]=(market_share[one_firm]*(1-market_share[one_firm]))/(market_share['firm1']*(1-market_share['firm1']))*b['firm1','firm1']
        else:
            b[one_firm,other_firm] = - market_share[one_firm]/(1-market_share[other_firm])*b[other_firm,other_firm]

for one_firm in firms:
    for other_firm in firms:
        if one_firm == other_firm:
            elasticity[one_firm,one_firm] = -1 + b[one_firm,one_firm]/market_share[one_firm]+market_share[one_firm]*(market_elasticity+1)
        else:
            elasticity[one_firm,other_firm] = b[one_firm,other_firm]/market_share[one_firm]+market_share[other_firm]*(market_elasticity+1)

table = ListTable()
table.append(['b-coeff.', 'firm1','firm2','firm3'])
for one_firm in firms:
    row = []
    row.append(one_firm)
    for other_firm in firms:
        row.append("%0.3f" % b[one_firm,other_firm])
    table.append(row)
print "table of b coefficients:"
table
table of b coefficients:
b-coeff.firm1firm2firm3
firm1-0.4000.1500.250
firm20.150-0.5250.375
firm30.2500.375-0.625
table = ListTable()
table.append(['elast.', 'firm1','firm2','firm3'])
for one_firm in firms:
    row = []
    row.append(one_firm)
    for other_firm in firms:
        row.append("%0.3f" % elasticity[one_firm,other_firm])
    table.append(row)
print "table of elasticities:"
table
table of elasticities:
elast.firm1firm2firm3
firm1-3.0000.7501.250
firm20.500-2.7501.250
firm30.5000.750-2.250

A profit maximizing firm \(i\) sets its margin equal to \(\mu_i = -1/e_{ii}\). This can be derived as follows. A firm chooses its price level \(p_i\) to maximize profits:

\[\pi_i = p_i q_i(p_i,p_{-i}) - c_i(q_i(p_i,p_{-i}))\]

First order condition can be written as

\[q_i + (p_i - c_q) \frac{\partial q_i}{\partial p_i} = 0\]

which can be written as

\[\mu_i = \frac{p_i - c_q}{p_i} = \frac{dp_i}{dq_i} \frac{q_i}{p_i} = \frac{-1}{e_{ii}}\]

Hence we can calculate the margin for each firm as follows:

margin = {}
for firm in firms:
    margin[firm] = -1.0/elasticity[firm,firm]

The paper shows that the ex post merger outcome in terms of market share, \(s_j^p\), price cost margin, \(\mu_j^p\), and price change \(\delta_j = \frac{p_j^p - p_j}{p_j}\) (9 variables with 3 firms) satisfies the following 9 equations:

where the efficiency gain is defined as

\[\gamma_i = \frac{c_i^p-c_i}{c_i}\]

the percentage change in \(i\)’s costs \(c_i\) due to the merger.

In addition we have the elasticity equations (E) above with post-merger market shares, denoted \(s_j^p\).

Details are in the paper, we give a sketch of the proof of the equations above. The equation for \(s_i^p\) follows from writing the (D) equations for both \(s_i\) –as above– and for \(s_i^p\). Then take the difference of these equations and note that \(1+\delta_i=p^p_i/p_i\) and \(\ln(p_i^p)-\ln(p_i)=\ln(1+\delta_i)\).

For the non merging firm we have \(\mu_3 = -1/e_{33}^p\) as before. Now, consider the first order condition for \(p_j\) for the merged firm (consisting of \(j\) and \(k\)):

\[\frac{\partial q_j}{\partial p_j} (p_j - c_j) + q_j + \frac{\partial q_k}{\partial p_j} (p_k - c_k)\]

which can be rewritten as

\[s_j^p = -\varepsilon_{jj}^p s_j^p \mu_j^p -\varepsilon_{kj}^p s_k^p \mu_k^p\]

Finally, the expression for \(\mu_i^p\) follows from the definitions of \(\gamma_i,\delta_i\) and \(\mu_i\).

merging_firms = ['firm1','firm2']                      # list of the merging firms
nonmerging_firms = list(set(firms)-set(merging_firms)) # list of non-merging firms
efficiency_gains = {} # dictionary with efficiency gains --due to the merger-- for the firms
for firm in merging_firms:
    efficiency_gains[firm] = 0.0
for firm in nonmerging_firms:
    efficiency_gains[firm] = 0.0

We create dictionaries for post merger market shares (\(s_i^p\)), elasticities (\(e_{ij}^p\)), margins (\(\mu_i^p\)) and price changes (\(\delta_i\)). Then we create a vector function with all the equations above. We look for the values of \(s_i^p,e_{ij}^p,\mu_i^p,\delta_i\) such that this vector function equals zero (all equations are satisfied).

We form a list of equations and append it with new equations. For a given guess of the values \(s_i^p,e_{ij}^p,\mu_i^p,\delta_i\) equations returns the extent to which the equations are satisfied. If equations=0, all equations are satisfied and we find the post merger equilibrium.

post_marketshare = {}
post_elasticity = {}
post_margin = {}
price_change = {}

def vector_function(post_marketshare,post_elasticity,post_margin,price_change):
    equations = []
    for firm in sorted(firms):
        equations.append(post_marketshare[firm]-(market_share[firm]+sum([b[firm,other_firm]*log(1+price_change[other_firm]) for other_firm in firms])))
        equations.append(post_margin[firm] - (1-(1+efficiency_gains[firm])/(1+price_change[firm])*(1-margin[firm])))
    for firm in sorted(nonmerging_firms):
        equations.append(post_margin[firm] + 1/post_elasticity[firm,firm])
        equations.append(post_elasticity[firm,firm] - (-1 + b[firm,firm]/post_marketshare[firm]+post_marketshare[firm]*(market_elasticity+1)))
    for firm in sorted(merging_firms):
        equations.append(post_marketshare[firm]+sum(post_elasticity[other_firm,firm]*post_marketshare[other_firm]*post_margin[other_firm] for other_firm in sorted(merging_firms)))
        for other_firm in sorted(merging_firms):
            equations.append(post_elasticity[firm,other_firm] - (-(firm==other_firm) + b[firm,other_firm]/post_marketshare[firm]+post_marketshare[other_firm]*(market_elasticity+1)))
    return equations

As the solver works with a vector \(x\) for the values (not a collection of dictionaries), we define a wrapper function which turns \(x\) to the values in the dictionaries. Once we have the solution \(x\) we ‘unwrap’ it into values for our dictionaries.

def wrapper_function(x): # the solver used below needs a vector x as input, not a collection of dictionaries
    counter = 0
    for dict in [post_marketshare,post_margin,price_change]:
        for firm in sorted(firms):
            dict[firm] = x[counter]
            counter +=1
    for firm in sorted(nonmerging_firms):
        post_elasticity[firm, firm] = x[counter]
        counter +=1
    for firm in sorted(merging_firms):
        for other_firm in sorted(merging_firms):
            post_elasticity[firm, other_firm] = x[counter]
            counter +=1
    return vector_function(post_marketshare,post_elasticity,post_margin,price_change)

def unwrap(x):   # once we have the equilibrium solution as a vector x, we go back to dictionaries
    counter = 0  # as they are easier to work with
    for dict in [post_marketshare,post_margin,price_change]:
        for firm in sorted(firms):
            dict[firm] = x[counter]
            counter +=1
    for firm in sorted(nonmerging_firms):
        post_elasticity[firm, firm] = x[counter]
        counter +=1
    for firm in sorted(merging_firms):
        for other_firm in sorted(merging_firms):
            post_elasticity[firm, other_firm] = x[counter]
            counter +=1
    return [post_marketshare,post_elasticity,post_margin,price_change]

The solver needs an initial guess for the post merger solution; we use the pre-merger values as initial guess.

def initial_value(): # initial value based on before merger values
    x = []
    for dict in [market_share,margin]:
        for firm in sorted(firms):
            x.append(dict[firm])
    for firm in sorted(firms):
        x.append(0.0) # price change
    for firm in sorted(nonmerging_firms):
        x.append(elasticity[firm, firm])
    for firm in sorted(merging_firms):
        for other_firm in sorted(merging_firms):
            x.append(elasticity[firm, other_firm])
    return x

We use the fsolve routine to find the values where vector_function equals 0 (where all equations hold). We solve this “via” wrapper_function as fsolve expects a vector \(x\) as input, not a list of dictionaries. We print the relevant post merger values in a table.

def after_merger_values():
    outcome = unwrap(optimize.fsolve(wrapper_function, initial_value()))
    table = ListTable()
    table.append(['firm', 'marketshare','margin','price increase'])
    for firm in firms:
        table.append([firm, "%0.3f" % outcome[0][firm],"%0.3f" % outcome[2][firm],"%0.3f" % outcome[3][firm]])
    print "table of post merger outcomes:"
    return table
after_merger_values()
table of post merger outcomes:
firmmarketsharemarginprice increase
firm10.1740.4140.138
firm20.2810.4250.108
firm30.5460.4660.041

As one would expect, a merger between firms producing substitutes with no efficiency gains leads to higher prices for all firms. The market share of the firm that does not merge (firm3) increases due to the merger as prices increase more for the merging firms.