Training hours and productivity growth: task F6-conf-04 (simulated data)
Question
What is the causal effect of one more training hour per employee during the programme year on productivity growth in the following year, measured in percentage points (pp) per hour? This is identified only under selection on observables: conditional on the pre-programme skill_index, training hours are as good as randomly assigned, and the effect is linear and homogeneous. As a baseline, I also report the unadjusted association.
Data and exclusions
data/firms.csv (simulated) has 1200 rows (n_input_rows) and 4 columns. There are no missing values, no duplicate rows and no duplicate firm_ids. The full audit is in out/state/data_audit.md.
- Exclusion: 12 firms (
n_excluded_negative_hours) have negativetraining_hours, with a minimum of −7.585. Negative hours are impossible, so I dropped these rows from the main analysis. That leaves 1188 firms (n_analysed). The ledger is inout/exclusions.csv: 1200 − 0 − 0 − 12 = 1188. A sensitivity analysis that keeps the 12 rows is reported below. - Confounding:
skill_indexis measured before the programme, so including it cannot introduce post-treatment bias. It is strongly correlated with training hours (r = 0.678 in the analysed sample,corr_skill_training) and strongly negatively associated with growth. Both issues are listed indata_issues.
Method
- Baseline (M1): OLS of
productivity_growthontraining_hourswith an intercept. - Main model (M2): OLS of
productivity_growthontraining_hoursandskill_index. Under the stated assumption,skill_indexis the only observed confounder, so conditioning on it linearly targets the estimand. No other variables exist. - Standard errors: HC1 heteroskedasticity-robust. 95% CIs use the normal approximation, ±1.96·SE (
se_type). - Fit metric: in-sample residual RMSE.
- Robustness: R1 adds skill²; R2 adds an hours×skill interaction.
- Sensitivity: S1 and S2 re-estimate M1 and M2 on all 1200 rows.
- Randomness: none is used (
random_seed= null). - Outputs: Table 1 is
out/table1.csvand Figure 1 isout/figure1.png. Both are produced byout/analysis.py.
Results
- Unadjusted slope: −0.132 pp/hour (
naive_estimate), 95% CI [−0.160, −0.103] (naive_ci_low,naive_ci_high). Firms that trained more grew less. - Adjusted for
skill_index: the sign reverses. The effect is +0.254 pp per hour (effect_estimate), 95% CI [0.234, 0.273] (effect_ci_low,effect_ci_high). The coefficient onskill_indexis −4.62 pp per index unit (skill_index_coefficient). - Conclusion:
positive_effect(conclusion). - Fit: residual RMSE falls from 3.86 pp in the unadjusted model (
baseline_metric) to 1.98 pp in the adjusted model (model_metric). - Why the sign reverses: firms with higher baseline skill trained more but grew less. Omitting skill therefore biases the slope downward. The omitted-variable identity reproduces the naive slope exactly from the adjusted coefficients (
out/state/checks.md). - Robustness: the effect barely moves when adding skill² (0.2538,
robust_quadratic_skill_effect; p for skill² = 0.57,robust_quadratic_skill_sq_pvalue). An hours×skill interaction is negligible (−0.0003, p = 0.96;robust_interaction_coef,robust_interaction_pvalue). - Sensitivity: keeping the 12 negative-hour rows gives 0.252 (
sens_all_rows_effect_estimate) against −0.135 unadjusted (sens_all_rows_naive_estimate).
Uncertainty
- The main effect has an HC1 SE of 0.0098 pp/hour (
effect_se), so the 95% CI spans about ±0.019 around 0.254 and is far from zero. The unadjusted slope's SE is 0.0146 (naive_se). - These intervals cover sampling uncertainty only, given the model. They do not cover model or identification uncertainty, which is likely much larger: the gap between the naive and adjusted estimates (0.39 pp/hour) is about 40 times the SE.
Limitations and open issues
- What would invalidate the result:
- Unobserved confounding. Anything that drives both training and later growth and is not captured by
skill_indexwould bias the estimate. Examples are management quality, sector shocks, or expected growth, if firms anticipating a slowdown trained more. Adjustment flipped the sign, which shows that the estimate depends heavily on which confounders are controlled for. Another omitted confounder could move it substantially in either direction. - Measurement error in
skill_index. A noisy index would only partly control for confounding. Here the confounding pushes the slope downward, so residual confounding would likely bias the estimate downward too. That direction is a reasoned guess, not tested. - Mis-specification. Linearity and homogeneity are assumptions. Only the simple skill² and interaction checks were run.
- Unobserved confounding. Anything that drives both training and later growth and is not captured by
- What was not checked:
- No formal sensitivity analysis for unobserved confounding (e.g. E-values or Oster bounds).
- No non-parametric adjustment (matching or weighting), and no formal overlap diagnostics beyond the observed spread.
- Nonlinearity in training hours (e.g. diminishing returns) was not tested.
- No out-of-sample evaluation: the RMSEs are in-sample and describe fit, not causal validity.
- Data issue: the 12 negative training-hour values are unexplained. They could be coding errors, or the result of a generating process that does not respect the zero bound. Excluding them changes the effect only slightly (0.254 vs 0.252), and they are concentrated among low-skill firms.
- External validity: the data are simulated. The numbers say nothing about real training programmes.