2021-11-10

Intro

In this session, I am going to cover demand estimation.

  • Compute equilibrium outcomes with Logit demand
  • Simulate a dataset
  • Estimate Logit demand
  • Compare different instruments
  • Include supply

Model

In this first part, we are going to assume that consumer \(i \in \lbrace1,...,I\rbrace\) utility from good \(j \in \lbrace1,...,J\rbrace\) in market \(t \in \lbrace1,...,T\rbrace\) takes the form

\[ u_{ijt} = \boldsymbol x_{jt} \boldsymbol \beta_{it} - \alpha p_{jt} + \xi_{jt} + \epsilon_{ijt} \]

where

  • \(\xi_{jt}\) is type-1 extreme value distributed
  • \(\boldsymbol \beta\) has dimension \(K\)
    • i.e. goods have \(K\) characteristics

Setup

We have \(J\) firms and each product has \(K\) characteristics

J = 3;                            # 3 firms == products
K = 2;                            # 2 product characteristics
c = rand(Uniform(0, 1), J);       # Random uniform marginal costs
ξ = rand(Normal(0, 1), J+1);      # Random normal individual shocks
X = rand(Exponential(1), J, K);   # Random exponential product characteristics
β = [.5, 2, -1];                  # Preferences (last one is for prices, i.e. alpha)

Code Demand

function demand(p::Vector, X::Matrix, β::Vector, ξ::Vector)::Tuple{Vector, Number}
    """Compute demand"""
    δ = 1 .+ [X p] * β              # Mean value
    u = [δ; 0] + ξ                  # Utility
    e = exp.(u)                     # Take exponential
    q = e ./ sum(e)                 # Compute demand
    return q[1:end-1], q[end]
end;

We can try with an example.

p = 2 .* c;
demand(p, X, β, ξ)
## ([0.5491221990381039, 0.24359787592994134, 0.1810840379517347], 0.026195887080220043)

Code Supply

function profits(p::Vector, c::Vector, X::Matrix, β::Vector, ξ::Vector)::Vector
    """Compute profits"""
    q, _ = demand(p, X, β, ξ)       # Compute demand
    pr = (p - c) .* q               # Compute profits
    return pr
end;

We can try with an example.

profits(p, c, X, β, ξ)
## 3-element Array{Float64,1}:
##  0.16600481498889777
##  0.0889567306013599
##  0.12280474674253772

Code Best Reply

We first code the best reply of firm \(j\)

function profits_j(pj::Number, j::Int, p::Vector, c::Vector, X::Matrix, β::Vector, ξ::Vector)::Number
    """Compute profits of firm j"""
    p[j] = pj                       # Insert price of firm j
    pr = profits(p, c, X, β, ξ)     # Compute profits
    return pr[j]
end;

Let’s test it.

j = 1;
obj_fun(pj) = - profits_j(pj[1], j, copy(p), c, X, β, ξ);
pj = optimize(x -> obj_fun(x), [1.0], LBFGS()).minimizer[1]
## 1.706820256123432

What are the implied profits now?

print("Profits old: ",  round.(profits(p, c, X, β, ξ), digits=4))
## Profits old: [0.166, 0.089, 0.1228]
p_new = copy(p);
p_new[j] = pj;
print("Profits new: ",  round.(profits(p_new, c, X, β, ξ), digits=4))
## Profits new: [0.4045, 0.1405, 0.1939]

Indeed firm 1 has increased its profits.

Code Equilibrium

We can now compute equilibrium prices

function equilibrium(c::Vector, X::Matrix, β::Vector, ξ::Vector)::Vector
    """Compute equilibrium prices and profits"""
    p = 2 .* c;
    dist = 1;
    iter = 0;

    # Until convergence
    while (dist > 1e-8) && (iter<1000)

        # Compute best reply for each firm
        p1 = copy(p);
        for j=1:length(p)
            obj_fun(pj) = - profits_j(pj[1], j, p, c, X, β, ξ);
            optimize(x -> obj_fun(x), [1.0], LBFGS()).minimizer[1];
        end

        # Update distance
        dist = max(abs.(p - p1)...);
        iter += 1;
    end
    return p
end;

Code Equilibrium

Let’s test it

# Compute equilibrium prices
p_eq = equilibrium(c, X, β, ξ);
print("Equilibrium prices: ",  round.(p_eq, digits=4))
## Equilibrium prices: [1.9722, 1.7173, 2.0356]
# And profits
pi_eq = profits(p_eq, c, X, β, ξ);
print("Equilibrium profits: ",  round.(pi_eq, digits=4))
## Equilibrium profits: [0.6699, 0.3521, 0.3574]

As expected the prices of the first 2 firms are lower and their profits are higher.

DGP

Let’s generate our Data Generating Process (DGP).

  • \(\boldsymbol x \sim exp(V_{x})\)
  • \(\xi \sim N(0, V_{\xi})\)
  • \(w \sim N(0, 1)\)
  • \(\omega \sim N(0, 1)\)
function draw_data(J::Int, K::Int, rangeJ::Vector, varX::Number, varξ::Number)::Tuple
    """Draw data for one market"""
    J_ = rand(rangeJ[1]:rangeJ[2])              # Number of firms (products)
    X_ = rand(Exponential(varX), J_, K)         # Product characteristics
    ξ_ = rand(Normal(0, varξ), J_+1)            # Product-level utility shocks
    w_ = rand(Uniform(0, 1), J_)                # Cost shifters
    ω_ = rand(Uniform(0, 1), J_)                # Cost shocks
    c_ = w_ + ω_                                # Cost
    j_ = sort(sample(1:J, J_, replace=false))   # Subset of firms
    return X_, ξ_, w_, c_, j_
end;

Equilibrium

We first compute the equilibrium in one market.

function compute_mkt_eq(J::Int, b::Vector, rangeJ::Vector, varX::Number, varξ::Number)::DataFrame
    """Compute equilibrium one market"""

    # Initialize variables
    K = size(β, 1) - 1
    X_, ξ_, w_, c_, j_ = draw_data(J, K, rangeJ, varX, varξ)

    # Compute equilibrium
    p_ = equilibrium(c_, X_, β, ξ_)      # Equilibrium prices
    q_, q0 = demand(p_, X_, β, ξ_)       # Demand with shocks
    pr_ = (p_ - c_) .* q_               # Profits

    # Save to data
    q0_ = ones(length(j_)) .* q0
    df = DataFrame(j=j_, w=w_, p=p_, q=q_, q0=q0_, pr=pr_)
    for k=1:K
      df[!,"x$k"] = X_[:,k]
      df[!,"z$k"] = sum(X_[:,k]) .- X_[:,k]
    end
    return df
end;

Simulate Dataset

We can now write the code to simulate the whole dataset.

function simulate_data(J::Int, b::Vector, T::Int, rangeJ::Vector, varX::Number, varξ::Number)
    """Simulate full dataset"""
    df = compute_mkt_eq(J, β, rangeJ, varX, varξ)
    df[!, "t"] = ones(nrow(df)) * 1
    for t=2:T
        df_temp = compute_mkt_eq(J, β, rangeJ, varX, varξ)
        df_temp[!, "t"] = ones(nrow(df_temp)) * t
        append!(df, df_temp)
    end
    CSV.write("../data/logit.csv", df)
end;

Simulate Dataset (2)

We generate the dataset by simulating many markets that differ by

  • number of firms (and their identity)
  • their marginal costs
  • their product characteristics
# Set parameters
J = 10;                 # Number of firms
K = 2;                  # Product caracteristics
T = 500;                # Markets
β = [.5, 2, -1];        # Preferences
rangeJ = [2, 6];        # Min and max firms per market
varX = 1;               # Variance of X
varξ = 2;               # Variance of xi

# Simulate
df = simulate_data(J, β, T, rangeJ, varX, varξ);

The Data

What does the data look like? Let’s switch to R!

# Read data
df = fread("../data/logit.csv")
kable(df[1:6,], digits=4)
j w p q q0 pr x1 z1 x2 z2 t
2 0.7551 1.9353 0.0201 0.0219 0.0205 0.3786 2.9567 0.7150 1.1575 1
3 0.4460 4.3041 0.7404 0.0219 2.8514 2.1026 1.2326 0.4512 1.4212 1
6 0.9402 2.5264 0.1311 0.0219 0.1509 0.7970 2.5383 0.5200 1.3525 1
10 0.4002 1.8530 0.0865 0.0219 0.0946 0.0571 3.2782 0.1863 1.6862 1
4 0.5314 1.7842 0.0034 0.5238 0.0034 0.8688 1.4175 0.0895 1.4332 2
8 0.2636 2.8325 0.4728 0.5238 0.8969 1.4175 0.8688 1.4332 0.0895 2

Estimation

First we need to compute the dependent variable

df$y = log(df$q) - log(df$q0)

Now we can estimate the logit model. The true values are \(alpha=1\).

ols <- lm(y ~ x1 + x2 + p, data=df)
kable(tidy(ols), digits=4)
term estimate std.error statistic p.value
(Intercept) -1.5203 0.1578 -9.6327 0
x1 0.3382 0.0569 5.9456 0
x2 1.1879 0.0651 18.2468 0
p 0.3826 0.0687 5.5732 0

The estimate of \(\alpha = 1\) is biased (positive and significant) since \(p\) is endogenous. We need instruments.

IV 1: Cost Shifters

First set of instruments: cost shifters.

fm_costiv <- ivreg(y ~ x1 + x2 + p | x1 + x2 + w, data=df)
kable(tidy(fm_costiv), digits=4)
term estimate std.error statistic p.value
(Intercept) 0.7224 0.5213 1.3858 0.1660
x1 0.5022 0.0710 7.0704 0.0000
x2 1.8395 0.1594 11.5388 0.0000
p -0.8400 0.2787 -3.0145 0.0026

Now the estimate of \(\alpha\) is negative and significant.

IV 2: BLP Instruments

Second set of instruments: product characteristics of other firms in the same market.

fm_blpiv <- ivreg(y ~ x1 + x2 + p | x1 + x2 + z1 + z2, data=df)
kable(tidy(fm_blpiv), digits=4)
term estimate std.error statistic p.value
(Intercept) 1.1162 0.4419 2.5261 0.0116
x1 0.5309 0.0694 7.6457 0.0000
x2 1.9539 0.1381 14.1440 0.0000
p -1.0547 0.2340 -4.5075 0.0000

Also the BLP instruments deliver an estimate of \(\alpha\) is negative and significant.

Appendix

References