Assignment 1: Linear Demand Estimation¶

This homework assignment will give you practice working with the basics of the multinomial logit and nested logit demand models. It is closely related to Berry (1994) but is limited in terms of consumer heterogeneity. The following homework (problem set 2) will introduce simulation-based estimators to provide much richer sources of consumer heterogeneity.

The file \textbf{product_data.csv} contains information on 10 different soft drinks, for 100 different markets (or time periods). This dataset contains the following variables: product_ID (the product ID), nest (a variable indicating if the product is a diet or a regular soda), price (the product's price during period t, in USD), market_share (the market share of the product during period t), sugar (the grams of sugar contained in product $j$ at time $t$), caffeine (the milligrams of caffeine contained in product $j$ at time $t$), caffeine_extract_price (the price of caffeine extract paid by the firm manufacturing product $j$ at time $t$), corn_syrup_price (the price of high fructose corn syrup paid by the firm manufacturing product $j$ at time $t$), and $t$ (the observation's time period).

In [1]:
# Set working directory
cd("C:/Users/Eric/Documents/GitHub/Econ-543/PS 1/data")

# Load the dataset using the CSV.jl package
using CSV, DataFrames, StatsModels, Econometrics, GLM, NLsolve, Statistics, DataFramesMeta, Latexify, LinearAlgebra

product_data = CSV.File("product_data_2.csv") |> DataFrame

product_data[:, :market_share] = product_data[:, :quantity] ./ 100000

product_data
Out[1]:
1000×10 DataFrame
975 rows omitted
Rowproduct_IDnestpricesugarcaffeinecaffeine_extract_pricecorn_syrup_pricetquantitymarket_share
Int64String7Float64Float64Float64Float64Float64Int64Int64Float64
11Diet1.991620.2703723.18830.2827260.228225117890.01789
22Diet1.949780.3514394.485670.1421780.2377641121440.12144
33Diet3.32370.2448856.807560.2744630.2830121182250.18225
44Diet2.519570.3914485.10140.2307910.2750841125740.12574
55Diet2.225620.1595974.318690.2632950.190009136240.03624
66Regular2.629013.748174.831580.197830.124657182680.08268
77Regular4.050854.208145.413750.2973320.316541129080.02908
88Regular3.50374.354455.083040.2757550.235712141610.04161
99Regular3.342425.967533.632940.222530.219853199420.09942
1010Regular3.575584.792144.526630.2794010.252537143790.04379
111Diet2.552650.2716255.132730.2709630.263825248520.04852
122Diet2.43080.940853.560330.2689310.315047285770.08577
133Diet2.917310.4366045.958070.2696290.276241295740.09574
⋮⋮⋮⋮⋮⋮⋮⋮⋮⋮⋮
9899Regular3.230824.443154.70040.2799810.2027179917990.01799
99010Regular3.079264.972664.723890.1413770.23591499105650.10565
9911Diet2.23330.01896664.453750.2673070.24102710028180.02818
9922Diet3.110170.6223467.138330.2246270.247321100202560.20256
9933Diet4.034230.7043367.495870.3040580.248869100284350.28435
9944Diet2.73240.1849335.390840.3014120.22884810048030.04803
9955Diet2.696050.6311735.540920.2589550.27900310062870.06287
9966Regular3.252334.773852.44990.2457790.3312811007630.00763
9977Regular3.572125.050584.461910.2612330.27873910015160.01516
9988Regular3.545484.866765.285240.1578170.29748710073630.07363
9999Regular3.714083.585296.848650.2286420.23813910079150.07915
100010Regular3.914484.253395.815220.2902030.26753110039440.03944

1(a): Multinomial Logit¶

Assume the indirect utility of consumer $i$ for consuming soft drink $j$ in period $t$ is given by:

\begin{equation} u_{ijt} = \alpha p_{jt} + \beta_1 sug_{jt} + \beta_2 caf_{jt} + \gamma_d Diet_j + \gamma_r Regular_j + \xi_{jt} + \varepsilon_{ijt} \end{equation}

where $p_{jt}$ denotes the price (in USD) of product $j$ at time $t$, $sug_{jt}$ denote $j$'s sugar content (in grams) at time $t$, $caf_{jt}$ denotes $j$'s milligrams of caffeine at time $t$, $Diet_j$ is an indicator variable equal to 1 if $j$ is a diet product, $Regular_j$ is an indicator variable equal to 1 if $j$ is a regular soda, and $\xi_{jt}$ represents the unobserved quality of product $j$ during time period $t$.

Moreover, assume that $\varepsilon_{ijt}$ are $iid$ draws from a standard type 1 extreme value distribution. Note that the only source of heterogeneity between consumer preferences comes from the extreme value shock.

Finally, assume that there is an outside option (product 0) representing not consuming any soft drinks. The utility of the outside option is normalized such that it is mean zero:

\begin{equation} u_{i0t} = \varepsilon_{i0t} \end{equation}

Recall that given our distributional assumptions over $\varepsilon_{ijt}$, the probability that consumer $i$ chooses product $j$ during time $t$ is:

$$ s_{ijt} = \mathbb{P}(u_{ijt} > u_{ikt} \: \text{for all} \: k) = \frac{\exp(\delta_{jt})}{\sum_k \exp(\delta_k) + 1 } $$

With $\delta_{jt} = \alpha p_{jt} + \beta_1 sug_{jt} + \beta_2 caf_{jt} + \gamma_d Diet_j + \gamma_r Regular_j + \xi_{jt}$.

Assume that $p_{jt}$, $sug_{jt}$, and $caf_{jt}$ are all exogenous variables. Estimate the parameters of this model ($\alpha$, $\beta_1$, $\beta_2$, $\gamma_d$, and $\gamma_r$).

Solution¶

Note that we can rearrange the market share equation for the outside option (product $0$) to get the following expression:

$$ \delta_{jt} = \log(s_{jt}) - \log(s_{0t}) $$

We can then substitute this expression into equation (3) to get:

$$ \log(s_{jt}) - \log(s_{0t}) =\alpha p_{jt} + \beta_1 sug_{jt} + \beta_2 caf_{jt} + \gamma_d Diet_j + \gamma_r Regular_j + \xi_{jt} $$

Given that this is a linear equation, and we have assumed that $p_{jt}$, $sug_{jt}$, $caf_{jt}$, $Diet_j$, and $Regular_j$ are all exogenous, we can estimate the parameters of this model by OLS.

In [2]:
function multinomial_logit_coeff(product_data::DataFrame, estimation::String="IV")
    
    data = copy(product_data)
    
    # Compute outside_share
    grouped_data = groupby(data, :t)
    outside_share_df = combine(grouped_data, :market_share => (x -> 1 .- sum(x)) => :outside_share)
    merged_data = leftjoin(product_data, outside_share_df, on=:t)
    
    # Compute delta_jt
    merged_data[:, :delta_jt] = log.(merged_data[:, :market_share]) .- log.(merged_data[:, :outside_share])
    
    # Add dummy columns
    merged_data.dummy_Diet = ifelse.(merged_data.nest .== "Diet", 1, 0)
    merged_data.dummy_Regular = ifelse.(merged_data.nest .== "Regular", 1, 0)

    
    if estimation == "IV"
        coefficients = fit(EconometricModel,
            @formula(delta_jt ~ 0 + sugar + caffeine + dummy_Diet + dummy_Regular + (price ~ caffeine_extract_price + corn_syrup_price)),
            merged_data,
            panel = :product_ID,
            time = :t)
        
    elseif estimation == "OLS"
        formula = @formula(delta_jt ~ 0 + price + sugar + caffeine + dummy_Diet + dummy_Regular)
        coefficients = lm(formula, merged_data)
        
    else
        error("estimation must be either IV or OLS")
    end
    
    return coefficients
end
Out[2]:
multinomial_logit_coeff (generic function with 2 methods)
In [3]:
coefficients_iv = multinomial_logit_coeff(product_data, "OLS")
Out[3]:
StatsModels.TableRegressionModel{LinearModel{GLM.LmResp{Vector{Float64}}, GLM.DensePredChol{Float64, CholeskyPivoted{Float64, Matrix{Float64}, Vector{Int64}}}}, Matrix{Float64}}

delta_jt ~ 0 + price + sugar + caffeine + dummy_Diet + dummy_Regular

Coefficients:
────────────────────────────────────────────────────────────────────────────
                   Coef.  Std. Error       t  Pr(>|t|)  Lower 95%  Upper 95%
────────────────────────────────────────────────────────────────────────────
price          -0.337102   0.0512248   -6.58    <1e-10  -0.437623  -0.236581
sugar           0.708483   0.0269605   26.28    <1e-99   0.655577   0.761389
caffeine        0.691937   0.0231248   29.92    <1e-99   0.646558   0.737316
dummy_Diet     -3.8965     0.0823602  -47.31    <1e-99  -4.05812   -3.73488
dummy_Regular  -7.10876    0.126564   -56.17    <1e-99  -7.35712   -6.86039
────────────────────────────────────────────────────────────────────────────

Problem 1(b)¶

Now assume that $sweetness_{jt}$, and $healthiness_{jt}$ are exogenous, but that $price_{jt}$ is correlated with the unobserved quality $\xi_{jt}$. Use $Z1_{jt}$ and $Z2_{jt}$ as instruments for prices, and estimate $\alpha$, $\beta_1$, and $\beta_2$.

Solution¶

If $price_{jt}$ is correlated with the unobserved product quality $\xi_{jt}$, the estimates we obtained in part (a) will be biased. We can therefore use the usual 2SLS IV estimator using Z1 and Z2 as instruments for prices.

In [4]:
coefficients_iv = multinomial_logit_coeff(product_data, "IV")
Out[4]:
Continuous Response Model
Number of observations: 1000
Null Loglikelihood: -1308.75
Loglikelihood: -690.20
R-squared: NaN
LR Test: 1237.10 ∼ χ²(5) ⟹  Pr > χ² = 0.0000
Formula: delta_jt ~ 0 + sugar + caffeine + dummy_Diet + dummy_Regular + (price ~ caffeine_extract_price + corn_syrup_price)
Variance Covariance Estimator: OIM
─────────────────────────────────────────────────────────────────────────────
                   PE         SE      t-value  Pr > |t|      2.50%     97.50%
─────────────────────────────────────────────────────────────────────────────
sugar           0.860601  0.030166    28.5289    <1e-99   0.801405   0.919798
caffeine        0.850355  0.0267068   31.8404    <1e-99   0.797947   0.902763
dummy_Diet     -3.60285   0.0882533  -40.824     <1e-99  -3.77603   -3.42966
dummy_Regular  -7.00604   0.131703   -53.1957    <1e-99  -7.26449   -6.7476
price          -0.785799  0.0626406  -12.5446    <1e-32  -0.908722  -0.662875
─────────────────────────────────────────────────────────────────────────────

Problem 1(c)¶

Use the parameters you estimated in problem 1 (b) to derive the vector of price own-price derivatives $\frac{\partial s_{jt}}{\partial p_{jt}}$ for all $j$ and all $t$. Use this vector to estimate the vector of price elasticities $\frac{\partial s_{jt}}{\partial p_{jt}} \frac{p_{jt}}{s_{jt}}$ for all $j$ and all $t$.

Solution¶

The own-price derivative of the choice probability for product $j$ in period $t$ is:

$$ \frac{\partial s_{jt}}{\partial p_{jt} }=-\alpha s_{ jt}\left(1-s_{jt}\right) $$

Therefore, the own-price elasticity for product $j$ in period $t$ is:

$$ -\alpha s_{ jt}\left(1-s_{jt}\right) \times \frac{p_{jt}}{s_{jt}} $$

In [5]:
function price_elasticity_mult_logit(product_data::DataFrame, alpha::Float64)
    
    data = copy(product_data)
    
    # Compute the price_derivative and price_elasticities
    data[!, :price_derivative] = alpha .* data[!, :market_share] .* (1 .- data[!, :market_share])
    data[!, :price_elasticities] = data[!, :price_derivative] .* (data[!, :price] ./ data[!, :market_share])
    
    # Select the desired columns for the result
    result = select(data, :product_ID, :nest, :price_derivative, :price_elasticities, :t)
    
    return result
end

alpha_hat_mult = coef(coefficients_iv)[5]

own_price_elasticity_mult = price_elasticity_mult_logit(product_data, alpha_hat_mult)
Out[5]:
1000×5 DataFrame
975 rows omitted
Rowproduct_IDnestprice_derivativeprice_elasticitiest
Int64String7Float64Float64Int64
11Diet-0.0138064-1.537011
22Diet-0.0838387-1.346071
33Diet-0.117111-2.135771
44Diet-0.0863824-1.730931
55Diet-0.0274453-1.685511
66Regular-0.0595981-1.895071
77Regular-0.0221865-3.090591
88Regular-0.0313366-2.638641
99Regular-0.070357-2.365341
1010Regular-0.0329033-2.686651
111Diet-0.036277-1.908542
122Diet-0.0616172-1.746292
133Diet-0.0680296-2.072952
⋮⋮⋮⋮⋮⋮
9899Regular-0.0138822-2.493199
99010Regular-0.0742486-2.1640499
9911Diet-0.0215198-1.70547100
9922Diet-0.12693-1.94892100
9933Diet-0.159906-2.26868100
9944Diet-0.0359292-2.04399100
9955Diet-0.0462972-1.98536100
9966Regular-0.0059499-2.53617100
9977Regular-0.0117321-2.76441100
9988Regular-0.0535982-2.5809100
9999Regular-0.0572732-2.68752100
100010Regular-0.0297696-2.95467100

Problem 1(d)¶

Use the parameters you estimated in problem 1 (b) to derive the vector of cross-price derivatives $\frac{\partial s_{jt}}{\partial p_{1t}}$ for all $j \neq 1$ and all $t$. Use this vector to estimate the vector of cross-price elasticities $\frac{\partial s_{jt}}{\partial p_{1t}} \frac{p_{1t}}{s_{jt}}$ for all $j \neq 1$ and all $t$.

Solution¶

The cross-price derivatives are

$$ \frac{\partial s_{jt}}{\partial p_{1t} }=\alpha s_{1t} s_{jt} $$

Therefore, the cross-price elasticity for product $j$ with respect to product $1$ in period $t$ is:

$$ \alpha s_{1t} s_{jt} \times \frac{p_{1t}}{s_{jt}} $$

In [6]:
function cross_price_elasticity_mult_logit(product_data::DataFrame, alpha::Float64, product)
    
    data = copy(product_data)
    
    # Selecting the specified product
    selected_product = filter(row -> row[:product_ID] == product, data)
    
    # Creating the selected_product DataFrame
    selected_product = select(selected_product, :product_ID => :product_k_ID, :price => :price_k, :market_share => :share_k, :t)
    
    # Merging the data with selected_product
    data = leftjoin(data, selected_product, on = :t)
    
    # Compute the cross_price_derivative and cross_price_elasticities
    data[!, :cross_price_derivative] = - alpha .* data[!, :market_share] .* data[!, :share_k]
    data[!, :cross_price_elasticities] = data[!, :cross_price_derivative] .* (data[!, :price_k] ./ data[!, :market_share])
    
    # Removing rows with the specified product
    filter!(row -> row[:product_ID] != product, data)
    
    # Selecting the desired columns for the result
    result = select(data, :product_ID, :nest, :cross_price_derivative, :cross_price_elasticities, :t)
    
    return result
end

cross_price_elasticity_mult_logit_vec = cross_price_elasticity_mult_logit(product_data, alpha_hat_mult, 1)
Out[6]:
900×5 DataFrame
875 rows omitted
Rowproduct_IDnestcross_price_derivativecross_price_elasticitiest
Int64String7Float64Float64Int64
12Diet0.00170720.0279981
23Diet0.002562060.0279981
34Diet0.001767650.0279981
45Diet0.000509460.0279981
56Regular0.001162310.0279981
67Regular0.0004088050.0279981
78Regular0.0005849510.0279981
89Regular0.001397640.0279981
910Regular0.0006155970.0279981
102Diet0.003270150.09732462
113Diet0.003650270.09732462
124Diet0.004863470.09732462
135Diet0.002508750.09732462
⋮⋮⋮⋮⋮⋮
8898Regular0.003190970.095259799
8909Regular0.0006741710.095259799
89110Regular0.003959210.095259799
8922Diet0.004485450.0494537100
8933Diet0.006296590.0494537100
8944Diet0.001063570.0494537100
8955Diet0.001392180.0494537100
8966Regular0.0001689570.0494537100
8977Regular0.00033570.0494537100
8988Regular0.001630450.0494537100
8999Regular0.001752680.0494537100
90010Regular0.0008733520.0494537100

Problem 2(e)¶

Write a function that generates the Jacobian matrix of price derivatives $\Delta(p)$ (i.e., $\Delta_{ij} = \frac{\partial s_j}{p_i}$) for a given period. Your function should take the time period, product variables, and model parameters as arguments, and return the Jacobian matrix. Print the Jacobian for the last time period in the sample (t = 100).

Solution¶

In [7]:
using LinearAlgebra

function build_jacobian_matrix_mult_logit(product_variables::DataFrame, alpha::Number, time_period)
    dd = copy(product_variables)
    dd = dd[dd.t .== time_period, :]
    sort!(dd, [:nest, :product_ID])

    dd[!, :self_derivative] = alpha * dd.market_share .* (1 .- dd.market_share)

    Jacobian_matrix = dd.market_share * (-alpha .* dd.market_share)'

    # Update the diagonal of Jacobian_matrix with self_derivative values
    Jacobian_matrix[diagind(Jacobian_matrix)] = dd.self_derivative

    return Jacobian_matrix
end

Jacobian_mult = build_jacobian_matrix_mult_logit(product_data, alpha_hat_mult, 100)

latexify(round.(Jacobian_mult, digits=4))
Out[7]:
\begin{equation} \left[ \begin{array}{cccccccccc} -0.0215 & 0.0045 & 0.0063 & 0.0011 & 0.0014 & 0.0002 & 0.0003 & 0.0016 & 0.0018 & 0.0009 \\ 0.0045 & -0.1269 & 0.0453 & 0.0076 & 0.01 & 0.0012 & 0.0024 & 0.0117 & 0.0126 & 0.0063 \\ 0.0063 & 0.0453 & -0.1599 & 0.0107 & 0.014 & 0.0017 & 0.0034 & 0.0165 & 0.0177 & 0.0088 \\ 0.0011 & 0.0076 & 0.0107 & -0.0359 & 0.0024 & 0.0003 & 0.0006 & 0.0028 & 0.003 & 0.0015 \\ 0.0014 & 0.01 & 0.014 & 0.0024 & -0.0463 & 0.0004 & 0.0007 & 0.0036 & 0.0039 & 0.0019 \\ 0.0002 & 0.0012 & 0.0017 & 0.0003 & 0.0004 & -0.0059 & 0.0001 & 0.0004 & 0.0005 & 0.0002 \\ 0.0003 & 0.0024 & 0.0034 & 0.0006 & 0.0007 & 0.0001 & -0.0117 & 0.0009 & 0.0009 & 0.0005 \\ 0.0016 & 0.0117 & 0.0165 & 0.0028 & 0.0036 & 0.0004 & 0.0009 & -0.0536 & 0.0046 & 0.0023 \\ 0.0018 & 0.0126 & 0.0177 & 0.003 & 0.0039 & 0.0005 & 0.0009 & 0.0046 & -0.0573 & 0.0025 \\ 0.0009 & 0.0063 & 0.0088 & 0.0015 & 0.0019 & 0.0002 & 0.0005 & 0.0023 & 0.0025 & -0.0298 \\ \end{array} \right] \end{equation}

2(a): Nested Logit¶

Now suppose that there are two sources of consumer heterogeneity (1) an extreme value shock as in problem 1 and (2) a preference for diet soda. Let products $0,1,2,...,20$ be grouped into three mutually exclusive sets: $\mathcal{J}_D$ (for diet products), $\mathcal{J}_R$ (for regular products), and $\mathcal{J}_0$ (for the outside option). For product $j \in \mathcal{J}_g$ during time $t$, let consumer $i$ derive utility equal to:

\begin{equation} u_{ijt} = \alpha p_{jt} + \beta_1 sug_{jt} + \beta_2 caf_{jt} + \gamma_d Diet_j + \gamma_r Regular_j + \xi_{jt} + \varsigma_{igt} + (1 - \sigma) \times \varepsilon_{ijt} \end{equation}

Where $\varepsilon_{ijt}$ is again distributed type 1 extreme value, and $\varsigma_{igt}$ follows a distribution such that $\varsigma_{igt} + (1 - \sigma) \times \varepsilon_{ijt}$ is also an extreme value random variable. The parameter $\sigma$ represents the degree of correlation between the error terms of products in the same nest and satisfies $0 \leq \sigma < 1$.

Given our distributional assumptions, the inside share of good $j$ belonging to group $g$ at time $t$ is given by:

\begin{equation} s_{jt|g} = \frac{ \exp(\frac{\delta_{jt}}{1 - \sigma}) }{ \sum_{k \in \mathcal{J}_g } \exp(\frac{\delta_{kt}}{1 - \sigma})} \end{equation}

with $\delta_{jt} = \alpha p_{jt} + \beta_1 sug_{jt} + \beta_2 caf_{jt} + \gamma_d Diet_j + \gamma_r Regular_j + \xi_{jt}$

The probability that a product from group $g$ is chosen is

\begin{equation} s_g = \frac{D_{g}^{(1 - \sigma)}}{\sum_g D_{g}^{(1 - \sigma)}} \end{equation}

with $D_g = \sum_{k \in \mathcal{J}_g } \exp(\frac{\delta_{kt}}{1 - \sigma})$

Therefore, product $j$'s share at time $t$ is

\begin{equation} s_{jt} = s_{jt|g} \times s_g \end{equation}

Estimate the parameters of the model ($\alpha$, $\beta_1$, $\beta_2$, $\gamma_d$, and $\gamma_r$, and $\sigma$), assuming that $p_{jt}$ is correlated with the unobserved quality $\xi_{jt}$.

Solution¶

We can invert the demand system to get the following linear expression:

$$ \log \left(s_{j t}\right)- \log \left(s_{0 t}\right)= \alpha \times p_{jt} + \beta_1 sug_{jt} + \beta_2 caf_{jt} + \gamma_d Diet_j + \gamma_r Regular_j + (1-\sigma) \log \left(s_{jt| g}\right)+\xi_{j t} $$

As before, we can use the prices of caffeine and corn syrup as instruments for prices. However, note that $s_{jt| g}$ is also endogenous, so we need instruments for the inside share as well. Given our assumptions that $sug_{jt}$ and $caf_{jt}$ are exogenous, we can use the average $sug_{jt}$ and $caf_{jt}$ of competing products inside the same nest to instrument for $s_{jt| g}$, then estimate $\alpha$, $\beta_1$, $\beta_2$, and $\sigma$ by 2SLS IV.

In [8]:
function nested_logit_coeff(product_data::DataFrame)
   data = copy(product_data)
    
    grouped_by_t = groupby(data, :t)
    outside_share_df = combine(grouped_by_t, :market_share => (x -> 1 .- sum(x)) => :outside_share)
    data = leftjoin(data, outside_share_df, on=:t)
    data[:, :delta_jt] = log.(data.market_share) .- log.(data.outside_share)

    grouped_by_t_nest = groupby(data, [:t, :nest])
    nest_share_df = combine(grouped_by_t_nest, :market_share => sum => :nest_share)
    data = leftjoin(data, nest_share_df, on=[:t, :nest])
    data[:, :inside_share] = data[:, :market_share] ./ data[:, :nest_share]

    # Compute adjusted sum and count
    sugar_sum_df = combine(grouped_by_t_nest, :sugar => (x -> sum(x) .- x) => :adj_sum, :sugar => length => :sugar_count)
    sugar_sum_df[:, :competitors_sugar] = sugar_sum_df[:, :adj_sum] ./ (sugar_sum_df[:, :sugar_count] .- 1)

    #Compute adjusted sum and count
    caffeine_sum_df = combine(grouped_by_t_nest, :caffeine => (x -> sum(x) .- x) => :adj_sum, :caffeine => length => :caffeine_count)
    caffeine_sum_df[:, :competitors_caffeine] = caffeine_sum_df[:, :adj_sum] ./ (caffeine_sum_df[:, :caffeine_count] .- 1)

    sort!(data, [:t, :product_ID])
 
    sugar_sum_df = DataFrame(product_ID = data.product_ID, competitors_sugar = sugar_sum_df.competitors_sugar, t = data.t)
    caffeine_sum_df = DataFrame(product_ID = data.product_ID, competitors_caffeine = caffeine_sum_df.competitors_caffeine, t = data.t)
    
    data = leftjoin(data, sugar_sum_df, on=[:t, :product_ID])

    data = leftjoin(data, caffeine_sum_df, on=[:t, :product_ID])
    
    # Add dummy columns
    data.dummy_Diet = ifelse.(data.nest .== "Diet", 1, 0)
    data.dummy_Regular = ifelse.(data.nest .== "Regular", 1, 0)


    coefficients = fit(EconometricModel,
            @formula(delta_jt ~0 + sugar + caffeine + dummy_Diet + dummy_Regular +  (log(inside_share) + price ~ caffeine_extract_price  + corn_syrup_price  + competitors_sugar + competitors_caffeine)),
            data)
    
    xi = residuals(coefficients) 
    xi_hat = DataFrame(product_ID = data.product_ID, xi = xi, t = data.t)
    
    return Dict(:coefficients => coefficients, :xi_hat => xi_hat)
    
end

nested_logit_result = nested_logit_coeff(product_data)

nested_logit_result[:coefficients]
Out[8]:
Continuous Response Model
Number of observations: 1000
Null Loglikelihood: -1308.75
Loglikelihood: 8.14
R-squared: NaN
LR Test: 2633.78 ∼ χ²(6) ⟹  Pr > χ² = 0.0000
Formula: delta_jt ~ 0 + sugar + caffeine + dummy_Diet + dummy_Regular + (:(log(inside_share)) + price ~ caffeine_extract_price + corn_syrup_price + competitors_sugar + competitors_caffeine)
Variance Covariance Estimator: OIM
─────────────────────────────────────────────────────────────────────────────────
                       PE         SE      t-value  Pr > |t|      2.50%     97.50%
─────────────────────────────────────────────────────────────────────────────────
sugar               0.494104  0.0204195   24.1977    <1e-99   0.454034   0.534175
caffeine            0.490971  0.0188912   25.9894    <1e-99   0.4539     0.528042
dummy_Diet         -1.53519   0.103962   -14.7668    <1e-44  -1.7392    -1.33118
dummy_Regular      -3.56714   0.165241   -21.5875    <1e-84  -3.8914    -3.24287
log(inside_share)   0.494244  0.0215998   22.8819    <1e-92   0.451858   0.536631
price              -0.466178  0.0316167  -14.7447    <1e-43  -0.528221  -0.404134
─────────────────────────────────────────────────────────────────────────────────

Problem 2(b)¶

Use the parameters you estimated in problem 2 (a) to derive the vector of price self-derivatives $\frac{\partial s_{jt}}{\partial p_{jt}}$ for all $j$ and all $t$. Use this vector to estimate the vector of price elasticities $\frac{\partial s_{jt}}{\partial p_{jt}} \frac{p_{jt}}{s_{jt}}$ for all $j$ and all $t$.

Solution¶

The derivative of product $j$'s share, for $j \in \mathcal{J}_g$, with respect to its own price is given by

$$ \frac{\partial s_{jt}}{\partial p_{jt}} = \frac{\partial \delta_{jt}}{\partial p_{jt}} \times \frac{\partial s_{jt} }{\partial \delta_{jt} }=\frac{\alpha}{1-\sigma} s_{jt} \left(1-\sigma s_{jt | g}-(1-\sigma) s_{jt} \right) $$

The own price elasticity of product $j$ in group $g$ is therefore given by

$$ \frac{\alpha}{1-\sigma} s_{jt} \left(1-\sigma s_{jt | g}-(1-\sigma) s_{jt} \right) \times \frac{p_{jt}}{s_{jt}} $$

In [9]:
function price_elasticity_nested_logit(product_data::DataFrame, alpha, sigma)
    data = copy(product_data)
    
    # Compute nest_share
    grouped_by_t_nest = groupby(data, [:t, :nest])
    nest_share_df = combine(grouped_by_t_nest, :market_share => sum => :nest_share)
    data = leftjoin(data, nest_share_df, on=[:t, :nest])

    # Compute inside_share
    data[:, :inside_share] = data[:, :market_share] ./ data[:, :nest_share]

    # Compute price_derivative
    data[:, :price_derivative] = (alpha / (1 - sigma)) .* data[:, :market_share] .* (1 .- sigma .* data[:, :inside_share] .- (1 .- sigma) .* data[:, :market_share])

    # Compute price_elasticities
    data[:, :price_elasticities] = data[:, :price_derivative] .* (data[:, :price] ./ data[:, :market_share])
    
    # Create result DataFrame
    result = select(data, :product_ID, :nest, :price_derivative, :price_elasticities, :t)
    
    return result
end

alpha_hat_nested = coef(nested_logit_result[:coefficients])[6]


sigma_hat = coef(nested_logit_result[:coefficients])[5]

price_elasticity_nested_logit_vec = price_elasticity_nested_logit(product_data, alpha_hat_nested , sigma_hat)
Out[9]:
1000×5 DataFrame
975 rows omitted
Rowproduct_IDnestprice_derivativeprice_elasticitiest
Int64String7Float64Float64Int64
11Diet-0.0160393-1.785591
22Diet-0.0911677-1.463741
33Diet-0.121212-2.210541
44Diet-0.0936344-1.876241
55Diet-0.0315545-1.937871
66Regular-0.0625225-1.988061
77Regular-0.0251111-3.497991
88Regular-0.0348871-2.937611
99Regular-0.071849-2.41551
1010Regular-0.0365238-2.982271
111Diet-0.0410925-2.161882
122Diet-0.067713-1.919052
133Diet-0.074112-2.258282
⋮⋮⋮⋮⋮⋮
9899Regular-0.0160619-2.8845499
99010Regular-0.0794368-2.3152599
9911Diet-0.0250267-1.98339100
9922Diet-0.137721-2.11461100
9933Diet-0.165563-2.34893100
9944Diet-0.0415171-2.36188100
9955Diet-0.0532309-2.2827100
9966Regular-0.00688242-2.93367100
9977Regular-0.0133796-3.15259100
9988Regular-0.0538538-2.59321100
9999Regular-0.0567618-2.66353100
100010Regular-0.0323326-3.20906100

Problem 2(c)¶

Use the parameters you estimated in problem 2 (a) to derive the vector of cross-price derivatives $\frac{\partial s_{jt}}{\partial p_{1t}}$ for all $j \neq 1$ and all $t$. Use this vector to estimate the vector of cross-price elasticities $\frac{\partial s_{jt}}{\partial p_{1t}} \frac{p_{1t}}{s_{jt}}$ for all $j \neq 1$ and all $t$.

Solution¶

The derivative of product $j$ 's share with respect to product $k$ 's price, for $j$ and $k$ both in group $g$, equals

$$ \frac{\partial s_{jt}}{\partial p_{kt}} = \frac{\partial \delta_{kt}}{\partial p_{kt}} \times \frac{\partial s_{jt} }{\partial \delta_{kt} }=-\alpha s_{kt} \left(s_{jt} +\frac{\sigma}{1-\sigma} s_{jt|g}\right) $$

The cross-price derivative is therefore given by

$$ -\alpha s_{kt} \left(s_{jt} +\frac{\sigma}{1-\sigma} s_{jt|g}\right) \times \frac{p_{kt}}{s_{jt}} $$

The derivative of product $j$ 's share with respect to product $k$ 's mean valuation, for $j$ and $k$ in different groups, equals $$ \frac{\partial s_{jt}}{\partial p_{kt}} = \frac{\partial \delta_{kt}}{\partial p_{kt}} \times \frac{\partial s_{jt} }{\partial \delta_{kt} } = -\alpha s_{jt} s_{kt} $$

The cross-price elasticity is therefore given by

$$ -\alpha s_{jt} s_{kt} \times \frac{p_{kt}}{s_{jt}} $$

In [10]:
function cross_price_elasticity_nested_logit(product_data::DataFrame, alpha, sigma, product)
    data = copy(product_data)
    
    # Compute nest_share and inside_share
    grouped_by_t_nest = groupby(data, [:t, :nest])
    nest_share_df = combine(grouped_by_t_nest, :market_share => sum => :nest_share)
    data = leftjoin(data, nest_share_df, on=[:t, :nest])
    data[:, :inside_share] = data[:, :market_share] ./ data[:, :nest_share]
    
    # Get the selected_product
    selected_product = filter(row -> row.product_ID == product, data)
    selected_product_df = DataFrame(
        product_k_ID = selected_product.product_ID,
        nest_k = selected_product.nest,
        price_k = selected_product.price,
        share_k = selected_product.market_share,
        t = selected_product.t
    )

    # Merge the two DataFrames
    data = leftjoin(data, selected_product_df, on=:t)

    # Compute cross_price_derivative
    data[:, :cross_price_derivative] = ifelse.(
        data.nest .== data.nest_k,
        - alpha .* data.share_k .* (data.market_share + (sigma / (1 - sigma)) .* data.inside_share),
        - alpha .* data.market_share .* data.share_k
    )

    # Compute cross_price_elasticities
    data[:, :cross_price_elasticities] = data[:, :cross_price_derivative] .* (data[:, :price_k] ./ data[:, :market_share])
    
    # Filter out rows where product_ID equals the selected product
    data = filter(row -> row.product_ID != product, data)
    
    # Create result DataFrame
    result = select(data, :product_ID, :nest, :cross_price_derivative, :cross_price_elasticities, :t)
    
    return result
end



cross_price_elasticity_vec = cross_price_elasticity_nested_logit(product_data, alpha_hat_nested, sigma_hat, 1)
Out[10]:
900×5 DataFrame
875 rows omitted
Rowproduct_IDnestcross_price_derivativecross_price_elasticitiest
Int64String7Float64Float64Int64
12Diet0.003059590.05017741
23Diet0.004591660.05017741
34Diet0.003167930.05017741
45Diet0.0009130410.05017741
56Regular0.0006895440.01660991
67Regular0.0002425250.01660991
78Regular0.0003470240.01660991
89Regular0.0008291550.01660991
910Regular0.0003652050.01660991
102Diet0.006417860.1910052
113Diet0.007163880.1910052
124Diet0.009544860.1910052
135Diet0.004923580.1910052
⋮⋮⋮⋮⋮⋮
8898Regular0.001893060.056513199
8909Regular0.0003999540.056513199
89110Regular0.002348810.056513199
8922Diet0.006815130.0751393100
8933Diet0.009566960.0751393100
8944Diet0.001615970.0751393100
8955Diet0.002115260.0751393100
8966Regular0.0001002340.0293386100
8977Regular0.0001991550.0293386100
8988Regular0.0009672690.0293386100
8999Regular0.001039780.0293386100
90010Regular0.0005181190.0293386100

Problem 2(d)¶

How do the results you obtained from parts 2 (a), 2 (b), and 2 (c) compare to the ones from parts 1 (b), 1 (c), and 1(d)?

Solution¶

Note that given the assumptions of Multinomial Logit, the cross-price elasticity of all products with respect to product 1 is constant (see problem 1(d)). To see why, let's go back to the expression for $\frac{\partial s_{jt}}{\partial p_{kt} }$. Note that this expression implies that the share of consumers who switch from product $k$ to product $j$ only depends on the market shares of products $j$ and $k$. For instance, suppose that $k$ is a diet soda. If $j$ and $l$ are a diet and a regular soda with the same market share, this implies that a consumer is equally likely to switch from product $k$ to products $j$ and $k$, even though we would expect a consumer of diet products to be more likely to switch to another diet product.

In contrast, under a nested logit specification, the cross-price elasticity with respect to product $k$ of all products in the same nest as product $k$ will be higher than the cross-price elasticity for products in other groups. Therefore, consumers who choose a diet soda are now more likely to substitute to another soda in the same nest (diet), compared to a regular soda. Note, however, that the elasticity is still constant within each group.

Problem 2(e)¶

Write a function that generates the Jacobian matrix of price derivatives $\Delta(p)$ (i.e., $\Delta_{ij} = \frac{\partial s_j}{p_i}$) for a given period, under the nested logit specification. Your function should take the time period, product variables, and model parameters as arguments, and return the Jacobian matrix. Print the Jacobian for the last time period in the sample (t = 100).

Solution¶

In [11]:
function build_jacobian_matrix_nested_logit(product_variables::DataFrame, alpha::Number, sigma::Number, time_period::Int)
    dd = copy(product_variables)
    dd = dd[dd.t .== time_period, :]
    sort!(dd, [:nest, :product_ID])

    grouped_data = groupby(dd, [:t, :nest])
    nest_share_df = combine(grouped_data, :market_share => sum => :nest_share)
    dd = leftjoin(dd, nest_share_df, on=[:t, :nest])

    dd[!, :inside_share] = dd.market_share ./ dd.nest_share

    dd[!, :same_nest] = dd.market_share + (sigma / (1 - sigma)) .* dd.inside_share
    dd[!, :self_derivative] = alpha / (1 - sigma) .* dd.market_share .* (1 .- sigma .* dd.inside_share - (1 - sigma) .* dd.market_share)

    Jacobian_part_same_nest = dd.same_nest * (-alpha .* dd.market_share)'
    Jacobian_par_diff_nest = dd.market_share * (-alpha .* dd.market_share)'

    Jacobian_matrix = Jacobian_part_same_nest
    Jacobian_matrix[6:10,1:5] = Jacobian_par_diff_nest[6:10,1:5]
    Jacobian_matrix[1:5,6:10] = Jacobian_par_diff_nest[1:5,6:10]
    Jacobian_matrix[diagind(Jacobian_matrix)] = dd.self_derivative

    return Jacobian_matrix
end


Jacobian_nested = build_jacobian_matrix_nested_logit(product_data, alpha_hat_nested, sigma_hat, 100)

latexify(round.(Jacobian_nested, digits=4))
Out[11]:
\begin{equation} \left[ \begin{array}{cccccccccc} -0.025 & 0.0068 & 0.0096 & 0.0016 & 0.0021 & 0.0001 & 0.0002 & 0.001 & 0.001 & 0.0005 \\ 0.0068 & -0.1377 & 0.0688 & 0.0116 & 0.0152 & 0.0007 & 0.0014 & 0.007 & 0.0075 & 0.0037 \\ 0.0096 & 0.0688 & -0.1656 & 0.0163 & 0.0213 & 0.001 & 0.002 & 0.0098 & 0.0105 & 0.0052 \\ 0.0016 & 0.0116 & 0.0163 & -0.0415 & 0.0036 & 0.0002 & 0.0003 & 0.0016 & 0.0018 & 0.0009 \\ 0.0021 & 0.0152 & 0.0213 & 0.0036 & -0.0532 & 0.0002 & 0.0004 & 0.0022 & 0.0023 & 0.0012 \\ 0.0001 & 0.0007 & 0.001 & 0.0002 & 0.0002 & -0.0069 & 0.0003 & 0.0015 & 0.0016 & 0.0008 \\ 0.0002 & 0.0014 & 0.002 & 0.0003 & 0.0004 & 0.0003 & -0.0134 & 0.0029 & 0.0031 & 0.0015 \\ 0.001 & 0.007 & 0.0098 & 0.0016 & 0.0022 & 0.0015 & 0.0029 & -0.0539 & 0.0151 & 0.0075 \\ 0.001 & 0.0075 & 0.0105 & 0.0018 & 0.0023 & 0.0016 & 0.0031 & 0.0151 & -0.0568 & 0.0081 \\ 0.0005 & 0.0037 & 0.0052 & 0.0009 & 0.0012 & 0.0008 & 0.0015 & 0.0075 & 0.0081 & -0.0323 \\ \end{array} \right] \end{equation}

Problem 3(a)¶

Assume that the supply side is a single-product Bertrand Nash Equilibrium (each firm owns only one product). That is, firm $f$ owning product $j$ solves the following problem in period $t$ to set prices:

$$ \max_{p_{jt}} \Pi_{ft} = p_{jt} q_{jt} - c_{jt} q_{jt} $$

Where $c_{jt}$ denotes product $j$'s marginal cost, and $q_{jt} = s_{jt} \bar{M}$. $\bar{M} = 10,000$ is the number of consumers in this market. Use the firms' FOCs to estimate the marginal cost for all $j$ and all $t$.

Solution¶

The FOC for firm $f$ owning product $j$ is given by:

$$ \frac{\partial \Pi_{ft}}{\partial p_{jt}} = s_{jt} + (p_{jt} - c_{jt})\times \frac{\partial s_{jt}}{\partial p_{jt}} = 0 $$

We can solve for $c_{jt}$ to get:

$$ c_{jt} = s_{jt} \times \Big (\frac{\partial s_{jt}}{\partial p_{jt}} \Big)^{-1} + p_{jt} $$

In [12]:
function get_marginal_cost(product_data::DataFrame, alpha, sigma)
    # Compute price elasticities using the price_elasticity_nested_logit function
    price_elasticities = price_elasticity_nested_logit(product_data, alpha, sigma)
    
    # Merge the original product_data with the computed price elasticities
    dd = innerjoin(product_data, price_elasticities, on=[:product_ID, :nest, :t])
    
    # Calculate marginal_cost
    dd[:, :marginal_cost] = (dd[:, :market_share] .+ dd[:, :price] .* dd[:, :price_derivative]) ./ dd[:, :price_derivative]
    
    # Create result DataFrame
    result = select(dd, :product_ID, :marginal_cost, :t)
    
    return result
end

marginal_cost = get_marginal_cost(product_data, alpha_hat_nested, sigma_hat)
Out[12]:
1000×3 DataFrame
975 rows omitted
Rowproduct_IDmarginal_costt
Int64Float64Int64
110.8762311
220.6177291
331.820131
441.176691
551.077131
661.306611
772.89281
882.310991
991.958681
10102.376631
1111.371892
1221.164142
1331.625492
⋮⋮⋮⋮
98992.1107799
990101.7492799
99111.1073100
99221.63937100
99332.31675100
99441.57552100
99551.51497100
99662.1437100
99772.43904100
99882.17826100
99992.31966100
1000102.69466100

Problem 3(b)¶

Now assume that the firms who own products 1 and 2 merge at $t = 100$, so that a single firm now owns both products. Simulate how the prices of all firms would change in this scenario.

Solution¶

The FOCs for firm $1$ are now given by:

$$ \frac{\partial \Pi_{1t}}{\partial p_{1t}} = s_{1t} + (p_{1t} - c_{1t})\times \frac{\partial s_{1t}}{\partial p_{1t}} + (p_{2t} - c_{2t}) \times \frac{\partial s_{2t}}{\partial p_{1t}} = 0 $$

$$ \frac{\partial \Pi_{1t}}{\partial p_{2t}} = s_{2t} + (p_{2t} - c_{2t})\times \frac{\partial s_{2t}}{\partial p_{2t}} + (p_{1t} - c_{1t}) \times \frac{\partial s_{1t}}{\partial p_{2t}} = 0 $$

The FOCs for firms $f = 3,...,10$ owning products $j = 3,,..,10$ are given by:

$$ \frac{\partial \Pi_{ft}}{\partial p_{jt}} = s_{jt} + (p_{jt} - c_{jt})\times \frac{\partial s_{jt}}{\partial p_{jt}} = 0 $$

This gives us a system with 10 equations (one for each product) and 10 unknowns ($p_{jt}$ for $j = 1,2,..,10$).

Note that each product's share $s_{jt}$ is a function of the vector of prices $\mathbf{p}_t$, the vectors of characteristics of all products ($s_{jt}$, $c_{jt}$, $Diet_j$, $Regular_j$), and of the unobserved product quality $\boldsymbol{\xi}_t$. We can estimate $\boldsymbol{\xi}_t$ as follows:

$$ \hat{\xi}_{jt} = \delta_{jt} - \hat{\alpha} p_{jt} - \hat{\beta}_1 s_{jt} - \hat{\beta}_2 c_{jt} - \hat{\gamma_d} Diet_j - \hat{\gamma_r} Regular_j $$

We then solve for the vector of prices that solves the system of equations to get the simulated vector of prices.

Alternatively, we may write the FOCs in matrix notation as:

$$ \mathbf{s}_t(\mathbf{p}_t) + (\Omega \circ \Delta(\mathbf{p}_t))(\mathbf{p}_t - \mathbf{c}_t) = 0 $$

Where $\mathbf{s}_t$ represents the vector of shares for all firms, $\mathbf{p}_t$ the vector of prices of all firms, $\Omega$ is an ownership matrix with element $ij$ equal to one if product $i$ and $j$ are owned by the same firm, $\Delta(p_t)$ is the Jacobian matrix derived in part 2(e), $\circ$ denotes the Hadamard product, and $\mathbf{c}_t$ is the vector of marginal costs of all firms.

In [13]:
xi_hat = nested_logit_result[:xi_hat]

# Construct firm_primitives DataFrame
firm_primitives = select(product_data, :product_ID, :nest, :sugar, :caffeine, :t)

# Merge with xi_hat DataFrame
firm_primitives = innerjoin(firm_primitives, xi_hat, on=[:product_ID, :t])

# merge with firm_primitives
firm_primitives = innerjoin(firm_primitives, marginal_cost, on=[:product_ID, :t])

initial_prices = product_data[product_data.t .== 100, :price]

model_parameters = Dict(
    :alpha => coef(nested_logit_result[:coefficients])[6],
    :beta_1 => coef(nested_logit_result[:coefficients])[1],
    :beta_2 => coef(nested_logit_result[:coefficients])[2],
    :gamma_d  => coef(nested_logit_result[:coefficients])[3],
    :gamma_r  => coef(nested_logit_result[:coefficients])[4],
    :sigma => coef(nested_logit_result[:coefficients])[5]
)
Out[13]:
Dict{Symbol, Float64} with 6 entries:
  :alpha   => -0.466178
  :beta_1  => 0.494104
  :beta_2  => 0.490971
  :gamma_r => -3.56714
  :gamma_d => -1.53519
  :sigma   => 0.494244
In [14]:
function merger_sim_mat(model_parameters, firm_primitives, ownership_matrix, initial_guess)
    alpha = model_parameters[:alpha]
    beta_1 = model_parameters[:beta_1]
    beta_2 = model_parameters[:beta_2]
    gamma_d = model_parameters[:gamma_d]
    gamma_r = model_parameters[:gamma_r]
    sigma = model_parameters[:sigma]

    dd = firm_primitives[firm_primitives.t .== 100, :]
    sort!(dd, :product_ID)

    function FOCs(par)
        data = copy(dd)
        
        data.diet = ifelse.(data.nest .== "Diet", 1, 0)
        data.regular = ifelse.(data.nest .== "Regular", 1, 0)
        data.price = par
        
        data.diet = ifelse.(data.nest .== "Diet", 1, 0)
        data.regular = ifelse.(data.nest .== "Regular", 1, 0)
        
        data.delta = alpha .* data.price .+ beta_1 .* data.sugar .+ beta_2 .* data.caffeine .+ gamma_d .* data.diet .+ gamma_r .* data.regular .+ data.xi
        
        data_grouped = combine(groupby(data, :nest), 
            :delta => (x -> sum(exp.(x ./ (1 - sigma)))) => :D_g)
        data = leftjoin(data, data_grouped, on=:nest)
        
        data.inside_share = exp.(data.delta ./ (1 - sigma)) ./ data.D_g
        data.group_share = data.D_g.^(1 - sigma) ./ (data.D_g[1]^(1-sigma) + data.D_g[end]^(1-sigma) + 1)
        data.market_share = data.inside_share .* data.group_share
        
        Jacobian = build_jacobian_matrix_nested_logit(data, alpha, sigma, 100)
        
        FOC_vector = data.market_share .+ (ownership_matrix .* Jacobian) * (data.price - data.marginal_cost)
        return FOC_vector
    end

    solution = nlsolve(FOCs, initial_guess)
    
    result = DataFrame(product_ID = dd.product_ID, nest = dd.nest, price = solution.zero)

    return result
end

ownership_matrix = Matrix{Int}(I, 10, 10)  # Create a mutable 10x10 identity matrix
ownership_matrix[1, 2] = 1
ownership_matrix[2, 1] = 1

merger_prices = merger_sim_mat(model_parameters, firm_primitives, ownership_matrix, initial_prices)
  
merger_prices
Out[14]:
10×3 DataFrame
Rowproduct_IDnestprice
Int64String7Float64
11Diet2.62634
22Diet3.15841
33Diet4.05187
44Diet2.73484
55Diet2.69931
66Regular3.25239
77Regular3.57224
88Regular3.54617
99Regular3.71483
1010Regular3.91482

Problem 3(c)¶

How does the average price of the products from the merging firms change after the merger? How does the average price of competing products in the same nest change after the merger? How does the average price for competing products in a different nest change after the merger?

Solution¶

In [15]:
# Get original prices
original_prices_data = DataFrame(
    product_ID = product_data[product_data.t .== 100, :product_ID],
    original_price = product_data[product_data.t .== 100, :price]
)

# Merge merger_prices with original_prices
merger_prices = leftjoin(merger_prices, original_prices_data, on=:product_ID)

# Calculate the average price difference for different groups
merging_firms_diff = mean(merger_prices[merger_prices.product_ID .<= 2, :price] .- merger_prices[merger_prices.product_ID .<= 2, :original_price])

competitors_same_nest_diff = mean(merger_prices[(merger_prices.product_ID .> 2) .& (merger_prices.nest .== "Diet"), :price] .- merger_prices[(merger_prices.product_ID .> 2) .& (merger_prices.nest .== "Diet"), :original_price])

competitors_other_nest_diff = mean(merger_prices[(merger_prices.product_ID .> 2) .& (merger_prices.nest .== "Regular"), :price] .- merger_prices[(merger_prices.product_ID .> 2) .& (merger_prices.nest .== "Regular"), :original_price])

# Create result DataFrame
result = DataFrame(
    Group = ["Merging products", "Competitors in same nest", "Competitors in a different nest"],
    Avg_Change = [merging_firms_diff, competitors_same_nest_diff, competitors_other_nest_diff]
)

result
Out[15]:
3×2 DataFrame
RowGroupAvg_Change
StringFloat64
1Merging products0.220639
2Competitors in same nest0.00777896
3Competitors in a different nest0.000393117

Problem 3(d)¶

Now instead assume that all firms collude during the last period (t = 100) and choose the prices that maximize the sum of all profits. Simulate how the prices of all firms would change in this scenario.

Solution¶

In [16]:
collusion_matrix = ones(10, 10)

collusion_prices = merger_sim_mat(model_parameters, firm_primitives, collusion_matrix, initial_prices)

collusion_prices
Out[16]:
10×3 DataFrame
Rowproduct_IDnestprice
Int64String7Float64
11Diet5.79755
22Diet6.32962
33Diet7.007
44Diet6.26577
55Diet6.20522
66Regular6.83395
77Regular7.12929
88Regular6.86851
99Regular7.00991
1010Regular7.3849