library(mediation)

Fit a model to estimate the total effect. treat is the treatment and depress2 the outcome. The other variables are covariates.

a <- lm(depress2 ~ treat + econ_hard + sex + age, data=jobs)

Now the models for the mediation test (job_seek is the mediator):

b <- lm(job_seek ~ treat + econ_hard + sex + age, data=jobs)
c <- lm(depress2 ~ treat + job_seek + econ_hard + sex + age, data=jobs)

Estimate the CIs for the various effects by nonparametric bootstrap:

contcont.boot <- mediate(b, c, boot=TRUE, sims=1000, treat="treat", mediator="job_seek")
Running nonparametric bootstrap
summary(contcont.boot)

Causal Mediation Analysis 

Nonparametric Bootstrap Confidence Intervals with the Percentile Method

               Estimate 95% CI Lower 95% CI Upper p-value
ACME            -0.0157      -0.0400         0.01    0.21
ADE             -0.0403      -0.1237         0.04    0.40
Total Effect    -0.0560      -0.1452         0.03    0.24
Prop. Mediated   0.2811      -2.0516         2.53    0.37

Sample Size Used: 899 


Simulations: 1000 

Average direct effect (ADE):

coef(c)["treat"]
     treat 
-0.0402647 

Average causal mediation effect (ACME):

coef(b)["treat"] * coef(c)["job_seek"]
      treat 
-0.01574465 

Total effect:

coef(a)["treat"]
      treat 
-0.05600935 

Or the ADE + ACME:

coef(c)["treat"] + coef(b)["treat"] * coef(c)["job_seek"]
      treat 
-0.05600935 

Proportion mediated is ACME / total effect:

coef(b)["treat"] * coef(c)["job_seek"] / coef(a)["treat"]
    treat 
0.2811075 

Summary of all model coefficients:

summary(a)

Call:
lm(formula = depress2 ~ treat + econ_hard + sex + age, data = jobs)

Residuals:
    Min      1Q  Median      3Q     Max 
-1.0584 -0.4935 -0.1346  0.3738  2.8561 

Coefficients:
              Estimate Std. Error t value Pr(>|t|)    
(Intercept)  1.3268664  0.1098292  12.081  < 2e-16 ***
treat       -0.0560093  0.0451637  -1.240   0.2152    
econ_hard    0.1357868  0.0215838   6.291 4.93e-10 ***
sex          0.1086375  0.0427532   2.541   0.0112 *  
age         -0.0004517  0.0020347  -0.222   0.8244    
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 0.6369 on 894 degrees of freedom
Multiple R-squared:  0.04924,   Adjusted R-squared:  0.04498 
F-statistic: 11.57 on 4 and 894 DF,  p-value: 3.635e-09
summary(b)

Call:
lm(formula = job_seek ~ treat + econ_hard + sex + age, data = jobs)

Residuals:
    Min      1Q  Median      3Q     Max 
-3.1658 -0.4092  0.0577  0.5819  1.1121 

Coefficients:
             Estimate Std. Error t value Pr(>|t|)    
(Intercept)  3.670585   0.125169  29.325   <2e-16 ***
treat        0.065615   0.051472   1.275   0.2027    
econ_hard    0.053162   0.024598   2.161   0.0309 *  
sex         -0.007637   0.048725  -0.157   0.8755    
age          0.004586   0.002319   1.978   0.0482 *  
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 0.7259 on 894 degrees of freedom
Multiple R-squared:  0.01147,   Adjusted R-squared:  0.007048 
F-statistic: 2.593 on 4 and 894 DF,  p-value: 0.03529
summary(c)

Call:
lm(formula = depress2 ~ treat + job_seek + econ_hard + sex + 
    age, data = jobs)

Residuals:
    Min      1Q  Median      3Q     Max 
-1.5192 -0.4461 -0.1503  0.3597  2.7568 

Coefficients:
              Estimate Std. Error t value Pr(>|t|)    
(Intercept)  2.2076414  0.1480548  14.911  < 2e-16 ***
treat       -0.0402647  0.0435059  -0.926   0.3550    
job_seek    -0.2399550  0.0282433  -8.496  < 2e-16 ***
econ_hard    0.1485434  0.0208269   7.132 2.04e-12 ***
sex          0.1068049  0.0411471   2.596   0.0096 ** 
age          0.0006489  0.0019625   0.331   0.7410    
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 0.613 on 893 degrees of freedom
Multiple R-squared:  0.1203,    Adjusted R-squared:  0.1154 
F-statistic: 24.43 on 5 and 893 DF,  p-value: < 2.2e-16
library(jtools)

And some pics:

plot_summs(a, c, model.names = c("a", "b"))

plot_summs(b)

LS0tDQp0aXRsZTogIk1lZGlhdG9yIGVmZmVjdHMiDQphdXRob3I6ICJBbmRpIEZ1Z2FyZCINCmRhdGU6IDMwIEp1bmUgMjAyMw0Kb3V0cHV0OiANCiAgaHRtbF9ub3RlYm9vazogDQogICAgY29kZV9mb2xkaW5nOiBub25lDQotLS0NCg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCmxpYnJhcnkobWVkaWF0aW9uKQ0KYGBgDQoNCg0KDQpGaXQgYSBtb2RlbCB0byBlc3RpbWF0ZSB0aGUgdG90YWwgZWZmZWN0LiAqdHJlYXQqIGlzIHRoZSB0cmVhdG1lbnQgYW5kICpkZXByZXNzMiogdGhlIG91dGNvbWUuIFRoZSBvdGhlciB2YXJpYWJsZXMgYXJlIGNvdmFyaWF0ZXMuDQoNCmBgYHtyfQ0KYSA8LSBsbShkZXByZXNzMiB+IHRyZWF0ICsgZWNvbl9oYXJkICsgc2V4ICsgYWdlLCBkYXRhPWpvYnMpDQpgYGANCg0KTm93IHRoZSBtb2RlbHMgZm9yIHRoZSBtZWRpYXRpb24gdGVzdCAoKmpvYl9zZWVrKiBpcyB0aGUgbWVkaWF0b3IpOg0KDQpgYGB7cn0NCmIgPC0gbG0oam9iX3NlZWsgfiB0cmVhdCArIGVjb25faGFyZCArIHNleCArIGFnZSwgZGF0YT1qb2JzKQ0KYyA8LSBsbShkZXByZXNzMiB+IHRyZWF0ICsgam9iX3NlZWsgKyBlY29uX2hhcmQgKyBzZXggKyBhZ2UsIGRhdGE9am9icykNCmBgYA0KDQoNCkVzdGltYXRlIHRoZSBDSXMgZm9yIHRoZSB2YXJpb3VzIGVmZmVjdHMgYnkgbm9ucGFyYW1ldHJpYyBib290c3RyYXA6DQoNCmBgYHtyfQ0KY29udGNvbnQuYm9vdCA8LSBtZWRpYXRlKGIsIGMsIGJvb3Q9VFJVRSwgc2ltcz0xMDAwLCB0cmVhdD0idHJlYXQiLCBtZWRpYXRvcj0iam9iX3NlZWsiKQ0KYGBgDQoNCg0KYGBge3J9DQpzdW1tYXJ5KGNvbnRjb250LmJvb3QpDQpgYGANCg0KQXZlcmFnZSBkaXJlY3QgZWZmZWN0IChBREUpOg0KDQpgYGB7cn0NCmNvZWYoYylbInRyZWF0Il0NCmBgYA0KDQpBdmVyYWdlIGNhdXNhbCBtZWRpYXRpb24gZWZmZWN0IChBQ01FKToNCg0KYGBge3J9DQpjb2VmKGIpWyJ0cmVhdCJdICogY29lZihjKVsiam9iX3NlZWsiXQ0KYGBgDQoNCg0KVG90YWwgZWZmZWN0Og0KDQpgYGB7cn0NCmNvZWYoYSlbInRyZWF0Il0NCmBgYA0KT3IgdGhlIEFERSArIEFDTUU6DQoNCmBgYHtyfQ0KY29lZihjKVsidHJlYXQiXSArIGNvZWYoYilbInRyZWF0Il0gKiBjb2VmKGMpWyJqb2Jfc2VlayJdDQpgYGANCg0KDQpQcm9wb3J0aW9uIG1lZGlhdGVkIGlzIEFDTUUgLyB0b3RhbCBlZmZlY3Q6DQoNCmBgYHtyfQ0KY29lZihiKVsidHJlYXQiXSAqIGNvZWYoYylbImpvYl9zZWVrIl0gLyBjb2VmKGEpWyJ0cmVhdCJdDQpgYGANCg0KU3VtbWFyeSBvZiBhbGwgbW9kZWwgY29lZmZpY2llbnRzOg0KDQpgYGB7cn0NCnN1bW1hcnkoYSkNCnN1bW1hcnkoYikNCnN1bW1hcnkoYykNCmBgYA0KDQpgYGB7cn0NCmxpYnJhcnkoanRvb2xzKQ0KYGBgDQoNCg0KQW5kIHNvbWUgcGljczoNCg0KYGBge3J9DQpwbG90X3N1bW1zKGEsIGMsIG1vZGVsLm5hbWVzID0gYygiYSIsICJiIikpDQpgYGANCg0KDQpgYGB7cn0NCnBsb3Rfc3VtbXMoYikNCmBgYA0K