{smcl}
{* 10dec2021}{...}
{cmd:help mata mm_ebal()}
{hline}
{title:Title}
{pstd}
{bf:mm_ebal() -- Entropy balancing}
{title:Syntax}
{pstd}
Initialize the balancing problem:
{p 12 27 2}
{it:S} = {cmd:mm_ebal_init(}{it:X1}{cmd:,} {it:w1}{cmd:,} {it:X0}{cmd:,} {it:w0} [{cmd:,} {it:target}{cmd:,} {it:cov}{cmd:,} {it:nc}{cmd:,} {it:dfc}{cmd:,} {it:nostd}]{cmd:)}
{p 8 8 2}
where
{p 12 16 2}
{it:X1} is the data matrix of the treatment group; rows are observations, columns
are variables; {it:X1} and {it:X0} must have the same number of columns
{p 12 16 2}
{it:w1} is a column vector containing base weights of the treatment group; use
{it:w1} = {cmd:1} for unweighted data; if not scalar, {it:w1} must have
the same number of rows as {it:X1}
{p 12 16 2}
{it:X0} is the data matrix of the control group; rows are observations, columns
are variables; {it:X1} and {it:X0} must have the same number of columns
{p 12 16 2}
{it:w0} is a column vector containing base weights of the control group; use
{it:w0} = {cmd:1} for unweighted data; if not scalar, {it:w0} must have
the same number of rows as {it:X0}
{p 12 16 2}
{it:target} specifies the target moments to be balanced; {it:target} is a vector containing
elements equal to {cmd:1} (balance mean), {cmd:2} (balance mean and variance), or
{cmd:3} (balance mean, variance, and skewness); the elements of {it:target} will
be applied to the variables one after the other; if {it:target} is shorter
then the number of variables, the elements will be recycled; for example,
{it:target} = {cmd:2} will balance all means and all variances; the default is
{it:target} = {cmd:1} (balance all means)
{p 12 16 2}
{it:cov}!=0 balances the covariances
(in addition to the moments requested by {it:target})
{p 12 16 2}
{it:nc}!=0 includes the normalization constraint (target sum of weights)
in the optimization problem, rather than rescaling the weights ex
ante; in this case, if perfect balance is not possible, the sum of the
balancing weights is no longer guaranteed to be equal to the size of the
treatment group
{p 12 16 2}
{it:dfc}!=0 applies degrees-of-freedom correction to balancing constraints
for variances and covariances; by default, {cmd:mm_ebal()} uses constraints
that are consistent with computing variances and covariances in the
reweighted control group based on weights that are normalized to sum to the
size of the treatment group (or with variance formulas that ignore
degrees-of-freedom adjustment in the denominator); if {it:dfc}!=0 is
specified, the results are consistent with
weights normalized to the size of the control group; {it:dfc} only
affects variances and covariances that are not collinear with lower
moments
{p 12 16 2}
{it:nostd}!=0 suppresses standardization of the constraint matrix; by default,
{cmd:mm_ebal()} divides the columns of the constraint matrix by the
standard deviations of the corresponding terms in the treatment group (if the
standard deviations exist); this should make the optimization more stable as all
constraints have a similar scaling and the balancing loss is expressed in terms of
standardized differences; specify {it:nostd}!=0 to omit such standardization
{pstd}
Run the balancing algorithm:
{p 12 27 2}
{it:balanced} = {cmd:mm_ebal(}{it:S}{cmd:)}
{p 8 8 2}
where
{it:balanced} will be {cmd:1} if balance is achieved (balancing loss < {it:btol}) and
{cmd:0} else
{pstd}
Retrieve the balancing weights:
{p 12 27 2}
{it:W} = {cmd:mm_ebal_W(}{it:S}{cmd:)}
{p 8 8 2}
where
{it:W} will be a column vector containing the weights that can be used to adjust
the control group to the treatment group; the sum of {it:W} will equal the
size (sum of weights) of the treatment group
{pstd}
Define optimization settings before running {cmd:mm_ebal()}:
{p 8 12 2}
{cmd:mm_ebal_btol(}{it:S}{cmd:,} {it:btol}{cmd:)}{break}set the balancing
tolerance; default is {it:btol} = {cmd:1e-5}; balancing is achieved if the
balancing loss is smaller than {it:btol}
{p 8 12 2}
{cmd:mm_ebal_trace(}{it:S}{cmd:,} {help mf_optimize##i_tracelevel:{it:trace}}{cmd:)}{break}set
what is displayed in the iteration log; default is {it:trace} =
{cmd:"value"}; {it:trace} = {cmd:"none"} suppresses the iteration log;
see {helpb mf_optimize##i_tracelevel:optimize()} for details
{p 8 12 2}
{cmd:mm_ebal_maxiter(}{it:S}{cmd:,} {it:maxiter}{cmd:)}{break}set the maximum
number of iterations; the default is {cmd:c(maxiter)} as set by
{helpb set maxiter} (16,000 by default)
{p 8 12 2}
{cmd:mm_ebal_ptol(}{it:S}{cmd:,} {it:ptol}{cmd:)}{break}set the convergence
tolerance for the parameter vector (lambda coefficients); default is
{it:ptol} = {cmd:1e-6}; convergence is reached if {it:ptol} or {it:vtol} is
satisfied; also see {helpb mf_optimize##i_ptol:optimize()}
{p 8 12 2}
{cmd:mm_ebal_vtol(}{it:S}{cmd:,} {it:vtol}{cmd:)}{break}set the convergence
tolerance for the balancing loss; default is {it:vtol} = {cmd:1e-7};
convergence is reached if {it:ptol} or {it:vtol} is satisfied; also see
{helpb mf_optimize##i_ptol:optimize()}
{p 8 12 2}
{cmd:mm_ebal_difficult(}{it:S}{cmd:,} {it:flag}{cmd:)}{break}set the stepping
algorithm to be used in nonconcave regions; {it:flag}=0 (the default) uses
the standard algorithm; {it:flag}!=0 uses an alternative algorithm; see the
singular H methods in {helpb mf_optimize##i_singularH:optimize()} and
the description of the {cmd:difficult} option in {helpb maximize}
{p 8 12 2}
{cmd:mm_ebal_nowarn(}{it:S}{cmd:,} {it:flag}{cmd:)}{break}set whether
the message "convergence not achieved" is to be displayed if convergence is
not reached within the maximum number of iterations; {it:flag}=0 (the default)
displays the message; {it:flag}!=0 suppresses the message
{p 8 12 2}
{cmd:mm_ebal_Z(}{it:S}{cmd:,} {it:Z}{cmd:)}{break}set the starting value
for the parameter vector (lambda coefficients); the default is
{it:Z} = J(1, {it:c}, 0) where {it:c} is the number of balancing constraints; if
{it:nc}!=0 the default is {it:Z} = (ln(N1/N0), J(1, {it:c}, 0)) where N1
and N0 are the group sizes (sum of weights)
{p 8 8 2}
If applied without 2nd argument, the above functions return the current value of the
setting. For example, type {it:btol} = {cmd:mm_ebal_btol(}{it:S}{cmd:)}
to obtain the current balancing tolerance setting.
{pstd}
Retrieve auxiliary results after running {cmd:mm_ebal()}:
{p 8 12 2}
{it:balanced} = {cmd:mm_ebal_balanced(}{it:S}{cmd:)}{break}{cmd:1} if balance is achieved (balancing loss < {it:btol}), {cmd:0} else
{p 8 12 2}
{it:conv} = {cmd:mm_ebal_conv(}{it:S}{cmd:)}{break}{cmd:1} if
the optimization algorithm converged, {cmd:0} else
{p 8 12 2}
{it:rc} = {cmd:mm_ebal_rc(}{it:S}{cmd:)}{break}
scalar containing the return code issued by {helpb mf_optimize##r_error:optimize()} in case of error
{p 8 12 2}
{it:Z} = {cmd:mm_ebal_Z(}{it:S}{cmd:)}{break}
row vector containing the fitted parameter vector (lambda coefficients)
{p 8 12 2}
{it:g} = {cmd:mm_ebal_g(}{it:S}{cmd:)}{break}row vector containing
the gradient at {it:Z}
{p 8 12 2}
{it:v} = {cmd:mm_ebal_v(}{it:S}{cmd:)}{break}scalar containing the
balancing loss at {it:Z}
{p 8 12 2}
{it:i} = {cmd:mm_ebal_i(}{it:S}{cmd:)}{break}scalar containing the number
of iterations
{p 8 12 2}
{it:N} = {cmd:mm_ebal_N(}{it:S}{cmd:)}{break}scalar containing the
target sum of weights (size of the treatment group)
{p 8 12 2}
{it:C} = {cmd:mm_ebal_C(}{it:S}{cmd:)}{break}constraint matrix (excluding
collinear columns); rows are observations
{p 8 12 2}
{it:CC} = {cmd:mm_ebal_CC(}{it:S}{cmd:)}{break}(non-redundant) collinear
columns from constraint matrix; rows are observations
{p 8 12 2}
{it:Q} = {cmd:mm_ebal_Q(}{it:S}{cmd:)}{break}column vector containing the
(rescaled) base weights of the control group
{title:Description}
{pstd}
{cmd:mm_ebal()} performs entropy balancing proposed by Hainmueller (2012).
Given data from a treatment group and a control group, the goal of
{cmd:mm_ebal()} is to find a vector of weights such that selected moments
(e.g. means and variances) in the reweighted control group match the
corresponding moments in the treatment group (i.e., to find weights that
balance the treatment and control groups with respect to the moments).
{pstd}
The code of {cmd:mm_ebal()} is loosely based on the Stata package
{helpb ebalance} (version 1.5.4, 2015-01-29) by Hainmueller and Xu (2011,
2013) and on R package {cmd:ebal} (version 0.1-6, 2014-01-27) by Hainmueller
(2014). Instead of using a custom algorithm, however, {cmd:mm_ebal()} is
implemented in terms of Mata's {helpb mf_optimize:optimize()}. Results will
be highly accurate as long as a balancing solution exists.
{pstd}
A newer function for entropy balancing with somewhat different
features is available as {helpb mf_mm_ebalance:mm_ebalance()}. A wrapper
that obtains balancing weights in a single
line of code is available as {helpb mf_mm_wbal:mm_wbal()}. A Stata command
based on {helpb mf_mm_ebalance:mm_ebalance()} is available as
{helpb ebalfit} (Jann 2021).
{pstd}
Remark on how {cmd:mm_ebal()} handles collinearity: (1) Terms that are
collinear across both groups will be ignored because the
corresponding balancing constraints are redundant (example: variance of a
binary variable). (2) Terms that are collinear only in the control group
will be ignored during optimization, however, since the corresponding
balancing constraints are not redundant, these terms will be taken into
account when computing the final balancing loss after optimization has
completed (example: empty factor-variable level).
{title:Examples}
{dlgtab:Basic procedure}
{pstd}
The basic procedure is to first define the balancing problem using
{cmd:mm_ebal_init()}, then apply {cmd:mm_ebal()} to find the balancing
weights, and then to retrieve the fitted weights using {cmd:mm_ebal_W()}. Here
is an example:
. {stata sysuse nlsw88, clear}
. {stata drop if missing(wage, union, age, grade, hours, ttl_exp, tenure)}
. {stata generate byte nonunion = 1 - union}
. {stata "mata:"}
: {stata X1 = st_data(., "age grade hours ttl_exp tenure", "union")}
: {stata X0 = st_data(., "age grade hours ttl_exp tenure", "nonunion")}
: {stata S = mm_ebal_init(X1, 1, X0, 1)}
: {stata (void) mm_ebal(S)}
: {stata w = mm_ebal_W(S)}
: {stata end}
{pstd}
We can now confirm that the weights do balance the data. In the original sample,
the group means and the mean differences are:
. {stata "mata: mean(X1)', mean(X0)', mean(X1)' - mean(X0)'"}
{pstd}
Applying the balancing weights to the control group removes the mean differences
(apart from roundoff error):
. {stata "mata: mean(X1)', mean(X0, w)', mean(X1)' - mean(X0, w)'"}
{dlgtab:Balancing higher order moments}
{pstd}
By default, {cmd:mm_ebal()} computes weights that balance the means of the
provided variables. Specify {it:target} and {it:cov} to
balance additional moments. In the following example, {it:tar} = {cmd:2}
and {it:cov} = {cmd:1} is used to balance all means, variances, and
covariances:
. {stata "mata:"}
: {stata S = mm_ebal_init(X1, 1, X0, 1, 2, 1)}
: {stata (void) mm_ebal(S)}
: {stata w = mm_ebal_W(S)}
: {stata mean(X1) - mean(X0, w)}
: {stata variance(X1) - variance(X0, w)}
: {stata end}
{dlgtab:Estimating an ATT based on the balanced sample}
{pstd}
We can now use the balancing weights to estimate an average treatment
effect on the treated (ATT). Using unbalanced data, the estimate of the effect
of union status on wages is 1.46 USD:
. {stata regress wage union}
{pstd}
Adjusting covariate imbalance using the weights computed by {cmd:mm_ebal()}
we get an ATT of .82 USD:
. {stata generate double w = 1}
. {stata `"mata: st_store(., "w", "nonunion", w)"'}
. {stata regress wage union [pweight = w]}
{dlgtab:Adjusting a sample to known population moments}
{pstd}
Assume that the mean age in the relevant population is known to be 35 with a standard deviation of 2.5
and that the mean grade is 12 with a standard deviation of 1.9. The population
size is 1.2 million. You could adjust the sample to these values as follows:
. {stata sysuse nlsw88, clear}
. {stata drop if missing(age, grade)}
. {stata "mata:"}
: {stata age = st_data(., "age")}
: {stata grade = st_data(., "grade")}
: {stata N = 1.2e6; dfc = N / (N - 1)}
: {stata pop = (35, 2.5^2, 12, 1.9^2)}
: {stata "sample = (age, (age:-35):^2 * dfc, grade, (grade:-12):^2 * dfc)"}
: {stata S = mm_ebal_init(pop, N, sample, 1)}
: {stata (void) mm_ebal(S)}
: {stata w = mm_ebal_W(S)}
: {stata sum(w)}
: {stata mean(age, w), sqrt(variance(age, w))}
: {stata mean(grade, w), sqrt(variance(grade, w))}
: {stata end}
{dlgtab:Orthogonalization of variables}
{pstd}
Entropy balancing can also be used, for example, to find weights that
orthogonalize the data (such that correlations between variables are
zero). Here is an example:
. {stata sysuse nlsw88, clear}
. {stata drop if missing(age, hours, tenure)}
. {stata "mata:"}
: {stata X = st_data(., "age hours tenure")}
: {stata "C = X :- mean(X)"}
: {stata "C = C, C[,1]:*C[,2], C[,1]:*C[,3], C[,2]:*C[,3]"}
: {stata S = mm_ebal_init(J(1, cols(C), 0), rows(C), C, 1)}
: {stata (void) mm_ebal(S)}
: {stata w = mm_ebal_W(S)}
: {stata mean(X)} // results from original data
: {stata variance(X)}
: {stata mean(X, w)} // reweighted results
: {stata variance(X, w)}
: {stata end}
{pstd}
The trick is to first center the data and then fit weights such that
the means of the pairwise products between the centered variables become zero.
{pstd}
In the above example, the means of the variables are preserved, but the
variances change. To preserve the variances in addition to the means,
standardize the data (excluding degrees of freedom correction) and add the
squares of the standardized variables to the optimization problem:
. {stata "mata:"}
: {stata N = rows(X)}
: {stata "C = (X:-mean(X)) :/ sqrt(diagonal(variance(X))*(N-1)/N)'"}
: {stata "C = C, C:^2:-1, C[,1]:*C[,2], C[,1]:*C[,3], C[,2]:*C[,3]"}
: {stata S = mm_ebal_init(J(1, cols(C), 0), rows(C), C, 1)}
: {stata (void) mm_ebal(S)}
: {stata w = mm_ebal_W(S)}
: {stata mean(X)} // results from original data
: {stata variance(X)}
: {stata mean(X, w)} // reweighted results
: {stata variance(X, w)}
: {stata end}
{title:Source code}
{pstd}
{help moremata11_source##mm_ebal:mm_ebal.mata}
{title:References}
{phang}
Hainmueller, J. (2012). Entropy Balancing for Causal Effects: A
Multivariate Reweighting Method to Produce Balanced Samples in
Observational Studies. Political Analysis
20(1): 25-46. DOI: {browse "http://doi.org/10.1093/pan/mpr025":10.1093/pan/mpr025}
{p_end}
{phang}
Hainmueller, J. (2014). ebal: Entropy reweighting to create balanced
samples. {browse "http://CRAN.R-project.org/package=ebal"}.
{p_end}
{phang}
Hainmueller, J., Y. Xu (2011). EBALANCE: Stata module to perform Entropy
reweighting to create balanced samples. Statistical Software Components
S457326, Boston College Department of Economics. {browse "http://ideas.repec.org/c/boc/bocode/s457326.html"}.
{p_end}
{phang}
Hainmueller, J., Y. Xu (2013). ebalance: A Stata Package for Entropy Balancing.
Journal of Statistical Software
54(7):1-18. DOI: {browse "http://doi.org/10.18637/jss.v054.i07":10.18637/jss.v054.i07}
{p_end}
{phang}
Jann, B. (2021). ebalfit: Stata module to perform entropy balancing. Available
from http://github.com/benjann/ebalfit/.
{p_end}
{title:Author}
{pstd} Ben Jann, University of Bern, ben.jann@unibe.ch
{title:Also see}
{psee}
Online: help for
{helpb mf_optimize:[M-5] optimize()},
{helpb moremata}, {helpb mf_mm_ebalance:mm_ebalance()}, {helpb mf_mm_wbal:mm_wbal()},
{helpb ebalance} (if installed), {helpb ebalfit} (if installed),
{p_end}