This short tutorial covers the very basic use cases to get you started with markets. More usage details can be found in the documentation of the package.
Load the required libraries.
Prepare the data. Normally this step is long and depends on the nature of the data and the considered market. For this example, we will use simulated data. Although we could simulate data independently from the package, we will use the top-level simulation functionality of markets to simplify the process. See the documentation of simulate_data
for more information on the simulation functionality. Here, we simulate data using a data generating process for a market in disequilibrium with stochastic price dynamics.
nobs <- 1000
tobs <- 10
alpha_d <- -0.3
beta_d0 <- 6.8
beta_d <- c(0.3, -0.02)
eta_d <- c(0.6, -0.1)
alpha_s <- 0.6
beta_s0 <- 4.1
beta_s <- c(0.9)
eta_s <- c(-0.5, 0.2)
gamma <- 1.2
beta_p0 <- 0.9
beta_p <- c(-0.1)
sigma_d <- 1
sigma_s <- 1
sigma_p <- 1
rho_ds <- 0.0
rho_dp <- 0.0
rho_sp <- 0.0
seed <- 4430
stochastic_adjustment_data <- simulate_data(
"diseq_stochastic_adjustment", nobs, tobs,
alpha_d, beta_d0, beta_d, eta_d,
alpha_s, beta_s0, beta_s, eta_s,
gamma, beta_p0, beta_p,
sigma_d = sigma_d, sigma_s = sigma_s, sigma_p = sigma_p,
rho_ds = rho_ds, rho_dp = rho_dp, rho_sp = rho_sp,
seed = seed
)
Prepare the basic parameters for model initialization. The simulate_data
call uses Q
for the simulated traded quantity, P
for the simulated prices, id
for subject identification, and date
for time identification. It automatically creates the demand-specific variables Xd1
and Xd2
, the supply-specific variable Xs1
, the common (i.e., both demand and supply) variables X1
and X2
, and the price dynamics’ variable Xp1
.
The market specification has to be modified in two cases. For the diseq_directional
, the price variable is removed from the supply equation because the separation rule of the model can only be used for markets with exclusively either inelastic demand or supply. For the diseq_stochastic_adjustment
, the right-hand side of the price dynamics equation is appended in the market specification.
By default, the models are estimated by allowing the demand, supply, and price equations to have correlated error shocks. The default verbosity behavior is to display errors and warnings that might occur when estimating the models.
By default, all models are estimated using full information maximum likelihood based on the "BFGS"
optimization algorithm. The first equilibrium_model
call modifies the estimation behavior and estimates the model using two stage least squares. The diseq_basic
call modifies the default optimization behavior and estimates the model using the "Nelder-Mead"
optimization methods.
Standard errors are by default assumed to be homoscedastic. The second equilibrium_model
and diseq_deterministic_adjustment
calls modify this behavior by calculating clustered standard errors based on the subject identifier, while the diseq_basic
and diseq_directional
calls modify it by calculating heteroscedastic standard errors via the sandwich estimator.
eq_reg <- equilibrium_model(
market_spec, stochastic_adjustment_data,
estimation_options = list(method = "2SLS")
)
eq_fit <- equilibrium_model(
market_spec, stochastic_adjustment_data,
estimation_options = list(standard_errors = c("id"))
)
bs_fit <- diseq_basic(
market_spec, stochastic_adjustment_data,
estimation_options = list(
method = "Nelder-Mead", control = list(maxit = 1e+5),
standard_errors = "heteroscedastic"
)
)
dr_fit <- diseq_directional(
formula(update(Formula(market_spec), . ~ . | . - P)),
stochastic_adjustment_data,
estimation_options = list(standard_errors = "heteroscedastic")
)
da_fit <- diseq_deterministic_adjustment(
market_spec, stochastic_adjustment_data,
estimation_options = list(standard_errors = c("id"))
)
sa_fit <- diseq_stochastic_adjustment(
formula(update(Formula(market_spec), . ~ . | . | Xp1)),
stochastic_adjustment_data,
estimation_options = list(control = list(maxit = 1e+5))
)
All the model estimates support the summary
function. The eq_2sls
also provides the first-stage estimation, but it is not included in the summary and has to be explicitly asked.
summary(eq_reg@fit$first_stage_model)
#>
#> Call:
#> lm(formula = first_stage_formula, data = object@data)
#>
#> Residuals:
#> Min 1Q Median 3Q Max
#> -4.8178 -0.9065 0.0739 0.9472 5.0795
#>
#> Coefficients:
#> Estimate Std. Error t value Pr(>|t|)
#> (Intercept) 3.64003 0.01388 262.278 <2e-16 ***
#> Xd1 0.12367 0.01385 8.932 <2e-16 ***
#> Xd2 0.02181 0.01379 1.582 0.114
#> X1 0.53012 0.01398 37.920 <2e-16 ***
#> X2 -0.14884 0.01392 -10.689 <2e-16 ***
#> Xs1 -0.41736 0.01401 -29.793 <2e-16 ***
#> ---
#> Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#>
#> Residual standard error: 1.388 on 9994 degrees of freedom
#> Multiple R-squared: 0.2035, Adjusted R-squared: 0.2031
#> F-statistic: 510.7 on 5 and 9994 DF, p-value: < 2.2e-16
summary(eq_reg)
#> Equilibrium Model for Markets in Equilibrium:
#> Demand RHS : D_P + D_Xd1 + D_Xd2 + D_X1 + D_X2
#> Supply RHS : S_P + S_Xs1 + S_X1 + S_X2
#> Market Clearing : Q = D_Q = S_Q
#> Shocks : Correlated
#> Nobs : 10000
#> Sample Separation : Not Separated
#> Quantity Var : Q
#> Price Var : P
#> Key Var(s) : id, date
#> Time Var : date
#>
#> Least square estimation:
#> Method : 2SLS
#>
#> First Stage:
#>
#> Call:
#> lm(formula = first_stage_formula, data = object@data)
#>
#> Residuals:
#> Min 1Q Median 3Q Max
#> -4.8178 -0.9065 0.0739 0.9472 5.0795
#>
#> Coefficients:
#> Estimate Std. Error t value Pr(>|t|)
#> (Intercept) 3.64003 0.01388 262.278 <2e-16 ***
#> Xd1 0.12367 0.01385 8.932 <2e-16 ***
#> Xd2 0.02181 0.01379 1.582 0.114
#> X1 0.53012 0.01398 37.920 <2e-16 ***
#> X2 -0.14884 0.01392 -10.689 <2e-16 ***
#> Xs1 -0.41736 0.01401 -29.793 <2e-16 ***
#> ---
#> Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#>
#> Residual standard error: 1.388 on 9994 degrees of freedom
#> Multiple R-squared: 0.2035, Adjusted R-squared: 0.2031
#> F-statistic: 510.7 on 5 and 9994 DF, p-value: < 2.2e-16
#>
#>
#> Demand Equation:
#>
#> Call:
#> lm(formula = demand_formula, data = object@data)
#>
#> Residuals:
#> Min 1Q Median 3Q Max
#> -3.4735 -0.5629 0.0282 0.6062 3.1546
#>
#> Coefficients:
#> Estimate Std. Error t value Pr(>|t|)
#> (Intercept) 8.138365 0.077954 104.400 <2e-16 ***
#> P_FITTED -0.772875 0.021274 -36.330 <2e-16 ***
#> Xd1 0.278803 0.009154 30.455 <2e-16 ***
#> Xd2 0.014726 0.008751 1.683 0.0925 .
#> X1 0.622276 0.014412 43.177 <2e-16 ***
#> X2 -0.109598 0.009403 -11.656 <2e-16 ***
#> ---
#> Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#>
#> Residual standard error: 0.8795 on 9994 degrees of freedom
#> Multiple R-squared: 0.1877, Adjusted R-squared: 0.1873
#> F-statistic: 461.9 on 5 and 9994 DF, p-value: < 2.2e-16
#>
#>
#> Supply Equation:
#>
#> Call:
#> lm(formula = supply_formula, data = object@data)
#>
#> Residuals:
#> Min 1Q Median 3Q Max
#> -3.4792 -0.5661 0.0298 0.6070 3.1133
#>
#> Coefficients:
#> Estimate Std. Error t value Pr(>|t|)
#> (Intercept) 0.10239 0.25480 0.402 0.688
#> P_FITTED 1.43487 0.06999 20.502 <2e-16 ***
#> Xs1 0.92130 0.03050 30.205 <2e-16 ***
#> X1 -0.54807 0.03807 -14.398 <2e-16 ***
#> X2 0.21876 0.01376 15.900 <2e-16 ***
#> ---
#> Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#>
#> Residual standard error: 0.8801 on 9995 degrees of freedom
#> Multiple R-squared: 0.1865, Adjusted R-squared: 0.1862
#> F-statistic: 572.8 on 4 and 9995 DF, p-value: < 2.2e-16
summary(eq_fit)
#> Equilibrium Model for Markets in Equilibrium:
#> Demand RHS : D_P + D_Xd1 + D_Xd2 + D_X1 + D_X2
#> Supply RHS : S_P + S_Xs1 + S_X1 + S_X2
#> Market Clearing : Q = D_Q = S_Q
#> Shocks : Correlated
#> Nobs : 10000
#> Sample Separation : Not Separated
#> Quantity Var : Q
#> Price Var : P
#> Key Var(s) : id, date
#> Time Var : date
#>
#> Maximum likelihood estimation:
#> Method : BFGS
#> Convergence Status : success
#> Starting Values :
#> D_P D_CONST D_Xd1 D_Xd2 D_X1 D_X2 S_P
#> -0.77287 8.13837 0.27880 0.01473 0.62228 -0.10960 1.43487
#> S_CONST S_Xs1 S_X1 S_X2 D_VARIANCE S_VARIANCE RHO
#> 0.10239 0.92130 -0.54807 0.21876 0.77312 0.77429 0.99925
#>
#> Coefficients:
#> Estimate Std. Error z value Pr(z)
#> D_P -0.771928 0.03663 -21.0719 1.441e-98
#> D_CONST 8.134649 0.13347 60.9473 0.000e+00
#> D_Xd1 0.278247 0.01598 17.4155 6.296e-68
#> D_Xd2 0.002023 0.01291 0.1568 8.754e-01
#> D_X1 0.621663 0.02456 25.3070 2.676e-141
#> D_X2 -0.109469 0.01599 -6.8452 7.635e-12
#> S_P 1.490002 0.17705 8.4159 3.899e-17
#> S_CONST -0.098206 0.64480 -0.1523 8.789e-01
#> S_Xs1 0.944989 0.07671 12.3196 7.102e-35
#> S_X1 -0.577267 0.09615 -6.0035 1.931e-09
#> S_X2 0.227037 0.03386 6.7044 2.022e-11
#> D_VARIANCE 2.268433 0.13110 17.3034 4.438e-67
#> S_VARIANCE 4.374334 0.93742 4.6664 3.066e-06
#> RHO -0.508898 0.04535 -11.2220 3.179e-29
#>
#> -2 log L: 60383.4
summary(bs_fit)
#> Basic Model for Markets in Disequilibrium:
#> Demand RHS : D_P + D_Xd1 + D_Xd2 + D_X1 + D_X2
#> Supply RHS : S_P + S_Xs1 + S_X1 + S_X2
#> Short Side Rule : Q = min(D_Q, S_Q)
#> Shocks : Correlated
#> Nobs : 10000
#> Sample Separation : Not Separated
#> Quantity Var : Q
#> Price Var : P
#> Key Var(s) : id, date
#> Time Var : date
#>
#> Maximum likelihood estimation:
#> Method : Nelder-Mead
#> Max Iterations : 1e+05
#> Convergence Status : success
#> Starting Values :
#> D_P D_CONST D_Xd1 D_Xd2 D_X1 D_X2 S_P
#> 0.044738 5.161505 0.178662 -0.001769 0.185406 0.015149 0.128093
#> S_CONST S_Xs1 S_X1 S_X2 D_VARIANCE S_VARIANCE RHO
#> 4.857007 0.376491 0.143151 0.021754 0.871035 0.775016 0.000000
#>
#> Coefficients:
#> Estimate Std. Error z value Pr(z)
#> D_P -0.04710 0.01220 -3.86141 1.127e-04
#> D_CONST 5.96478 0.05151 115.79794 0.000e+00
#> D_Xd1 0.23523 0.01395 16.86213 8.546e-64
#> D_Xd2 -0.02331 0.01359 -1.71530 8.629e-02
#> D_X1 0.49477 0.02027 24.41089 1.311e-131
#> D_X2 -0.00153 0.01880 -0.08137 9.351e-01
#> S_P 0.36492 0.02044 17.85637 2.579e-71
#> S_CONST 4.81479 0.05047 95.39849 0.000e+00
#> S_Xs1 0.77298 0.02449 31.55999 1.308e-218
#> S_X1 -0.28517 0.02741 -10.40492 2.355e-25
#> S_X2 0.05738 0.02387 2.40392 1.622e-02
#> D_VARIANCE 0.85101 0.02030 41.92772 0.000e+00
#> S_VARIANCE 0.81718 0.02306 35.43429 5.065e-275
#> RHO 0.07948 0.04165 1.90818 5.637e-02
#>
#> -2 log L: 25178.57
summary(da_fit)
#> Deterministic Adjustment Model for Markets in Disequilibrium:
#> Demand RHS : D_P + D_Xd1 + D_Xd2 + D_X1 + D_X2
#> Supply RHS : S_P + S_Xs1 + S_X1 + S_X2
#> Short Side Rule : Q = min(D_Q, S_Q)
#> Separation Rule : P_DIFF analogous to (D_Q - S_Q)
#> Shocks : Correlated
#> Nobs : 9000
#> Sample Separation : Demand Obs = 3731, Supply Obs = 5269
#> Quantity Var : Q
#> Price Var : P
#> Key Var(s) : id, date
#> Time Var : date
#>
#> Maximum likelihood estimation:
#> Method : BFGS
#> Convergence Status : success
#> Starting Values :
#> D_P D_CONST D_Xd1 D_Xd2 D_X1 D_X2 S_P
#> 0.025528 5.258982 0.190255 0.002401 0.224474 0.012186 0.118691
#> S_CONST S_Xs1 S_X1 S_X2 P_DIFF D_VARIANCE S_VARIANCE
#> 4.900087 0.351146 0.175962 0.018346 0.967398 0.842610 0.769177
#> RHO
#> 0.000000
#>
#> Coefficients:
#> Estimate Std. Error z value Pr(z)
#> D_P -0.282794 0.013288 -21.2816 1.681e-100
#> D_CONST 6.858767 0.057986 118.2837 0.000e+00
#> D_Xd1 0.185900 0.013315 13.9612 2.690e-44
#> D_Xd2 0.008247 0.010004 0.8244 4.097e-01
#> D_X1 0.567684 0.015731 36.0862 3.730e-285
#> D_X2 -0.086836 0.012297 -7.0617 1.645e-12
#> S_P 0.208697 0.013135 15.8886 7.601e-57
#> S_CONST 4.799437 0.047213 101.6543 0.000e+00
#> S_Xs1 0.481558 0.013320 36.1534 3.292e-286
#> S_X1 -0.005406 0.013757 -0.3930 6.944e-01
#> S_X2 0.065150 0.009496 6.8605 6.862e-12
#> P_DIFF 0.614386 0.017231 35.6568 1.848e-278
#> D_VARIANCE 1.277953 0.032091 39.8228 0.000e+00
#> S_VARIANCE 0.760055 0.013867 54.8096 0.000e+00
#> RHO 0.539373 0.022816 23.6406 1.474e-123
#>
#> -2 log L: 45914.24
summary(sa_fit)
#> Stochastic Adjustment Model for Markets in Disequilibrium:
#> Demand RHS : D_P + D_Xd1 + D_Xd2 + D_X1 + D_X2
#> Supply RHS : S_P + S_Xs1 + S_X1 + S_X2
#> Price Dynamics RHS: I(D_Q - S_Q) + Xp1
#> Short Side Rule : Q = min(D_Q, S_Q)
#> Shocks : Correlated
#> Nobs : 9000
#> Sample Separation : Not Separated
#> Quantity Var : Q
#> Price Var : P
#> Key Var(s) : id, date
#> Time Var : date
#>
#> Maximum likelihood estimation:
#> Method : BFGS
#> Max Iterations : 1e+05
#> Convergence Status : success
#> Starting Values :
#> D_P D_CONST D_Xd1 D_Xd2 D_X1 D_X2 S_P
#> 0.025528 5.258982 0.190255 0.002401 0.224474 0.012186 0.118691
#> S_CONST S_Xs1 S_X1 S_X2 P_DIFF P_CONST P_Xp1
#> 4.900087 0.351146 0.175962 0.018346 0.968928 0.275093 -0.062290
#> D_VARIANCE S_VARIANCE P_VARIANCE RHO_DS RHO_DP RHO_SP
#> 0.842610 0.769177 1.600380 0.000000 0.000000 0.000000
#>
#> Coefficients:
#> Estimate Std. Error z value Pr(z)
#> D_P -0.3195795 0.01407 -22.70705 3.816e-114
#> D_CONST 6.9163194 0.06554 105.52937 0.000e+00
#> D_Xd1 0.2874772 0.01236 23.24957 1.437e-119
#> D_Xd2 0.0070640 0.01187 0.59507 5.518e-01
#> D_X1 0.6064175 0.01726 35.14010 1.647e-270
#> D_X2 -0.0980891 0.01253 -7.82584 5.043e-15
#> S_P 0.6163763 0.03164 19.48294 1.532e-84
#> S_CONST 3.9961080 0.07155 55.85101 0.000e+00
#> S_Xs1 0.8989133 0.03142 28.60723 5.462e-180
#> S_X1 -0.4975437 0.03701 -13.44240 3.412e-41
#> S_X2 0.2036846 0.01678 12.13517 6.877e-34
#> P_DIFF 1.2124441 0.04061 29.85630 7.272e-196
#> P_CONST 0.8350155 0.05185 16.10519 2.346e-58
#> P_Xp1 -0.0832652 0.01505 -5.53409 3.129e-08
#> D_VARIANCE 1.0591615 0.03176 33.34445 8.768e-244
#> S_VARIANCE 1.0052351 0.06763 14.86440 5.612e-50
#> P_VARIANCE 1.0188468 0.07944 12.82512 1.186e-37
#> RHO_DS -0.0252917 0.06887 -0.36726 7.134e-01
#> RHO_DP -0.0008456 0.04714 -0.01794 9.857e-01
#> RHO_SP -0.0202279 0.05417 -0.37338 7.089e-01
#>
#> -2 log L: 45126.03
Calculate marginal effects on the shortage probabilities. Markets offers two marginal effect calls out of the box. The mean marginal effects and the marginal effects ate the mean. Marginal effects on the shortage probabilities are state-dependent. If the variable is only in the demand equation, the output name of the marginal effect is the variable name prefixed by D_
. If the variable is only in the supply equation, the name of the marginal effect is the variable name prefixed by S_
. If the variable is in both equations, then it is prefixed by B_
.
diseq_abbrs <- c("bs", "dr", "da", "sa")
diseq_fits <- c(bs_fit, dr_fit, da_fit, sa_fit)
variables <- c("P", "Xd1", "Xd2", "X1", "X2", "Xs1")
apply_marginal <- function(fnc, ...) {
function(fit) {
sapply(variables, function(v) fnc(fit, v, ...), USE.NAMES = FALSE)
}
}
mspm <- sapply(diseq_fits, apply_marginal(shortage_probability_marginal))
colnames(mspm) <- diseq_abbrs
# Mean Shortage Probabilities' Marginal Effects
mspm
#> bs dr da sa
#> B_P -0.100154381 -0.008859036 -0.155828672 -0.172370413
#> D_Xd1 0.057180496 0.066005139 0.058940070 0.052943279
#> D_Xd2 -0.005665398 -0.000339893 0.002614653 0.001300937
#> B_X1 0.189587066 0.068923076 0.181699873 0.203311137
#> B_X2 -0.014320520 -0.024924848 -0.048187589 -0.055576183
#> S_Xs1 -0.187895816 -0.121583902 -0.152679292 -0.165548468
spmm <- sapply(
diseq_fits,
apply_marginal(shortage_probability_marginal, aggregate = "at_the_mean")
)
colnames(spmm) <- diseq_abbrs
# Shortage Probabilities' Marginal Effects at the Mean
spmm
#> bs dr da sa
#> B_P -0.12739838 -0.0095704990 -0.195712135 -0.230149573
#> D_Xd1 0.07273474 0.0713059667 0.074025446 0.070690050
#> D_Xd2 -0.00720650 -0.0003671896 0.003283858 0.001737016
#> B_X1 0.24115855 0.0744582412 0.228204924 0.271461735
#> B_X2 -0.01821599 -0.0269265452 -0.060520929 -0.074205513
#> S_Xs1 -0.23900725 -0.1313482223 -0.191756690 -0.221040887
Copy the disequilibrium model data frame and augment it with post-estimation data. The disequilibrium models can be used to estimate:
Shortage probabilities. These are the probabilities that the disequilibrium models assign to observing a particular extent of excess demand.
Normalized shortages. The point estimates of the shortages are normalized by the variance of the difference of demand and supply shocks.
Relative shortages: The point estimates of the shortages are normalized by the estimated supplied quantity.
fit <- sa_fit
mdt <- cbind(
fit@model@data,
shortage_indicators = c(shortage_indicators(fit)),
normalized_shortages = c(normalized_shortages(fit)),
shortage_probabilities = c(shortage_probabilities(fit)),
relative_shortages = c(relative_shortages(fit))
)
How is the sample separated post-estimation? The indices of the observations for which the estimated demand is greater than the estimated supply are easily obtained.
if (requireNamespace("ggplot2", quietly = TRUE)) {
pdt <- data.frame(
Shortage = c(mdt$normalized_shortages, mdt$relative_shortages),
Type = c(rep("Normalized", nrow(mdt)), rep("Relative", nrow(mdt))),
xpos = c(rep(-1.0, nrow(mdt)), rep(1.0, nrow(mdt))),
ypos = c(
rep(0.8 * max(mdt$normalized_shortages), nrow(mdt)),
rep(0.8 * max(mdt$relative_shortages), nrow(mdt))
)
)
ggplot2::ggplot(pdt) +
ggplot2::stat_density(ggplot2::aes(Shortage,
linetype = Type,
color = Type
), geom = "line") +
ggplot2::ggtitle(paste0("Estimated shortages densities (", name(fit), ")")) +
ggplot2::theme(
panel.background = ggplot2::element_rect(fill = "transparent"),
plot.background = ggplot2::element_rect(
fill = "transparent",
color = NA
),
legend.background = ggplot2::element_rect(fill = "transparent"),
legend.box.background = ggplot2::element_rect(
fill = "transparent",
color = NA
),
legend.position = c(0.8, 0.8)
)
} else {
summary(mdt[, grep("shortage", colnames(mdt))])
}
The estimated demanded and supplied quantities can be calculated per observation.
market <- cbind(
demand = demanded_quantities(fit)[, 1],
supply = supplied_quantities(fit)[, 1]
)
summary(market)
#> demand supply
#> Min. :3.175 Min. : 2.529
#> 1st Qu.:5.241 1st Qu.: 5.670
#> Median :5.684 Median : 6.366
#> Mean :5.683 Mean : 6.362
#> 3rd Qu.:6.129 3rd Qu.: 7.054
#> Max. :8.148 Max. :10.747
The package also offers basic aggregation functionality.
aggregates <- aggregate_demand(fit) |>
dplyr::left_join(aggregate_supply(fit), by = "date") |>
dplyr::mutate(date = as.numeric(date)) |>
dplyr::rename(demand = D_Q, supply = S_Q)
if (requireNamespace("ggplot2", quietly = TRUE)) {
pdt <- data.frame(
Date = c(aggregates$date, aggregates$date),
Quantity = c(aggregates$demand, aggregates$supply),
Side = c(rep("Demand", nrow(aggregates)), rep("Supply", nrow(aggregates)))
)
ggplot2::ggplot(pdt, ggplot2::aes(x = Date)) +
ggplot2::geom_line(ggplot2::aes(y = Quantity, linetype = Side, color = Side)) +
ggplot2::ggtitle(paste0(
"Aggregate estimated demand and supply (", name(fit), ")"
)) +
ggplot2::theme(
panel.background = ggplot2::element_rect(fill = "transparent"),
plot.background = ggplot2::element_rect(
fill = "transparent", color = NA
),
legend.background = ggplot2::element_rect(fill = "transparent"),
legend.box.background = ggplot2::element_rect(
fill = "transparent", color = NA
),
legend.position = c(0.8, 0.5)
)
} else {
aggregates
}