name | value | ||
---|---|---|---|
demand (theta ) |
price | -0.0068 | |
type 1 | -12.5906 | ||
type 2 | -12.1095 | ||
type 3 | -11.7011 | ||
type 4 | -11.3012 | ||
quality | 4.8860 | ||
prior | 12.2260 | ||
2.1134 | |||
supply (c ) |
mean entry cost (type 1) | 55,496 | |
mean entry cost (type 2) | 96,673 | ||
mean entry cost (type 3) | 161,946 | ||
mean entry cost (type 4) | 270,233 | ||
mean fixed cost (type 1) | 2,580 | ||
mean fixed cost (type 2) | 3,577 | ||
mean fixed cost (type 3) | 4,562 | ||
mean fixed cost (type 4) | 5,751 | ||
other (params ) |
discount factor | 0.995 | |
revenue fee | 0.142 | ||
review prob. | 0.7041 | ||
max. no. of reviews | 20 | ||
arrival rate | 10,000 | ||
max. no. of listings | 10,000 |
Function U_s(p,theta,t,params)
characterizes a guests's indirect utility of renting a property in state
The unobserved quality
ccp_s(p,P,s,theta,t,params)
characterizes the probability that a guest intends to book the property at rate
dccp_s(p,P,s,theta,t,params)
) and second-order (d2ccp_s(p,P,s,theta,t,params)
) derivatives of
The number of arriving guests is q_s(p,P,s,theta,t,params)
characterizes the probability that at least one of these consumers books the property, again assuming its rental rate is
Function dq_s(p,P,s,theta,t,params)
and function d2q_s(p,P,s,theta,t,params)
describe the first- and second-order derivatives of
Strictly speaking,
If a property is booked (
Accordingly, the probability params
.
Function dT_s(dq,theta,params)
stores the transition matrix
... | ||||||||
---|---|---|---|---|---|---|---|---|
0 | 0 | 0 | ... | 0 | ||||
0 | 0 | 0 | ... | 0 | ||||
0 | 0 | 0 | ... | 0 | ||||
0 | 0 | 0 | 0 | 0 | ... | 0 | ||
0 | 0 | 0 | 0 | 0 | ... | 0 | ||
0 | 0 | 0 | 0 | 0 | ... | 0 | ||
... | ... | ... | ... | ... | ... | ... | ... | 0 |
0 | 0 | 0 | 0 | 0 | 0 | ... | 1 |
Function dT_s(q,theta,params)
and d2T_s(q,theta,params)
store the first-order and second-order derivatives of
Types are equally distributed in the host population, meaning 2,500 properties have a certain type. If a host is inactive and has not yet entered the market, they can do so at the start of the following month at entry cost
Denote the number of properties of type
If a host is active they have entered the market. At the end of each month they have to pay the operating cost
The expected, total operating costs of properties in a certain state in a given month are the number of active hosts
F_s(p,P,s,q,chi,lamb,theta,t,params)
contains the expanded transition matrix
... | ... | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|
0 | 0 | 0 | ... | ... | ... | 0 | |||||
0 | 0 | 0 | ... | ... | ... | 0 | |||||
0 | 0 | 0 | ... | ... | ... | 0 | |||||
0 | 0 | 0 | 0 | 0 | ... | ... | ... | 0 | |||
0 | 0 | 0 | 0 | 0 | ... | ... | ... | 0 | |||
0 | 0 | 0 | 0 | 0 | ... | ... | ... | 0 | |||
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
0 | 0 | 0 | 0 | 0 | 0 | ... | ... | 0 | |||
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
0 | 0 | 0 | 0 | 0 | 0 | ... | ... | ... | |||
0 | 0 | 0 | 0 | 0 | ... | ... | ... | 0 | |||
0 | 0 | 0 | 0 | 0 | 0 | ... | ... | 0 | |||
0 | 0 | 0 | 0 | 0 | 0 | ... | ... | ... | 0 | ||
0 | 0 | 0 | 0 | 0 | 0 | ... | ... | ... | 0 |
solver(theta,c,guess,t,tol,params)
finds an oblivious equilibrium of the model. guess
contains starting values for the prices
Conditional on guess
The FOC requires that
until
dV_s(p,P,s,V,theta,phi_bar,t,params)
and d2V_s(p,P,s,V,theta,phi_bar,t,params)
store the first- and second-order derivative of
In code:
while dP>.1:
      P1 = P0 - dV_s(P0,P_old,s_old,V_old,theta,phi_bar,t,params)/d2V_s(P0,P_old,s_old,V_old,theta,phi_bar,t,params)
      P1 = np.where(np.isnan(P1) == True,P_old,np.where((P1<0),0,np.where((P1>1000),1000,P1)))
      dP = np.max(np.abs(P1 - P0))
      P0 = P1
Having found
In code:
q_new = q_s(P_new,P_new,s_old,theta,t,params)
T = T_s(P_new,P_new,s_old,q_new,theta,t,params)
eV = T @ V_old
V_new = 30 * (q_new * P_new.T) + delta * eV - (1 - np.exp(-delta * eV/phi_bar)) * phi_bar
We use
In code:
eV = T @ V_new
chi = np.exp(-delta * eV/phi_bar).flatten()
lamb = (1-np.exp(-delta * V_new.reshape((231,4),order='F')[0,:]/[kappa1,kappa2,kappa3,kappa4]))
F = F_s(q_new,chi,lamb,theta,params)
We use
In code:
while np.max(np.abs(s_new - s_old))>10e-3:
      s_old = s_new
      s_new = (np.array([np.append(s_old,np.array([J/4-s_old[0,:231].sum(),J/4-s_old[0,231:462].sum(),J/4-s_old[0,462:693].sum(),J/4-
      s_old[0,693:].sum()]))])@ F)[:1,:-4]
We update
tol
is set to 0.000001. To save time, we solve the host's pricing problem only if
Our initial guess of
In code:
P_init = np.array([[300] * len(S)])
s_init = np.array([[J/(2 * len(S))] * len(S)])
V_init = (30 * q_s(300,P_init,s_init,theta,0,params) * P_init.T)/(1-delta)
s_star = np.where(s_star<0,0,s_star)
The solution to the model is
In code:
V_star,s_star,P_star,chi_star,lamb_star = solver(theta,c,[P_init,s_init,V_init],tol,params)
We generate 4 years worth of mock data. We draw the average number of properties from the equilibrium state distribution. We associate each property with the number of reviews, equilibrium price (after adding some noise), and demand (after adding some noise) of the corresponding state. We repeat this for
In code:
for t in range(1,13 * 4+1):
if (t==1):
index = np.repeat(range(0,924),states
p = (P_star.T + np.random.normal(loc = 0, scale = 25.0, size = (P_star.T).shape))
q = q_s(p,p,s_star,theta,0,params)
data = np.hstack((np.zeros((len(index),1)) + t, S[index,:], p[index,:], q[index,:]+ np.random.normal(loc = 0, scale = 0.15, size = q[index,:].shape)))
else:
p = (P_star.T + np.random.normal(loc = 0, scale = 25.0, size = (P_star.T).shape))
q = q_s(p,p,s_star,theta,0,params)
data = np.vstack((data, np.hstack((np.zeros((len(index),1)) + t, S[index,:], p[index,:], q[index,:] + np.random.normal(loc = 0, scale = 0.15, size = q[index,:].shape)))))
data = pd.DataFrame(data,columns=['period','K','N','type 1','type 2','type 3','type 4','p','q'])
data.to_pickle('data.pkl')
We estimate the demand parameters using GMM. xi(omicron,adata,params)
stores the structural error term
We retrieve
Rather than estimating
The objective function is stored in O(omicron,adata,W,params)
. Let
We minimize
dO(omicron,adata,W,params)
contains dU(omicron,adata,params)
).
To initiate the minimization, we choose appropriate starting values. For the first step of two-step GMM we set the weighting matrix equal to the inverse of the variance-covariance matrix of the instruments.
In code:
start_values = [0,0,0,-10,-10,-10,-10,0]
W1 = np.linalg.inv( ((Z(start_values,data,params)).T @ (Z(start_values,data,params)))/len(data))
res_demand = minimize(O, start_values, args=(data,W1,params), method='BFGS',jac=dO)
In the second step, we choose the efficient weighting matrix. Let
In code:
xi_hat = xi(res_demand.x,data,params)
W2 = np.linalg.inv( ((xi_hat * Z(res_demand.x,data,params)).T @ (xi_hat * Z(res_demand.x,data,params)))/len(data) )
res_demand = minimize(O, start_values, args=(data,W2,params), method='BFGS',jac=dO)
As we have chosen the efficient weighting matrix in the second step, the (heteroscedasticity robust) standard errors simplify to
In code:
G_bar = ( (Z(res_demand.x,data,params).T @ (-dU(res_demand.x,data,params))) )/len(data)
W2 = np.linalg.inv( ((xi_hat * Z(res_demand.x,data,params)).T @ (xi_hat * Z(res_demand.x,data,params)))/len(data) )
S_hat = np.diag(np.linalg.inv((G_bar.T @ W2) @ G_bar))**.5/len(data)
parameter | estimate | standard error |
---|---|---|
1.8388 | (0.0002) | |
2.6677 | (0.0004) | |
-0.0068 | (0.0000) | |
-12.9853 | (0.0019) | |
-12.4921 | (0.0019) | |
-12.0770 | (0.0019) | |
-11.6858 | (0.0019) | |
5.2759 | (0.0020) |
We convert
In code:
theta_hat = [expit(res_demand.x[0])*np.exp(res_demand.x[1]),
(1-expit(res_demand.x[0]))*np.exp(res_demand.x[1]),
res_demand.x[2],
res_demand.x[3:7],
res_demand.x[7]]
Our estimates of
We estimate
In code:
s_d = np.array([(data.groupby(['x'])['period'].count()/data.groupby(['period']).mean().shape[0]).reindex(np.arange(0,len(S)), fill_value=0)])
l(k,theta,guess,tol,s_d,params)
stores the log-likelihood function (times -1).
We exclude states for which we do not observe any observations in the mock data or for which the model predicts that there are no observations as otherwise the log-likelihood is undefined.
tol
is set to 1. Each candidate for guess
as in the 'Solving the Model' section to initiate the solution algorithm. After that, we use the model solution for the previous set of candidates as the starting values to find the model solution for the next set of candidates. Furthermore, we use the demand estimates k0
contains the starting values.
In code:
k0 = np.log([100000,100000,100000,100000,3000,3000,3000,3000])
res_supply = minimize(l, k0, args=(theta,[P_init,s_init,V_init],tol,s_d,params), method='BFGS')
We use the the numerical approximation of the inverse Hessian res_supply.hess_inv
) to compute the standard errors of the estimates.
(np.diag(res_supply.hess_inv)/len(data))**0.5
We use the delta method to compute the standard errors of
In code:
((np.exp(res_supply.x) * np.diag(res_supply.hess_inv) * np.exp(res_supply.x))/len(data))**0.5
parameter | estimate | standard error | parameter | estimate | standard error |
---|---|---|---|---|---|
10.8625 | (0.00016) | 52183 | (9.0024) | ||
11.4392 | (0.00016) | 92888 | (15.7957) | ||
11.9692 | (0.00017) | 157812 | (27.2976) | ||
12.4895 | (0.00020) | 265529 | (53.6090) | ||
7.84021 | (0.00001) | 2541 | (0.0216) | ||
8.17893 | (0.00001) | 3565 | (0.0268) | ||
8.42697 | (0.00001) | 4569 | (0.0312) | ||
8.66163 | (0.00001) | 5777 | (0.0671) |
We simulate the model forward for 10 years - starting at the stationary equilibrium (Sub
) and/or (2) if consumers receive a $ t
) subsidy for each day they book a property that has not been reviewed before. The corresponding function is stored in simulation1(theta,c,sol,t,Sub,It,params)
.
We calculate the sum of host profits, the consumer surplus and the cost of the subsidy per month.
The expected cost of the lump-sum subsidy is
In code:
((30 * s_new * q_new.T) @ t) + (s_new @ Sub)
We calculate the expected consumer surplus per month. As each property can only be booked once, we focus on the consumer surplus from the inside good, i.e., the consumer surplus from Airbnb bookings.
In code:
-(s_new @ q_new) * 30 * np.log(1 + (s_new @ np.array([np.diagonal(np.exp(U(P_new,theta,t,params)))]).T))/alpha
Note that our consumer surplus measure likely understates the true consumer surplus.
We distinguish hosts who are in the market and hosts who are outside the market. Hosts in the market receive the expected monthly revenue of renting out the property as well as the lump-sum subsidy. They also pay the operating cost. See the 'Market Entry & Exit' section for the total expected operation costs per month.
In code:
s_new @ ((1+f)*np.array([np.diagonal(30 * q_s(P_new,P_new,s_new,theta,t,params)*P_new)]).T + Sub - (np.array([np.repeat(c[4:],231)]).T - np.array([chi_new]).T * (delta * eV_in + np.array([np.repeat(c[4:],231)]).T)))
Hosts who are currently outside the market do not earn revenue but pay the entry cost if they decide to enter that month.
-(J/4-s_new[0,:231].sum()) * (c[0] - (1-lamb_new[0]) * (delta * eV_out[0] + c[0]))
-(J/4-s_new[0,231:462].sum()) * (c[1] - (1-lamb_new[231])*(delta * eV_out[1] + c[1]))
-(J/4-s_new[0,462:693].sum()) * (c[2] - (1-lamb_new[462])*(delta * eV_out[2] + c[2]))
-(J/4-s_new[0,693:].sum()) * (c[3] - (1-lamb_new[693])*(delta * eV_out[3] + c[3]))
We calculate social welfare as the present discounted value of the sum of host profits and the consumer surplus less the cost of the subsidy over the 10 year time horizon.
For counterfactual 1, we maximize social welfare over Sub_prim(Sub,theta,c,sol,It,params)
. Initially, we set the the lump-sum subsidy to zero.
In code:
minimize(Sub_prim, [0,0,0,0], args=(theta_hat,c_hat,[P_star,s_star,V_star],'constrained',130,params), method='BFGS')
We find that a lump-sum subsidy corresponding to more or less 20-30% (depending on property type) of producer surplus (i.e., revenue) maximizes welfare.
type |
|
|
|
---|---|---|---|
1 | $409.51 | 21.32% | 79.61 |
2 | $646.95 | 23.68% | 77.04 |
3 | $945.56 | 26.47% | 65.74 |
4 | $1309.57 | 28.99% | 86.71 |
Subsidizing market entry raises social welfare by a bit below $2,000 per day. The cost of the subsidy amount to roughly $25,000 per day. Each Airbnb guest is on average better off by about $15.29 per day. Each host gains about $8.38 per day.
For counterfactual 2, we search for the welfare-maximizing subsidy t_prim(t,theta,c,sol,Sub,It,params)
. The forward simulation that keeps host revenues at their optimal level is simulation2(theta,c,sol,t,Sub,It,params)
.
In code:
W1_c,CS1_c,PS1_c,GS1_c,P1_c,s1_c,V1_c = simulation1(theta_hat,c_hat,[P_star,s_star,V_star],np.zeros((S.shape[0],1)),Sub_c,1000,params)
minimize(t_prim, [0,0,0,0], args=(theta_hat,c_hat,[P1_c,s1_c,V1_c],[409.51, 646.95, 945.56, 1309.57],130,params), method='BFGS')
We find that a per-day subsidy corresponding to about 20-21% of the rental rate maximizes welfare. From a welfare perspective, rental rates should be 11-16% lower. All changes are relative to counterfactual 1.
type |
|
price |
|
|
|
|
|
---|---|---|---|---|---|---|---|
1 | $41.60 | 20.40% | $212.94 | -$32.58 | -15.98% | 17.63% | -18.00 |
2 | $47.28 | 20.61% | $243.63 | -$33.06 | -14.41% | 16.13% | -15.67 |
3 | $53.98 | 20.96% | $277.82 | -$33.66 | -13.07% | 14.90% | -13.92 |
4 | $59.47 | 20.57% | $316.06 | -$32.59 | -11.27% | 12.97% | -11.33 |
Subsidizing social learning raises social welfare by a bit less than $2,000 per day. This compares to about $24,000 paid in subsidies each day. Each guest is better off by about $9.93 per day. By design, host profit does not change in any meaningful way.