3  基礎編 2

using CSV
using DataFrames
using StringEncodings
using FixedEffectModels
using RegressionTables
using Plots
using LinearAlgebra
using Statistics
using Optim
using Printf
using ForwardDiff
using Random
using GLM
using Serialization
mutable struct datalist_struct
    X1::Array{Float64,2};
    X2::Array{Float64,2};
    Z::Array{Float64,2};
    ShareVec::Vector{Float64};
    marketIndex::Vector{Int64};
    logitshare::Vector{Float64};
    randomDrawMat::Array{Float64,2};
    marketIndexMat::BitMatrix
end

3.1 関数の読み込み

for file in readdir("functions/demand_estimation")
    include("functions/demand_estimation/" * file)
end

3.2 前回からの加工済みデータの読み込み

data = CSV.read("tmp/demand_estimation_1/data.csv", DataFrame);

3.3 データの準備

NIPPYOautoIDvec = [
    260, 4, 76, 104, 64, 54, 152, 153, 71, 197,
    42, 45, 114, 208, 209, 77, 236, 58, 127, 187,
    79, 175, 19, 117, 216, 112, 256, 119, 37, 158
];

dataNIPPYO = data[
    in(NIPPYOautoIDvec).(data[:, :NameID]), 
    [:year, :share, :NameID, :Sales, :price, :hppw, :FuelEfficiency, :size, :Name]
    ];
dataNIPPYO[!, :log_sales] = log.(dataNIPPYO[:, :Sales]);
dataNIPPYO[!, :log_price] = log.(dataNIPPYO[:, :price]);
dataNIPPYO[!, :log10_sales] = log10.(dataNIPPYO[:, :Sales]);
dataNIPPYO[!, :log10_price] = log10.(dataNIPPYO[:, :price]);

3.4 Logitモデルにおける価格弾力性行列の作成

data[!, :logit_share] = log.(data[:, :share]) .- log.(data[:, :share0]);

resultGH = reg(
    data, 
    @formula(logit_share ~ (
        price ~ iv_GH_own_hppw + iv_GH_own_FuelEfficiency + iv_GH_own_size + 
            iv_GH_other_hppw + iv_GH_other_FuelEfficiency + iv_GH_other_size
    ) + hppw + FuelEfficiency + size),
    Vcov.robust(),
    save = true
);

data2016 = dataNIPPYO[
    dataNIPPYO.year .== 2016, 
    [:price, :share, :NameID, :Name]
    ];
numCars2016 = nrow(data2016);

ownElasticity = (
    resultGH.coef[resultGH.coefnames .== "price"][1] .* 
    data2016.price .* 
    (1.0 .- data2016.share)
);
crossElasticity = (
    - resultGH.coef[resultGH.coefnames .== "price"][1] .* 
    data2016.price .* 
    data2016.share
);

elasticityMat = reduce(
    hcat, 
    [crossElasticity for car = 1:numCars2016]
    );
elasticityMat[diagind(elasticityMat)] = ownElasticity;

elasticityMat[[12, 13, 10, 1], [12, 13, 10, 1]]
4×4 Matrix{Float64}:
 -1.76455       0.00114929    0.00114929   0.00114929
  0.00122042   -0.818689      0.00122042   0.00122042
  0.000148175   0.000148175  -0.959449     0.000148175
  0.00184509    0.00184509    0.00184509  -0.67175

3.5 入れ子型ロジット推定のためのBLP操作変数の作成

transform!(
    groupby(data, [:year, :Maker, :Type]),
    (
        [:hppw, :FuelEfficiency, :size] .=> 
        sum .=> 
        [:hppw_sum_own, :FuelEfficiency_sum_own, :size_sum_own]
    ),
    (
        [:hppw, :FuelEfficiency, :size] .=> 
        (x -> sum(x.^2)) .=> 
        [:hppw_sqr_sum_own, :FuelEfficiency_sqr_sum_own, :size_sqr_sum_own]
    ),
    nrow => :group_n
);
transform!(
    groupby(data, [:year, :Type]),
    (
        [:hppw, :FuelEfficiency, :size] .=> 
        sum .=> 
        [:hppw_sum_mkt, :FuelEfficiency_sum_mkt, :size_sum_mkt]
    ),
    (
        [:hppw, :FuelEfficiency, :size] .=> 
        (x -> sum(x.^2)) .=> 
        [:hppw_sqr_sum_mkt, :FuelEfficiency_sqr_sum_mkt, :size_sqr_sum_mkt]
    ),
    nrow => :mkt_n
);

for variable in ["hppw", "FuelEfficiency", "size"]
    addIVColumnsForNestedLogit!(data, variable)
end

data[!, :iv_BLP_own_num_nest] = data[:, :group_n] .- 1;
data[!, :iv_BLP_other_num_nest] = data[:, :mkt_n] .- data[:, :group_n];

3.6 入れ子型ロジットモデルの推定

data = transform(
    groupby(data, [:year, :Type]),
    :Sales => sum => :sum_year_body
);
data[!, :inside_share] = data.Sales ./ data.sum_year_body;
data[!, :log_inside_share] = log.(data.Sales ./ data.sum_year_body);
resultOLS = reg(
    data, 
    @formula(
        logit_share ~ 
        price + log_inside_share + hppw + FuelEfficiency + size
        )
    );

resultBLPNested = reg(
    data, 
    @formula(logit_share ~ (
        price + log_inside_share ~ iv_BLP_own_hppw_nest + iv_BLP_own_FuelEfficiency_nest + iv_BLP_own_size_nest + 
            iv_BLP_other_hppw_nest + iv_BLP_other_FuelEfficiency_nest + iv_BLP_other_size_nest +
            iv_BLP_own_num_nest + iv_BLP_other_num_nest
    ) + hppw + FuelEfficiency + size),
    Vcov.robust()
);

regtable(resultOLS, resultBLPNested)

-----------------------------------------------
                               logit_share     
                          ---------------------
                                (1)         (2)
-----------------------------------------------
(Intercept)               -7.557***   -9.548***
                            (0.166)     (0.239)
price                     -0.307***   -0.654***
                            (0.013)     (0.053)
log_inside_share           0.782***    0.595***
                            (0.009)     (0.035)
hppw                      10.636***   18.925***
                            (0.657)     (1.965)
FuelEfficiency             0.055***    0.069***
                            (0.004)     (0.006)
size                       0.156***    0.227***
                            (0.008)     (0.012)
-----------------------------------------------
Estimator                       OLS          IV
-----------------------------------------------
N                             1,823       1,823
R2                            0.862       0.765
Within-R2                                      
First-stage F statistic                   1.667
-----------------------------------------------

3.6.1 入れ子型ロジットモデルにおける自己価格弾力性の計算

alpha1 = resultOLS.coef[resultOLS.coefnames .== "price"][1]
sigma1 = resultOLS.coef[resultOLS.coefnames .== "log_inside_share"][1]

alpha2 = resultBLPNested.coef[resultBLPNested.coefnames .== "price"][1]
sigma2 = resultBLPNested.coef[resultBLPNested.coefnames .== "log_inside_share"][1]

data[!, :own_elas_ols] = alpha1 .* data[:, :price] .* (
    1.0 .- sigma1 .* data[:, :inside_share] .- 
    (1.0 .- sigma1) .* data[:, :share]
) ./ (1.0 .- sigma1);
data[!, :own_elas_ivblp_nested] = alpha2 .* data[:, :price] .* (
    1.0 .- sigma2 .* data[:, :inside_share] .- 
    (1.0 .- sigma2) .* data[:, :share]
) ./ (1.0 .- sigma2);

describe(data[:, r"^own_elas"], :mean, :std, :median, :min, :max)
2×6 DataFrame
Rowvariablemeanstdmedianminmax
SymbolFloat64Float64Float64Float64Float64
1own_elas_ols-3.521312.55849-2.84383-17.7936-0.96257
2own_elas_ivblp_nested-4.049352.93775-3.2664-20.4015-1.11262
dataNIPPYO = data[
    in(NIPPYOautoIDvec).(data[:, :NameID]), 
    [
        :year, :share, :Type, :inside_share, 
        :NameID, :Sales, :price, :hppw, :FuelEfficiency, :size, :Name
        ]
    ];
dataNIPPYO[!, :log_sales] = log.(dataNIPPYO[:, :Sales]);
dataNIPPYO[!, :log_price] = log.(dataNIPPYO[:, :price]);

data2016 = dataNIPPYO[
    dataNIPPYO.year .== 2016, 
    [:price, :Type, :share, :inside_share, :NameID, :Name]
    ];
numCars2016 = nrow(data2016);

ownElasticityNestedLogit = (
    alpha2 .* 
    data2016.price .* 
    (
        1.0 .- sigma2 .* 
        data2016.inside_share .- 
        (1.0 .- sigma2) .* 
        data2016.share
        ) ./ 
    (1.0 .- sigma2)
);
crossElasticityOtherGroup = (
    - alpha2 .* 
    data2016.price .* 
    data2016.share
);

crossElasticityOtherGroup = reduce(
    hcat, 
    [crossElasticityOtherGroup for car = 1:numCars2016]
    );
crossElasticityOtherGroup[
    diagind(crossElasticityOtherGroup)
    ] = ownElasticityNestedLogit;
crossElasticitySameGroup = reduce(
    hcat,
    [
        - alpha2 .* data2016.price .* (
            sigma2 .* data2016.inside_share .+ (1.0 .- sigma2) .* data2016.share
        ) ./ (1.0 .- sigma2)
    for car in 1:numCars2016]
);

sameGroupIndicatorMat = (
    reduce(hcat, [data2016.Type for car = 1:numCars2016]) .==
    permutedims(reduce(hcat, [data2016.Type for car = 1:numCars2016]))
);
otherGroupIndicatorMat = (sameGroupIndicatorMat .== 0);

elasticityNestedLogitMat = (
    crossElasticitySameGroup .* 
    sameGroupIndicatorMat .+ 
    crossElasticityOtherGroup .* 
    otherGroupIndicatorMat
);
elasticityNestedLogitMat[
    diagind(elasticityNestedLogitMat)
    ] = ownElasticityNestedLogit;

elasticityNestedLogitMat[[12, 13, 10, 1], [12, 13, 10, 1]]
4×4 Matrix{Float64}:
 -5.1197       0.0477573    0.0477573    0.00136172
  0.050713    -2.34881      0.050713     0.001446
  0.00615725   0.00615725  -2.80217      0.000175564
  0.00218614   0.00218614   0.00218614  -1.83296

3.7 ランダム係数ロジットモデルの推定

3.7.1 下準備

sort!(data, [:year, :NameID]);

N = nrow(data);
T = length(unique(data.year));

X1 = hcat(ones(N), Matrix(data[:, [:price, :FuelEfficiency, :hppw, :size]]));
X2 = hcat(data.price, ones(N), data.size);
Z = hcat(
    ones(N),
    Matrix(data[:, [:FuelEfficiency, :hppw, :size]]),
    Matrix(data[:, r"^iv_GH.*(?<!nest)$"])
    );

Random.seed!(123);
Nsim = 500;
randomDrawMat = randn(size(X2)[2], Nsim);

marketIndex = data.year;

uniqueMarketIndex = sort(unique(data.year));
marketIndexMat = reduce(
    hcat, 
    [marketIndex .== market for market in uniqueMarketIndex]
    );

datalist = datalist_struct(
    X1, 
    X2, 
    Z, 
    data.share, 
    marketIndex, 
    data.logit_share, 
    randomDrawMat, 
    marketIndexMat
    );

3.7.2 推定

ここでの推定結果が元のサポートサイトのものとかなり異なることに注意されたい。 元の推定では、ランダム係数の推計値が初期値からほとんど変わっていないように見える。 もしかすると、本のコードでは市場シェアの更新(永続代入演算子が用いられている)が正しくされていないのかもしれない。

initial_x = [0.3, 18, 0.01];
delta_ini = calculateMeanUtil(initial_x, datalist, datalist.logitshare);
objFunc_for_Optim = OnceDifferentiable(
    x -> calculateGMMObjective(x, datalist, delta_ini),
    initial_x;
    autodiff = :forward
    );
@time resultGMM = optimize(
    objFunc_for_Optim,
    [0.0, 0.0, 0.0],
    [Inf, Inf, Inf],
    initial_x,
    Fminbox(LBFGS()),
    Optim.Options(show_trace = false)
);
resultGMM.minimizer
3-element Vector{Float64}:
  0.6407730207977352
 14.72488892325358
  1.689052459807443e-11
W = inv(datalist.Z' * datalist.Z);    
delta = calculateMeanUtil(resultGMM.minimizer, datalist, delta_ini);
beta_hat = (
    (datalist.X1' * datalist.Z * W * datalist.Z' * datalist.X1) \ 
    (datalist.X1' * datalist.Z * W * datalist.Z' * delta)
);

beta_hat
5-element Vector{Float64}:
 -31.984921285756943
  -1.0841982623293536
   0.1076677873797389
   8.581036411670775
   0.28298440012892323

3.7.3 標準誤差の計算

Xi = delta - X1 * beta_hat;
Omega_hat = reduce(+, Z[i,:] * Z[i,:]' .* Xi[i]^2 ./ N for i = 1:N);
Ddelta = ForwardDiff.jacobian(
    x -> calculateMeanUtil(x, datalist, delta), 
    resultGMM.minimizer
    );
G = Z' * hcat(- X1, Ddelta) ./ N;
AsyVarMat = (G' * W * G) \ G' * W * Omega_hat * W * G * inv(G' * W * G);
Ase = sqrt.(diag(AsyVarMat) ./ N);

DataFrame(
    Var = [
        "Const", "Price", "Fuel Efficiency", "hppw", 
        "size", "random_price", "random_constant", "random_size"
        ],
    Est = vcat(beta_hat, resultGMM.minimizer),
    se = Ase
)
8×3 DataFrame
RowVarEstse
StringFloat64Float64
1Const-31.98496.91814
2Price-1.08421.34743
3Fuel Efficiency0.1076680.0115801
4hppw8.581043.13587
5size0.2829840.127824
6random_price0.6407730.845573
7random_constant14.724921.1974
8random_size1.68905e-111.79294

3.8 価格弾力性行列の計算

market2016Index = data.year .== 2016;

elasmat = calculateElasticity(
    data[market2016Index, :].price,
    X2[market2016Index, :],
    beta_hat,
    resultGMM.minimizer,
    randomDrawMat,
    delta[market2016Index, :]
)

elasmat[[59, 80, 102, 113], [59, 80, 102, 113]]
4×4 Matrix{Float64}:
 -2.07694      0.0043804    0.00405532   0.00447937
  0.0204111   -1.92602      0.0185025    0.0248292
  0.00413169   0.00404559  -2.14446      0.00376725
  0.00295524   0.0035155    0.00243949  -1.51216

3.9 応用: プライシング

以下で求められる値はサポートサイトのものとかなり異なるが、これは上で述べた推定結果が原因だと思われる。

price_range = range(1.8, 4.0, step = 0.05);
ownpi_res = calculateRevenue.(
    price_range, 
    Ref(data), Ref(datalist), Ref(delta), Ref(beta_hat), Ref(resultGMM.minimizer),
    "ownpi"
);

plot(price_range, ownpi_res, legend = false)
xlabel!("Price")
ylabel!("Revenue")
price_range = range(1.8, 4.0, step = 0.05);
totalpi_res = calculateRevenue.(
    price_range, 
    Ref(data), Ref(datalist), Ref(delta), Ref(beta_hat), Ref(resultGMM.minimizer),
    "totalpi"
);

plot(price_range, totalpi_res, legend = false)
xlabel!("Price")
ylabel!("Revenue")
ownpi_optim_res = optimize(
    x -> - calculateRevenue(
        x[1], data, datalist, delta, 
        beta_hat, resultGMM.minimizer, "ownpi"
        ),
    [3.0]
);

@printf("Revenue-maximizing price: %.3f \n", ownpi_optim_res.minimizer[1])
@printf("Maximized revenue : %.3f", -ownpi_optim_res.minimum)
Revenue-maximizing price: 3.256 
Maximized revenue : 44811.735
totalpi_optim_res = optimize(
    x -> - calculateRevenue(
        x[1], data, datalist, delta, 
        beta_hat, resultGMM.minimizer, "totalpi"
        ),
    [3.0]
);

@printf("Revenue-maximizing price: %.3f \n", totalpi_optim_res.minimizer[1])
@printf("Maximized revenue : %.3f", -totalpi_optim_res.minimum)
Revenue-maximizing price: 3.508 
Maximized revenue : 570181.486
calculateRevenue(
    ownpi_optim_res.minimizer[1], 
    data,
    datalist,
    delta,
    beta_hat,
    resultGMM.minimizer,
    "totalpi"
    )
569858.7798782628