This example RMD uses the example data TradeData0014, which is included with this package. The data set consists of a panel of 44 countries trading with one another over the years 2000-2014. The trade data uses aggregated trade flows based on WIOD and information on FTAs is from the NSF-Kellogg database maintained by Scott Baier and Jeff Bergstrand. To find out more about the trade data, see “Timmer, Dietzenbacher, Los, Stehrer, and de Vries (2015).

data(TradeData0014) # loads data included with package
head(TradeData0014)
#>   exporter importer expcode impcode year        trade eu_enlargement other_fta
#> 1      AUS      AUS       1       1 2000 7.047775e+05              0         1
#> 2      AUS      AUS       1       1 2005 1.334877e+06              0         1
#> 3      AUS      AUS       1       1 2010 2.210350e+06              0         1
#> 4      AUS      AUS       1       1 2014 2.436575e+06              0         1
#> 5      AUS      AUT       1       2 2000 6.195446e+01              0         0
#> 6      AUS      AUT       1       2 2005 1.407591e+02              0         0
#>   FTA
#> 1   1
#> 2   1
#> 3   1
#> 4   1
#> 5   0
#> 6   0

Suppose the researcher wishes to use this data set to quantify general equilibrium trade and welfare effects of the EU enlargements that took place between 2000-2014. To first obtain the partial effects of these enlargements on trade flows, a PPML (Poisson pseudo-maximum likelihood) gravity specification may be used. Specifically, to obtain the “partial” estimates of the effects of EU enlargements on trade, we can use the following three-way gravity specification:

\[X_{ijt} = \exp\bigg[\ln(\alpha_{it}) + \ln(\alpha_{jt}) + \ln(\alpha_{ij}) + \beta \cdot FTA_{ijt}\bigg] + e_{ijt}\]

The Stata equivalent of this package uses the ppmlhdfe command created by Correia, Guimarães, & Zylkin (2019) to achieve this. In our example, we will use the ‘alpaca’ library by Amrei Stammann to do the same via the (Fixed-Effect Generalized Linear Model) function.

library(alpaca)  # Needed for partial coefficients

# Generate foreign trade subset
f_trade <- TradeData0014[TradeData0014$exporter != TradeData0014$importer,]

# classify FEs for components to be absorbed (finding variable interactions)
f_trade$exp_year <- interaction(f_trade$expcode, f_trade$year)
f_trade$imp_year <- interaction(f_trade$impcode, f_trade$year)
f_trade$pair     <- interaction(f_trade$impcode, f_trade$expcode)

# Fit gravity model using PPML with exporter-time, importer-time, and exporter-importer FEs
partials <- feglm(
  formula = trade ~ eu_enlargement + other_fta | exp_year + imp_year + pair,
  data    = f_trade,
  family  = poisson()
)$coefficient  # We just need the coefficients for computation

print(round(partials, 3))
#> eu_enlargement      other_fta 
#>          0.224         -0.037

In addition to estimating the effects of EU enlargements on new EU pairs, this example also controls for any other FTAs signed during the period. Each of these variables is coded as a dummy variable that becomes 1 when the agreement goes into effect for a given pair. The estimated coefficient for is 0.224, implying that the expansion of the EU had an average partial effect of \(e^{0.224} − 1 = 25.1\%\) on trade between new EU members and existing members. With clustered standard errors, this estimate is statistically significant at the \(p < .01\) significance level.


Before proceeding, some further pre-processing is needed. Specifically, we need to supply the function with the partial effects of joining the EU estimated above for each of the appropriate pairs. To do this, we create a new variable whose value is either the eu_enlargement partial computed above or 0. We will use the year 2000 as the baseline for the general equilibrium counterfactual, so we assign these partial effects in the year 2000.

For Stata users, the Stata equivalent of this next step is:

sort exporter importer year
by exporter importer: gen new_eu_pair = (eu_enlargement[_N]-eu_enlargement[1])                   
by exporter importer: gen eu_effect = _b[eu_enlargement] * new_eu_pair

In R, we do the following:

# Sort matrix to make it easier to find imp/exp pairs
t_trade <- TradeData0014[order(
  TradeData0014$exporter,
  TradeData0014$importer,
  TradeData0014$year),
]

t_trade$new_eu_pair <- NA
t_trade$eu_effect   <- NA   # this creates a new column that will contain partial effect of EU membership for new EU pairs
i <- 1
# Effect of EU entrance on country based on partial, if entry happened
invisible(by(t_trade, list(t_trade$expcode, t_trade$impcode), function(row) {
  # Was a new EU pair created within time span?
  t_trade[i:(i+nrow(row)-1), "new_eu_pair"] <<- diff(row$eu_enlargement, lag=nrow(row)-1)
  i <<- i + nrow(row)
}))

# If added to EU, give it the computed partial eu_enlargement coefficient (0.224) as the effect
t_trade$eu_effect = t_trade$new_eu_pair * partials[1]

To finalize the data for the counterfactual, we will use the year 2000 as the baseline year. The data we will feed to the ‘ge_gravity’ command looks like this:

# Data to be finally fed to the function (we base the counterfactual on the year 2000.)
ge_baseline_data <- t_trade[t_trade$year == 2000,]   # In example, 1892 Entries, 5676 removed

# head(data) # First 10 rows
ge_baseline_data[sample(1:nrow(ge_baseline_data), 10),]  # 10 random rows
#>      exporter importer expcode impcode year        trade eu_enlargement
#> 6749      SVK      FRA      39      16 2000 3.525703e+02              0
#> 2361      EST      HRV      14      19 2000 4.696484e-01              0
#> 4373      JPN      RUS      25      38 2000 7.188399e+02              0
#> 2877      GBR      FRA      17      16 2000 3.085328e+04              1
#> 2801      FRA      SWE      16      41 2000 5.576677e+03              1
#> 4781      LUX      CHN      28       8 2000 1.050690e+02              0
#> 7553      TWN      SWE      43      41 2000 6.227414e+02              0
#> 2205      ESP      ITA      13      24 2000 1.172864e+04              1
#> 7165      SWE      NLD      41      32 2000 3.927551e+03              1
#> 5913      POL      LTU      34      27 2000 3.654052e+02              0
#>      other_fta FTA new_eu_pair eu_effect
#> 6749         1   1           1  0.224249
#> 2361         0   0           1  0.224249
#> 4373         0   0           0  0.000000
#> 2877         0   1           0  0.000000
#> 2801         0   1           0  0.000000
#> 4781         0   0           0  0.000000
#> 7553         0   0           0  0.000000
#> 2205         0   1           0  0.000000
#> 7165         0   1           0  0.000000
#> 5913         1   1           1  0.224249

Now that we have the data we need, we can run the ‘ge_gravity’ function as follows:

ge_results <- ge_gravity(
  exp_id = ge_baseline_data$expcode,    # Origin country associated with each observation
  imp_id = ge_baseline_data$impcode,    # Destination country associated with each observation
  flows  = ge_baseline_data$trade,      # Observed trade flows in the data for the year being used as the baseline
  beta   = ge_baseline_data$eu_effect,  # “Partial” change in trade, obtained as coefficient from gravity estimation
  theta  = 4,               # Trade elasticity
  mult   = FALSE,           # Assume trade balance is an additive component of national expenditure
  data   = ge_baseline_data
)
ge_results[sample(1:nrow(ge_results), 10),c(1:2,5:6,12:16)] # 10 random rows
#>      exporter importer year      trade  new_trade   welfare real_wage  nom_wage
#> 1913      DEU      SVK 2000 2723.95149 3269.19970 1.0007249 1.0007204 1.0002367
#> 3685      IDN      TUR 2000  206.00501  205.85536 0.9999865 0.9999931 0.9998933
#> 2501      FIN      CZE 2000  217.43389  264.63294 1.0004615 1.0004125 1.0008138
#> 4709      LTU      POL 2000  169.35396  207.88813 1.0065244 1.0064727 0.9993151
#> 3053      GRC      FRA 2000  271.52443  271.85529 1.0002792 1.0002622 0.9998333
#> 949       CAN      GRC 2000  207.26002  206.92770 0.9999912 0.9999941 0.9999349
#> 4301      JPN      HUN 2000  890.71506  862.04180 0.9999971 0.9999982 0.9999063
#> 2973      GBR      SVN 2000  283.02320  333.17404 1.0001393 1.0001392 1.0001324
#> 5749      NOR      MEX 2000   96.39469   96.31901 1.0002589 1.0002422 1.0001253
#> 6025      PRT      DEU 2000 3186.03737 3182.00629 1.0000813 1.0000725 0.9998933
#>      price_index
#> 1913   0.9995167
#> 3685   0.9999002
#> 2501   1.0004012
#> 4709   0.9928884
#> 3053   0.9995712
#> 949    0.9999408
#> 4301   0.9999082
#> 2973   0.9999932
#> 5749   0.9998831
#> 6025   0.9998208

This assumes a standard trade elasticity value of \(\theta = 4\). The input for \(\beta\) is given by the variable we created called eu_effect, which is equal to 0.224 for new EU pairs formed during the period and equal to 0 otherwise. Because of the small size of the sample, it solves almost instantly. Sample results for the first 10 rows in the data are shown above for the counterfactual trade level and the associated changes in welfare, real wages, nomimal wages, and the local price index. Click on the right arrow to scroll to the right if they are not all displayed above. For the latter four variables, the number shown is the result computed for the exporting country. Unsurprisingly, the new EU members (Bulgaria, Croatia, Czech Republic, Estonia, Hungary, Latvia, Lithuania, Malta, Poland, Romania, Slovakia, and Slovenia) realize the largest welfare gains from their joining the EU, with existing EU countries also gaining. All countries not included in the agreement experience small losses due to trade diversion, with the largest losses accruing to Russia.

We can also change how trade imbalances enter the model. The default (exibited above with mult = FALSE) is to assume that they enter expenditure additively (i.e., \(E_j = Y_j + D_j\)), but one can also change the model so that expenditure is instead a fixed multiple of income (i.e., let \(E_j = \delta_j Y_j\).) by setting mult = TRUE. While using multiplicative imbalances instead of additive balances changes the results slightly, they are still qualitatively very similar.

An important point about the above exercises is that the initial partial effect is estimated with some uncertainty. The GE results that were calculated may paint a misleading picture because they do not take this uncertainty into account. For this reason, it is considered good practice to use a bootstrap method to construct confidence intervals for the GE calculations. This type of procedure can be made easier using ge_gravity function:

library(data.table)  # needed below for bootstrap procedure

# Helper function for shuffling the data with replacement.
# This allows us to shuffle by pair rather than treating each observation as independent.
get_bootdata <- function(data=list(),id) {
  uniq_id <- levels(factor(data[,id]))
  draw    <- sort(sample(uniq_id,replace=TRUE))
  draw  <- data.table(draw)[,.N,by=draw]  # count duplicate pairs in bootstrap sample
  colnames(draw)[1] <- id
  boot_data  <- merge(data,draw,"pair",all.x=FALSE,all.y=FALSE)
  boot_index <- rep(row.names(boot_data), boot_data$N)  # replicates pairs drawn multiple times after merge
  boot_data  <- boot_data[matrix(boot_index), ]
  boot_data$rep <- (as.numeric(rownames(boot_data)) %% 1)*10+1
  return(data.frame(boot_data))
}

# set up for pair bootstrap
set.seed(12345)
bootreps      <- 20
TradeData0014[,"pair"] <- interaction(TradeData0014$expcode,TradeData0014$impcode) # This is the ID we will use for resampling.

# Initialize matrices for saving results
x             <- TradeData0014[,c("eu_enlargement","other_fta")]
save_partials <- matrix(nrow = bootreps, ncol = ncol(x), dimnames = list(1:bootreps,colnames(x)))
GE_effects    <- ge_results[,11:15]
save_GE       <- matrix(nrow = bootreps, ncol = ncol(GE_effects), dimnames = list(1:bootreps,colnames(GE_effects)))

# generate bootstrapped gravity estimates using alpaca's feglm function (20 boot reps)
library(alpaca)
for (b in 1:bootreps) {
    
    # This step shuffles the data using the get_bootdata() function defined above
    boot_data <- get_bootdata(TradeData0014,"pair")
    
    # These next few steps are exactly the same as the ones we used above to estimate the partial effects
    
    # Generate foreign trade subset
    f_trade <- boot_data[boot_data$exporter != boot_data$importer,]

    # Normalize trade data to unit interval
    f_trade$trade <- f_trade$trade / max(f_trade$trade)

    # classify FEs to be absorbed
    f_trade$exp_year <- interaction(f_trade$expcode, f_trade$year)
    f_trade$imp_year <- interaction(f_trade$impcode, f_trade$year)
    f_trade$pair     <- interaction(f_trade$impcode, f_trade$expcode)
    
    # Estimate and save partial effects
    save_partials[b,] <- feglm(
    formula = trade ~ eu_enlargement + other_fta | exp_year + imp_year + pair,
    data    = f_trade,
    family  = poisson()
    )$coefficient  # We just need the coefficients for computation
}


# Obtain bootstrapped GE results based on bootstrapped partial effects
bootstrap_GE_results <- ge_baseline_data
for (b in 1:bootreps) {
  
  # set up baseline data using the estimated partial effect from bootstrap b
  boot_ge_data <- ge_baseline_data
  boot_ge_data$eu_effect <- save_partials[b,1] * boot_ge_data$new_eu_pair 
  
  # run GE_gravity
  temp <- ge_gravity(
  exp_id = boot_ge_data$expcode,    # Origin country associated with each observation
  imp_id = boot_ge_data$impcode,    # Destination country associated with each observation
  flows  = boot_ge_data$trade,      # Observed trade flows in the data for the year being used as the baseline
  beta   = boot_ge_data$eu_effect,  # “Partial” change in trade, obtained as coefficient from gravity estimation
  theta  = 4,               # Trade elasticity
  mult   = FALSE,           # Assume trade balance is an additive component of national expenditure
  data   = boot_ge_data
  )
  
  # store results
  bootstrap_GE_results[,paste0("welf",b)] <- temp[,"welfare"]
  bootstrap_GE_results[,paste0("trade",b)] <- temp[,"new_trade"]
}
# get bootstrapped means, SDs, and 95% CIs for partial effects
colMeans(save_partials)
#> eu_enlargement      other_fta 
#>     0.24045195    -0.03465284
apply(save_partials, 2, sd)
#> eu_enlargement      other_fta 
#>     0.07753165     0.05578904
apply(save_partials, 2, function(x) quantile(x, probs = .975))
#> eu_enlargement      other_fta 
#>     0.34988358     0.05572457
apply(save_partials, 2, function(x) quantile(x, probs = .025))
#> eu_enlargement      other_fta 
#>     0.09867022    -0.11837670
# Get 95% CIs for GE effects
temp <- bootstrap_GE_results[,12:51]
temp <- temp[,order(colnames(temp))]

bootstrap_GE_results[,"lb_welf"] <- apply(temp[,21:40], 1, function(x) quantile(x, probs = .025))
bootstrap_GE_results[,"ub_welf"] <- apply(temp[,21:40], 1, function(x) quantile(x, probs = .975))
bootstrap_GE_results[,"lb_trade"] <- apply(temp[,1:20], 1, function(x) quantile(x, probs = .025))
bootstrap_GE_results[,"ub_trade"] <- apply(temp[,1:20], 1, function(x) quantile(x, probs = .975))

disp_cols <- c("exporter","importer","year","trade","lb_welf","ub_welf","lb_trade","ub_trade")
bootstrap_GE_results[sample(1:nrow(ge_results), 10),disp_cols] # 10 random rows; GE welfare effects are for *exporter*
#>      exporter importer year        trade   lb_welf   ub_welf     lb_trade
#> 1149      CHE      ITA 2000 7.749205e+03 0.9999020 0.9999746 7.752873e+03
#> 4017      IRL      ROW 2000 1.639069e+04 1.0000977 1.0003829 1.635859e+04
#> 1557      CYP      RUS 2000 1.165478e+01 1.0035637 1.0141773 1.174513e+01
#> 3605      IDN      IND 2000 1.156623e+03 0.9999779 0.9999943 1.156626e+03
#> 5197      MEX      ITA 2000 3.174025e+02 0.9999928 0.9999981 3.175410e+02
#> 5449      MLT      TWN 2000 9.318681e-01 1.0063494 1.0255347 9.040972e-01
#> 1093      CHE      CZE 2000 3.707880e+02 0.9999020 0.9999746 3.564777e+02
#> 497       BEL      ROW 2000 2.142915e+04 1.0001526 1.0006022 2.138667e+04
#> 7581      USA      BGR 2000 1.377859e+02 0.9999968 0.9999992 1.225898e+02
#> 6737      SVK      ESP 2000 7.114036e+01 1.0038431 1.0153599 7.871396e+01
#>          ub_trade
#> 1149 7.761856e+03
#> 4017 1.638199e+04
#> 1557 1.193969e+01
#> 3605 1.156631e+03
#> 5197 3.178676e+02
#> 5449 9.254744e-01
#> 1093 3.670091e+02
#> 497  2.141733e+04
#> 7581 1.334064e+02
#> 6737 1.012978e+02

Note again that the displayed bounds on welfare estimates refer to welfare changes for the exporting country.