Program Execution

In example.Rmd, we obtained the following preprocessed data:

library(alpaca)  # Needed for partial coefficients

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

# 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 generalized linear model based on specifications
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

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

t_trade$eu_effect <- NA      # this creates a new column with the 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), "eu_effect"] <<- diff(row$eu_enlargement, lag=nrow(row)-1)
  i <<- i + nrow(row)
}))
# If added to EU, give it the computed partial eu_enlargement coefficient as the effect
t_trade$eu_effect = t_trade$eu_effect * partials[1]

# Data to be finally fed to the function
data <- t_trade[t_trade$year == 2000,]   # In example, 1892 Entries, 5676 removed

head(data)
#>    exporter importer expcode impcode year        trade eu_enlargement other_fta
#> 1       AUS      AUS       1       1 2000 704777.47410              0         1
#> 5       AUS      AUT       1       2 2000     61.95446              0         0
#> 9       AUS      BEL       1       3 2000    345.46746              0         0
#> 13      AUS      BGR       1       4 2000      5.71090              0         0
#> 17      AUS      BRA       1       5 2000    557.74257              0         0
#> 21      AUS      CAN       1       6 2000   1583.67895              0         0
#>    FTA eu_effect
#> 1    1         0
#> 5    0         0
#> 9    0         0
#> 13   0         0
#> 17   0         0
#> 21   0         0

Then, we ran the ge_gravity function:

head(ge_gravity(
  exp_id = data$expcode,    # Origin country associated with each observation
  imp_id = data$impcode,    # Destination country associated with each observation
  flows  = data$trade,      # Observed trade flows in the data for the year being used as the baseline
  beta   = 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   = data
), 10)
#>    exporter importer expcode impcode year        trade eu_enlargement other_fta
#> 1       AUS      AUS       1       1 2000 704777.47410              0         1
#> 5       AUS      AUT       1       2 2000     61.95446              0         0
#> 9       AUS      BEL       1       3 2000    345.46746              0         0
#> 13      AUS      BGR       1       4 2000      5.71090              0         0
#> 17      AUS      BRA       1       5 2000    557.74257              0         0
#> 21      AUS      CAN       1       6 2000   1583.67895              0         0
#> 25      AUS      CHE       1       7 2000    291.68289              0         0
#> 29      AUS      CHN       1       8 2000   4783.37704              0         0
#> 33      AUS      CYP       1       9 2000     22.48929              0         0
#> 37      AUS      CZE       1      10 2000     72.31095              0         0
#>    FTA eu_effect    new_trade   welfare real_wage  nom_wage price_index
#> 1    1         0 7.047388e+05 0.9999938 0.9999945 0.9999236   0.9999292
#> 5    0         0 6.200899e+01 0.9999938 0.9999945 0.9999236   0.9999292
#> 9    0         0 3.454304e+02 0.9999938 0.9999945 0.9999236   0.9999292
#> 13   0         0 5.302484e+00 0.9999938 0.9999945 0.9999236   0.9999292
#> 17   0         0 5.576790e+02 0.9999938 0.9999945 0.9999236   0.9999292
#> 21   0         0 1.583680e+03 0.9999938 0.9999945 0.9999236   0.9999292
#> 25   0         0 2.917153e+02 0.9999938 0.9999945 0.9999236   0.9999292
#> 29   0         0 4.782346e+03 0.9999938 0.9999945 0.9999236   0.9999292
#> 33   0         0 2.126117e+01 0.9999938 0.9999945 0.9999236   0.9999292
#> 37   0         0 7.057916e+01 0.9999938 0.9999945 0.9999236   0.9999292

Instead of calling the function, let’s assume that we have the variables set as following:

exp_id <- data$expcode
imp_id <- data$impcode
flows  <- data$trade
beta   <- data$eu_effect
theta  <- 4
mult   <- FALSE

In the following, we are going to trace and test the algorithm as if it were being called.

As an assumption: - \(i\) indices are defined for each origin/exporter, and matrices enumerable by them are Nx1 matrices. Sums for all \(i\) are generally defined by colSums - \(j\) indices are defined for each destination/importer, and matrices enumerable by them are 1xN matrices. Sums for all \(j\) are generally defined by rowSums - Column- and Row-wise summations will be done explicitly, and R’s vector math operations will not be assumed to facilitate it.

To be safe and explanatory, we will also define a few functions: - Typesafe function that can verify that our initial dimensions hold and that no \(\texttt{NA}\) values are introduced. - Sanity function that will make sure that a vector/matrix does not change cardinality illogically. - to show only a tiny subset of data without much code.

printHead <- function(Vec, rows = 6, cols = 6) {
  print(Vec[1:min(rows, nrow(Vec)), 1:min(cols, ncol(Vec))])
}

ts <- function(Vec, Val, line = "?") {
  if (dim(Vec)[1] != dim(Val)[1] && dim(Vec)[2] != dim(Val)[2]) {
    warning(paste(" > Assigned vector has improper dimensions on line", line, "\n"))
    message("Assigning value: \n")
    printHead(Val)
    message("To Value: \n")
    printHead(Vec)
    if (readline() == "q") return()
  }
  if (anyNA(Val)) {
    warning(paste(" > Assigned vector has NAs on line", line, "\n"))
    printHead(Val)
    if (readline() == "q") return()
  }
  return(Val)
}

sanity <- function(name, Vec, dstr) {
  message(" > Sanity Check: ")
  for (i in 1:length(Vec))
    message("  - dim(", name[i], ") = ",
      dim(Vec[[i]])[1], " x ", dim(Vec[[i]])[2],
      " (defined for ", dstr[i], ")"
    )
}

Let us first set up the set of international trade flows matrix, \(X_{ij}\) (w/ \(i\) exporting to \(j\)). This is just the set of flows arranged in an exporter (rows) by importer (columns) fashion.

X   <- flows
n   <- sqrt(length(X))        # Length of row or column of trade matrix
X   <- t(matrix(flows, n, n)) # Square the matrix
printHead(X)
#>              [,1]         [,2]        [,3]        [,4]         [,5]
#> [1,] 7.047775e+05     61.95446    345.4675     5.71090 5.577426e+02
#> [2,] 3.658264e+02 261390.90409   1224.3476   144.50709 2.635246e+02
#> [3,] 4.753819e+02   1516.67071 343253.2434    68.76933 4.872147e+02
#> [4,] 7.448854e-01     17.67560    111.4472 25137.17943 1.040889e+00
#> [5,] 4.858000e+02    221.86748   1308.2011    47.55200 1.086039e+06
#> [6,] 1.437038e+03    659.92572   1136.6122     7.58329 8.533425e+02
#>              [,6]
#> [1,] 1.583679e+03
#> [2,] 8.459328e+02
#> [3,] 6.059549e+02
#> [4,] 1.269456e+01
#> [5,] 8.835362e+02
#> [6,] 1.066313e+06

Then, set \({\texttt B} \ (= e^{\beta})\) to be the matrix of partial effects. Notice that the diagonal must be set to 1 (i.e., \(\beta=0\)).

B <- beta
dim(B)  <- c(n, n) # Format B to have K.n columns
diag(B) <- 0       # Set diagonal to 0 (this is required and is corrected if found)
B <- exp(B)
printHead(B)
#>      [,1]     [,2]     [,3]     [,4] [,5] [,6]
#> [1,]    1 1.000000 1.000000 1.000000    1    1
#> [2,]    1 1.000000 1.000000 1.251383    1    1
#> [3,]    1 1.000000 1.000000 1.251383    1    1
#> [4,]    1 1.251383 1.251383 1.000000    1    1
#> [5,]    1 1.000000 1.000000 1.000000    1    1
#> [6,]    1 1.000000 1.000000 1.000000    1    1

Now, we can set up some more variables:

  • Let \(E_j\) be the Total National Expendatures for country \(j\) such that \(E_j \equiv \sum_i X_{ij}\).

  • Let \(Y_j\) be the Total Labor Income for country \(i\) such that \(Y_i \equiv w_iL_j = \sum_j X_{ij}\).

  • Let \(D_j\) be the National Trade Deficit for country \(j\) such that \(D_j \equiv E_j - Yj\).

# Set up Y, E, D vectors; calculate trade balances
E <- matrix(colSums(X), n, 1) # Total National Expendatures; Value of import for all origin
Y <- matrix(rowSums(X), n, 1) # Total Labor Income; Value of exports for all destinations;
D <- E - Y                    # D: National trade deficit / surplus

sanity(c("E","Y","D"), list(E, Y, D), c("j","j","j"))
#>  > Sanity Check:
#>   - dim(E) = 44 x 1 (defined for j)
#>   - dim(Y) = 44 x 1 (defined for j)
#>   - dim(D) = 44 x 1 (defined for j)

Then we set up the \(\pi_{ij}\) matrix of bilateral trade shares such that \(\pi_{ij} = X_{ij}/E_{j}\):

# set up pi_ij matrix of trade shares; pi_ij = X_ij/E_j
Pi <- X / kronecker(t(E), matrix(1,n,1))  # Bilateral trade share
sanity(c("X"), list(X), c("i and j"))
#>  > Sanity Check:
#>   - dim(X) = 44 x 44 (defined for i and j)

Now, we are almost done. In this model, we want to build up:

  • The change in price levels in each country \(\hat{P_j}\) for all exporters.

  • The change in welfare \(\hat{W}_{j}\) for all exporters.

  • The general equilibrium trade impact \(\hat{X}_{ij}\) between all importers and exporters.

The iterative algorithm provided will build these up iteratively, starting them off as 1-column matrices.

w_hat <- P_hat <- matrix(1, n, 1)   # Containers for running w_hat and P_hat
X_new <- X                          # Container for updated X
sanity(c("w_hat", "P_hat"), list(w_hat, P_hat), c("i", "i"))
#>  > Sanity Check:
#>   - dim(w_hat) = 44 x 1 (defined for i)
#>   - dim(P_hat) = 44 x 1 (defined for i)

Loop Processes

Step 1: Update \(\hat{w}_i\) for all origins using formula:

\[\widehat{w}_{i} =\left[Y_{i}^{-1}\sum_{j}\frac{\pi_{ij}\cdot e^{\beta\times FTA_{ij}}}{\widehat{P}_{j}^{-\theta}}\cdot E_{j}^{\prime}\right]^{\frac{1}{1+\theta}}\quad\forall i.\]

eqn_base <- ((Pi * B) %*% (E / P_hat)) / Y
w_hat    <- ts(w_hat, eqn_base^(1/(1+theta)))
Step 2: Normalize so total world output stays the same:

\(\sum_{i}Y_{i}\widehat{w}_{i}=\sum_{i}Y_{i}\).

w_hat <- ts(w_hat, w_hat * (sum(Y) / sum(Y*w_hat)))
Step 3: Update

\(\widehat{P}_{j}^{-\theta}=\left[\sum_{k}\pi_{kj}\widehat{w}_{k}^{-\theta}e^{b\times FTA_{kj}}\right] \forall \ j\).

P_hat <- ts(P_hat, (t(Pi) * t(B)) %*% (w_hat^(-theta)))
Step 4: Update

\(E_{j}^{\prime}=Y_{j}\widehat{w}_{j}+D_{j} \ \forall \ j\).

if (mult) {
  E = ts(E, (Y + D) * w_hat)
} else {
  # default is to have additive trade imbalances
  E = ts(E, Y * w_hat + D)
}
Calculate new trade shares (to verify convergence)
p1 <- (Pi * B)
p2 <- kronecker((w_hat^(-theta)), matrix(1,1,n))
p3 <- kronecker(t(P_hat), matrix(1,n,1))

Pi_new <- ts(Pi, p1 * p2 / p3)

X_new <- ts(X, Pi_new * kronecker(t(E), matrix(1,n,1)))

From there, we just need a way to check convergence to a steady-state, so let’s put it all together:

# Initialize w_i_hat = P_j_hat = 1
w_hat <- P_hat <- matrix(1, n, 1)    # Wi = Ei/Pi

# While Loop Initializations
X_new     <- X         # Container for updated X
crit      <- 1         # Convergence testing value
curr_iter <- 0         # Current number of iterations
max_iter  <- 1000000   # Maximum number of iterations
tol       <- .00000001 # Threshold before sufficient convergence

# B = i x j
# D = 1 x j
# E = 1 x j
# w_hat = P_hat = i x 1
# Y = E = D = i x 1

repeat { # Event Loop (using repeat to simulate do-while)

  X_last_step <- X_new

  #### Step 1: Update w_hat_i for all origins:
  eqn_base <- ((Pi * B) %*% (E / P_hat)) / Y
  w_hat    <- eqn_base^(1/(1+theta))

  #### Step 2: Normalize so total world output stays the same
  w_hat <- w_hat * (sum(Y) / sum(Y*w_hat))

  #### Step 3: update P_hat_j
  P_hat <- (t(Pi) * t(B)) %*% (w_hat^(-theta))

  #### Step 4: Update $E_j$
  if (mult) {
    E <- (Y + D) * w_hat
  } else {
    E <- Y * w_hat + D  # default is to have additive trade imbalances
  }

  #### Calculate new trade shares (to verify convergence)
  p1 <- (Pi * B)
  p2 <- kronecker((w_hat^(-theta)), matrix(1,1,n))
  p3 <- kronecker(t(P_hat), matrix(1,n,1))
  Pi_new <- p1 * p2 / p3

  X_new <- t(Pi_new * kronecker(t(E), matrix(1,n,1)))

  # Compute difference to see if data converged
  crit = max(c(abs(log(X_new) - log(X_last_step))), na.rm = TRUE)
  curr_iter <- curr_iter + 1

  if(crit <= tol || curr_iter >= max_iter)
    break
}

From here, we can just aggregate statistics, based in part with the formulas:

\[ \begin{alignat}{1} \textbf{GE Welfare Impact}:\quad & \widehat{W}_{i}=\widehat{E}_{i}/\widehat{P}_{i}\\ \\ \textbf{GE Real Wage Impact}:\quad & \widehat{rw}_{ij}=\widehat{w}_{i}/\widehat{P}_{i},\\ \textbf{GE Trade Impact}:\quad & \widehat{X}_{ij}=\frac{\widehat{w}_{i}^{-\theta}e^{\beta\times FTA_{ij}}}{\widehat{P}_{j}^{-\theta}}\cdot\widehat{E}_{j} \end{alignat} \]

# Post welfare effects and new trade values
dim(X_new) <- c(n*n, 1)

# Real wage impact
real_wage  <- w_hat / (P_hat)^(-1/theta)
# real_wage <- (diag(Pi_new) / diag(Pi))^(-1/theta)  # (ACR formula)

# Welfare impact
if (mult) {
  welfare <- real_wage
} else {
  welfare <- ((Y * w_hat) + D) / (Y+D) / (P_hat)^(-1/theta)
}

# Kronecker w/ this creates n dupes per dataset in column to align with X matrix
kron_base <- matrix(1, n, 1)

welfare     <- kronecker(welfare, kron_base)
real_wage   <- kronecker(real_wage, kron_base)
nom_wage    <- kronecker(w_hat, kron_base)

price_index <- kronecker(((P_hat)^(-1/theta)), kron_base)

And then, we can just return them:

# Build and return the final list
data_out <- data

data_out$new_trade   <- X_new
data_out$welfare     <- welfare
data_out$real_wage   <- real_wage
data_out$nom_wage    <- nom_wage
data_out$price_index <- price_index

head(data_out)
#>    exporter importer expcode impcode year        trade eu_enlargement other_fta
#> 1       AUS      AUS       1       1 2000 704777.47410              0         1
#> 5       AUS      AUT       1       2 2000     61.95446              0         0
#> 9       AUS      BEL       1       3 2000    345.46746              0         0
#> 13      AUS      BGR       1       4 2000      5.71090              0         0
#> 17      AUS      BRA       1       5 2000    557.74257              0         0
#> 21      AUS      CAN       1       6 2000   1583.67895              0         0
#>    FTA eu_effect    new_trade   welfare real_wage  nom_wage price_index
#> 1    1         0 7.047388e+05 0.9999938 0.9999945 0.9999236   0.9999292
#> 5    0         0 6.200899e+01 0.9999938 0.9999945 0.9999236   0.9999292
#> 9    0         0 3.454304e+02 0.9999938 0.9999945 0.9999236   0.9999292
#> 13   0         0 5.302484e+00 0.9999938 0.9999945 0.9999236   0.9999292
#> 17   0         0 5.576790e+02 0.9999938 0.9999945 0.9999236   0.9999292
#> 21   0         0 1.583680e+03 0.9999938 0.9999945 0.9999236   0.9999292