| Title: | Identification and Estimation of Child Penalties |
|---|---|
| Description: | Estimators from Leventer (2025), "Identification of Child Penalties" <doi:10.48550/arXiv.2602.07486>: within-gender difference-in-differences, triple differences, the conventional normalized estimator with a bias-bounding exercise, the effect of parenthood on the gender earnings ratio under normalized and proportional triple differences, the accounting of the gender gap, and aggregation across treatment groups, with influence-function standard errors clustered by person. |
| Authors: | Dor Leventer [aut, cre] |
| Maintainer: | Dor Leventer <[email protected]> |
| License: | MIT + file LICENSE |
| Version: | 0.3.0 |
| Built: | 2026-10-23 22:30:39 UTC |
| Source: | https://github.com/dorleventer/childpen |
Averages the treatment-group-specific estimates from
multiple_treatment_group_analysis across treatment groups, by
event time (Leventer 2025, Section 5). The default weights are the
treatment-group shares in the sample. Standard errors account for persons who
serve as controls for several treatment groups.
aggregate_estimands( results, weights = "sample", methods = c("DID_Female", "DID_Male", "TD", "Conventional", "NTD", "TD_E_LOGS", "TD_PR"), include_pre = FALSE )aggregate_estimands( results, weights = "sample", methods = c("DID_Female", "DID_Male", "TD", "Conventional", "NTD", "TD_E_LOGS", "TD_PR"), include_pre = FALSE )
results |
The data.frame returned by
|
weights |
How to weight treatment groups:
|
methods |
Methods to aggregate (default: all). |
include_pre |
Logical (default |
agg_type = "weighted_avg" is over the
treatment-group estimates . For DID_Female / DID_Male
this is ; for Conventional it is the
conventional aggregate gap , which under NTD is
biased for the gap in normalized effects (Theorem 1); for NTD it is
, the aggregate effect of parenthood on the gender earnings
ratio. agg_type = "ratio_of_avgs" (DID rows only) is
, the ratio of the weighted average ATE to the
weighted average APO; it puts more weight on higher-earning groups, which is
why the paper prefers (Section 5.1).
Standard errors. The influence function of an aggregate (aggregate
estimator section of the appendix on estimation and inference) sums the treatment-group influence functions with the weights; with
weights = "sample" it adds the term for the estimated shares,
,
, where is the share of persons
in group ( with the joint share of
gender and group for the within-gender DID rows) and
the summed share of the aggregated groups. For
ratio_of_avgs the chain rule is applied to the ratio of the two
weighted averages, each with its share term. Standard errors are clustered by
person. The share term is put on the person-age scale with the average number
of ages per person, exact for a balanced panel.
Not every treatment group has an estimate at every event time; each event
time uses the groups present, and n_groups reports how many.
A data.frame with one row per event time, estimand, method and
aggregation type: event_time, control_offset (;
after treatment it is event_time + 1), estimand,
method, agg_type, est, se, n_groups
(treatment groups with positive weight), ci_l, ci_h (est -/+
1.96 se), t, p.
multiple_treatment_group_analysis, bias_bounds
sim <- simulate_data(n_individuals = 2000) res <- multiple_treatment_group_analysis(sim, treatment_groups = 24:26, periods_post = 2, periods_pre = NULL, verbose = FALSE) agg <- aggregate_estimands(res) agg[agg$method %in% c("NTD", "Conventional"), ]sim <- simulate_data(n_individuals = 2000) res <- multiple_treatment_group_analysis(sim, treatment_groups = 24:26, periods_post = 2, periods_pre = NULL, verbose = FALSE) agg <- aggregate_estimands(res) agg[agg$method %in% c("NTD", "Conventional"), ]
The bias-bounding exercise of Leventer (2025, Section 3.3). Under NTD the
conventional estimator (the gender gap in normalized DID) is biased for the
gender gap in normalized effects when parallel
trends fails (Theorem 1). If fathers' normalized effect were
known, the gap would be identified (Proposition 2). This function computes the
bias-corrected gap for each assumed value of , for every
post-treatment cell of results (Proposition 2 applies after
treatment, ); comparing it with the conventional estimate
bounds the bias.
bias_bounds(results, theta_m = c(-0.1, 0.1))bias_bounds(results, theta_m = c(-0.1, 0.1))
results |
The data.frame returned by
|
theta_m |
Numeric vector of assumed fathers' normalized effects
(default |
For a post-treatment cell , with the
NTD rho_effect estimate (the effect of parenthood on the gender
earnings ratio) and
the DID-imputed counterfactual mean of gender
(rows APO, DID_Female / DID_Male), the
bias-corrected gap is (Proposition 2, in the form of the appendix on estimation and inference)
It is linear in , so the values at the ends of an interval
bound it over the whole interval. Standard errors come from the delta method on
the stored person-level influence functions, clustered by person.
A data.frame with one row per cell and value of theta_m:
d, dp, a, event_time, theta_m,
est (the bias-corrected gap), se, ci_l, ci_h.
multiple_treatment_group_analysis
sim <- simulate_data(n_individuals = 2000) res <- multiple_treatment_group_analysis(sim, treatment_groups = 24:26, periods_post = 2, periods_pre = NULL, verbose = FALSE) bb <- bias_bounds(res) # Conventional estimate and the bias-corrected band, d = 25 subset(res, method == "Conventional" & d == 25, c(event_time, est)) subset(bb, d == 25, c(event_time, theta_m, est, se))sim <- simulate_data(n_individuals = 2000) res <- multiple_treatment_group_analysis(sim, treatment_groups = 24:26, periods_post = 2, periods_pre = NULL, verbose = FALSE) bb <- bias_bounds(res) # Conventional estimate and the bias-corrected band, d = 25 subset(res, method == "Conventional" & d == 25, c(event_time, est)) subset(bb, d == 25, c(event_time, theta_m, est, se))
Runs single_treatment_group_analysis over a grid of 2x2
comparisons. For each treatment group (age at first birth) and event
time , the target age is compared with the
closest not-yet-treated group (Leventer 2025, Section 1.3).
Pre-treatment event times are estimated against every control group used
after treatment, because the identifying assumptions must hold pair by pair
(Section 2.3). Results are stacked, and the person-level influence functions
that aggregate_estimands and bias_bounds need are
attached.
multiple_treatment_group_analysis( data, treatment_groups, periods_post, periods_pre = 4, max_control_group = Inf, min_pre_age = 0, pre = 1, Y_name = "Y", age_name = "age", D_name = "D", id_name = "id", female_name = "female", verbose = TRUE )multiple_treatment_group_analysis( data, treatment_groups, periods_post, periods_pre = 4, max_control_group = Inf, min_pre_age = 0, pre = 1, Y_name = "Y", age_name = "age", D_name = "D", id_name = "id", female_name = "female", verbose = TRUE )
data |
A data.frame or data.table in long format, one row per person and
age; see |
treatment_groups |
Integer vector of ages at first birth |
periods_post |
Whole number |
periods_pre |
Whole number |
max_control_group |
Largest age at first birth allowed as a control group
(the paper's |
min_pre_age |
Smallest target age for pre-treatment cells (default 0, no bound). Post-treatment cells are not affected. |
pre |
Whole number |
Y_name, age_name, D_name, id_name, female_name
|
Names of the columns holding earnings, age, age at first birth, the person id and the female indicator. |
verbose |
Logical (default |
A data.frame with one block of rows per estimated cell
(d, dp, a), sorted by d, event_time and
dp; the columns are those of
single_treatment_group_analysis. Attributes used by
aggregate_estimands and bias_bounds:
"if_data" (person-level influence functions per cell), "n_obs"
(rows of the prepared data), "n_ids" (persons), "id_info"
(id, female, D). Attribute "skipped" lists cells
that could not be estimated, with the reason (e.g. an empty
gender x group x age cell). Pass the result to the aggregation functions
unmodified.
single_treatment_group_analysis,
aggregate_estimands, bias_bounds
sim <- simulate_data(n_individuals = 2000) res <- multiple_treatment_group_analysis(sim, treatment_groups = 24:26, periods_post = 1, periods_pre = 2, verbose = FALSE) # The new estimand, by treatment group and event time subset(res, method == "NTD" & estimand == "rho_effect" & event_time >= 0, c(d, event_time, dp, est, se)) # Pre-treatment validation tests of NTD, one row per control group subset(res, method == "Conventional" & event_time < 0, c(d, event_time, control_offset, est, se))sim <- simulate_data(n_individuals = 2000) res <- multiple_treatment_group_analysis(sim, treatment_groups = 24:26, periods_post = 1, periods_pre = 2, verbose = FALSE) # The new estimand, by treatment group and event time subset(res, method == "NTD" & estimand == "rho_effect" & event_time >= 0, c(d, event_time, dp, est, se)) # Pre-treatment validation tests of NTD, one row per control group subset(res, method == "Conventional" & event_time < 0, c(d, event_time, control_offset, est, se))
Generates a balanced panel of annual earnings designed to show the patterns the paper discusses: selection on the timing of first birth that makes parallel trends fail in the paper's direction, effects of parenthood that differ by age at first birth and fade with time since birth, and a gender earnings ratio that falls with age. The true values are attached, so estimates can be compared with them.
simulate_data(n_individuals = 10000, treatment_groups = 24:28, seed = 42)simulate_data(n_individuals = 10000, treatment_groups = 24:28, seed = 42)
n_individuals |
Whole number |
treatment_groups |
Whole numbers |
seed |
Whole number or |
Earnings of person with age at first birth at age are
with for women and:
Career entry and growth (selection on timing):
, with
experience , entry age
and slope .
Later first births enter the labor market later (more schooling) and then
grow faster. Before treatment, distant control groups are not yet working
and grow more slowly, so pre-treatment DID estimates grow in magnitude with
the distance to the control group and are negative for distant control
groups (for the closest ones they are small and can be positive); after
treatment, the later control groups
grow faster, so parallel trends fails with a negative violation for earlier
treatment groups.
Gender gap by age: ,
so the female/male ratio absent children is : 0.96 up to
age 25, then falling.
Effects of parenthood: mothers
(smaller for later
first births, fading with event time ); fathers
. In normalized terms .
Noise: (permanent),
(transitory), independent of
everything else.
Age at first birth: drawn from treatment_groups with
probabilities proportional to a normal density with mean 29 and standard
deviation 4, independently of gender.
Under this design DID-PT and TD-PT fail; TD-E-LOGS holds exactly (the gender terms cancel in log means); NTD-PT and TD-PR hold only approximately, because the gender ratio changes with age between the baseline and the target age. The NTD estimator therefore differs slightly from the true effect on the gender earnings ratio (by up to about 0.01 in this design); the attached true values show by how much.
A balanced long panel (data.frame), n_individuals x
(max(treatment_groups) - 20) rows sorted by id and age, with columns
id, female (1 = female), age, D (age at first
birth) and Y (earnings, positive). Attribute "truth": the
population values for every treatment group d and age
a >= d (event_time = a - d): APO_f, APO_m
(mean earnings absent children), ATE_f, ATE_m,
theta_f, theta_m, gap_theta, rho_cf
(), rho_obs (), rho_effect
(), share_parenthood. Attribute
"params": the parameter values.
multiple_treatment_group_analysis
sim <- simulate_data(n_individuals = 2000) head(sim) head(attr(sim, "truth"))sim <- simulate_data(n_individuals = 2000) head(sim) head(attr(sim, "truth"))
For one treatment group (age at first birth), control group
and target age , estimates the descriptive estimands of Leventer (2025):
within-gender DID, the gender gaps under TD, the conventional estimator, the
effect of parenthood on the gender earnings ratio (NTD and proportional TD),
and the accounting of the gender gap. Returns 19 rows with standard errors
clustered by person.
single_treatment_group_analysis( data, d, dp, a, pre = 1, Y_name = "Y", age_name = "age", D_name = "D", id_name = "id", female_name = "female" )single_treatment_group_analysis( data, d, dp, a, pre = 1, Y_name = "Y", age_name = "age", D_name = "D", id_name = "id", female_name = "female" )
data |
A data.frame (or data.table) in long format: one row per person
and age, with the person id, age, age at first birth, a female indicator and
earnings. The panel need not be balanced. Each person's female indicator and
age at first birth must be constant; a missing age at first birth means the
person never has a child. Rows with missing earnings are dropped (with a
message). Earnings may be zero, but the ratio rows need positive cell means.
|
d |
Whole number. Treatment group: age at first birth. |
dp |
Whole number. Control group |
a |
Whole number. Target age; event time is |
pre |
Whole number |
Y_name, age_name, D_name, id_name, female_name
|
Names of the columns holding
earnings, age, age at first birth, the person id (clustering unit) and the
female indicator (1 / |
Let be the mean earnings of gender
with age at first birth at age , estimated by the cell mean,
and the baseline age. The additive (DID) and multiplicative
imputations of counterfactual earnings at age are
The rows, with the assumption under which each identifies its target (all also need no anticipation):
| method | estimand | estimator; target |
| DID_Female, DID_Male | APO | ; under DID-PT |
| DID_Female, DID_Male | ATE | ; under DID-PT |
| DID_Female, DID_Male | theta | ATE row / APO row; under DID-PT |
| TD | gap_ATE | ATE row f minus m; under TD-PT |
| Conventional | gap_theta | theta row f minus m; under DID-PT only (under NTD-PT it is biased, Theorem 1) |
| NTD | rho_effect | ; under NTD-PT (Theorem 2) |
| TD_E_LOGS | rho_effect | as NTD with ; under TD-E-LOGS (proposition in the online appendix on proportional DID and TD) |
| TD_PR | gap_theta_pre | , f minus m; , f minus m, under TD-PR (same proposition) |
| NTD, TD_E_LOGS | rho_obs | ; the observed gender earnings ratio |
| NTD, TD_E_LOGS | rho_cf | (NTD) or with (TD_E_LOGS); under NTD-PT (NTD) or TD-E-LOGS (TD_E_LOGS) |
| NTD, TD_E_LOGS | share_parenthood | ; parenthood's share of the observed gender gap (Section 4.2), under the same assumption as rho_cf |
| NTD, TD_E_LOGS | share_other | share_parenthood; the share of other factors
|
Gaps are female minus male. The conventional estimator is the 2x2 version of
the normalized event-study child penalty; under NTD it equals the target gap
times a gender-invariant bias factor (Theorem 1), see bias_bounds
for the paper's bias-bounding exercise. The accounting rows and their
standard errors are an extension of the paper's Section 4.2 accounting
exercise.
Standard errors. Each row is a smooth function of the eight cell
means; its influence function follows by the chain rule from those of the cell
means, with the cell indicator
(the appendix on estimation and inference). The variance is , clustering by person ( person-age rows, persons).
vignette("estimators") writes out every estimator and influence
function.
A data.frame with 19 rows and columns d, dp, a,
event_time (), control_offset (),
estimand, method, est, se, ci_l,
ci_h (est -/+ 1.96 se), t, p (two-sided, normal), and
n_female_treat, n_female_control, n_male_treat,
n_male_control (persons with and ). Attribute
"if_scores": person-level sums of the influence functions, used by
aggregate_estimands and bias_bounds.
multiple_treatment_group_analysis for the grid of
comparisons the paper uses, aggregate_estimands,
bias_bounds, vignette("estimators")
sim <- simulate_data(n_individuals = 2000) # Treatment group d = 25 one year after first birth (a = 26), # closest not-yet-treated control group d' = a + 1 = 27 res <- single_treatment_group_analysis(sim, d = 25, dp = 27, a = 26) res[res$method %in% c("Conventional", "NTD"), c("method", "estimand", "est", "se")] # Pre-treatment validation (placebo) test: a < d - 1 val <- single_treatment_group_analysis(sim, d = 25, dp = 27, a = 22) val[val$method %in% c("Conventional", "NTD") & val$estimand %in% c("gap_theta", "rho_effect"), c("method", "estimand", "est", "se")]sim <- simulate_data(n_individuals = 2000) # Treatment group d = 25 one year after first birth (a = 26), # closest not-yet-treated control group d' = a + 1 = 27 res <- single_treatment_group_analysis(sim, d = 25, dp = 27, a = 26) res[res$method %in% c("Conventional", "NTD"), c("method", "estimand", "est", "se")] # Pre-treatment validation (placebo) test: a < d - 1 val <- single_treatment_group_analysis(sim, d = 25, dp = 27, a = 22) val[val$method %in% c("Conventional", "NTD") & val$estimand %in% c("gap_theta", "rho_effect"), c("method", "estimand", "est", "se")]