I've got a seemingly easy situation, which turns out to be a little more complex than originally thought.
Here's the Setup:
We have a randomized controlled trial. Test and Control groups are the same size with large n. We have many measures about the individuals and know that they differ in those. However, we don't use any of those in treatment assignment (no stratification, etc.) Also, we have historic data for the target measure. During a period (here: post period), we apply an intervention to the test treatment and expect it to affect the measure. We know, that there is no interaction between individuals, so each observation is independent.
Now, what would be the "best" approach to conclude if the intervention was successful?
- Of course, the first and simplest thing you might do, is apply a simple t-Test on the intervention period group means.
- Next, you might wonder if there are better analyses that yield a higher power / precision. For example, we know the measure before the intervention. This sounds like it carries some information that we could use. So you might do a difference in difference approach. Here, you could take the post and pre period difference for the measure for both treatments and compare those means (again t-Test).
- Another possibility would be to apply a regression analysis. Here, you could regress the treatment and the pre period values on the post values. Again, making use of the information in the pre period for a better inference.
- Finally, you could also add an interaction term to 3. between treatment and period. This seems to be a somewhat standard approach in econometrics.
Here is my question as Python code with simulated data
import pandas as pd
import numpy as np
import statsmodels.formula.api as smf
# Create some data
n = 100
np.random.seed(10)
x_pre = pd.Series(np.random.normal(10, 2, n), name="pre")
y_pre = pd.Series(np.random.normal(10, 2, n), name="pre")
intervention = np.random.normal(5, 1, n)
# Post and pre are correlated
x_post = x_pre * np.random.normal(2, 1, n)
y_post = y_pre * np.random.normal(2, 1, n) + intervention
# data to analysis format
x = pd.concat([x_pre, x_post], axis=1, keys=["pre", "post"])
y = pd.concat([y_pre, y_post], axis=1, keys=["pre", "post"])
x["test"] = 0
y["test"] = 1
df = pd.concat([x, y]).reset_index(drop=True)
print(df.sample(4))
pre post test
17 10.270274 18.831519 0
77 11.241201 11.581746 0
80 13.970169 19.358396 0
114 9.374342 18.699756 1
Let's visualize the data:
import seaborn as sns
sns.scatterplot(x="pre", y="post", hue="test", data=df)
Now, let's compare the different approaches:
# center pre data
df["pre_centered"] = df["pre"] - df["pre"].mean()
FORMULAE = [
"post ~ test", # t-Test on Outcome Means
"I(post - pre) ~ test", # t-Test on Diff-in-Diff Outcome Means
"post ~ pre + test", # Add covariate for pre, account for pre differences
"post ~ pre + test + pre * test", # " + interaction
"post ~ pre_centered + test + pre_centered * test", # " + center pre
]
results = [smf.ols(formula=f, data=df).fit() for f in FORMULAE]
for r in results:
print(r.summary())
/e: Added the interaction regression with centered pre
as suggested by Noah in the comments.
Here's the output (slightly shortened for brevity):
OLS Regression Results
==============================================================================
Dep. Variable: post R-squared: 0.024
Model: OLS Adj. R-squared: 0.019
No. Observations: 200 AIC: 1524.
Df Residuals: 198 BIC: 1531.
Df Model: 1
Covariance Type: nonrobust
==============================================================================
coef std err t P>|t| [0.025 0.975]
------------------------------------------------------------------------------
Intercept 21.2926 1.088 19.572 0.000 19.147 23.438
test 3.4092 1.539 2.216 0.028 0.375 6.443
==============================================================================
Omnibus: 2.489 Durbin-Watson: 2.227
Prob(Omnibus): 0.288 Jarque-Bera (JB): 2.095
Skew: 0.223 Prob(JB): 0.351
Kurtosis: 3.229 Cond. No. 2.62
==============================================================================
OLS Regression Results
==============================================================================
Dep. Variable: I(post - pre) R-squared: 0.027
Model: OLS Adj. R-squared: 0.022
No. Observations: 200 AIC: 1502.
Df Residuals: 198 BIC: 1509.
Df Model: 1
Covariance Type: nonrobust
==============================================================================
coef std err t P>|t| [0.025 0.975]
------------------------------------------------------------------------------
Intercept 11.1337 1.029 10.822 0.000 9.105 13.163
test 3.4296 1.455 2.357 0.019 0.560 6.299
==============================================================================
Omnibus: 4.666 Durbin-Watson: 2.266
Prob(Omnibus): 0.097 Jarque-Bera (JB): 6.319
Skew: -0.028 Prob(JB): 0.0424
Kurtosis: 3.869 Cond. No. 2.62
==============================================================================
OLS Regression Results
==============================================================================
Dep. Variable: post R-squared: 0.167
Model: OLS Adj. R-squared: 0.159
No. Observations: 200 AIC: 1495.
Df Residuals: 197 BIC: 1504.
Df Model: 2
Covariance Type: nonrobust
==============================================================================
coef std err t P>|t| [0.025 0.975]
------------------------------------------------------------------------------
Intercept -0.2797 3.841 -0.073 0.942 -7.855 7.295
pre 2.1235 0.365 5.820 0.000 1.404 2.843
test 3.4526 1.425 2.423 0.016 0.643 6.262
==============================================================================
Omnibus: 17.035 Durbin-Watson: 2.287
Prob(Omnibus): 0.000 Jarque-Bera (JB): 34.674
Skew: -0.391 Prob(JB): 2.96e-08
Kurtosis: 4.884 Cond. No. 56.4
==============================================================================
OLS Regression Results
==============================================================================
Dep. Variable: post R-squared: 0.175
Model: OLS Adj. R-squared: 0.163
No. Observations: 200 AIC: 1495.
Df Residuals: 196 BIC: 1508.
Df Model: 3
Covariance Type: nonrobust
==============================================================================
coef std err t P>|t| [0.025 0.975]
------------------------------------------------------------------------------
Intercept -5.4464 5.375 -1.013 0.312 -16.046 5.154
pre 2.6321 0.520 5.064 0.000 1.607 3.657
test 13.5859 7.526 1.805 0.073 -1.257 28.429
pre:test -0.9985 0.728 -1.371 0.172 -2.435 0.438
==============================================================================
Omnibus: 14.283 Durbin-Watson: 2.289
Prob(Omnibus): 0.001 Jarque-Bera (JB): 24.704
Skew: -0.375 Prob(JB): 4.32e-06
Kurtosis: 4.549 Cond. No. 145.
==============================================================================
OLS Regression Results
==============================================================================
Dep. Variable: post R-squared: 0.175
Model: OLS Adj. R-squared: 0.163
No. Observations: 200 AIC: 1495.
Df Residuals: 196 BIC: 1508.
Df Model: 3
Covariance Type: nonrobust
=====================================================================================
coef std err t P>|t| [0.025 0.975]
-------------------------------------------------------------------------------------
Intercept 21.2657 1.005 21.155 0.000 19.283 23.248
pre_centered 2.6321 0.520 5.064 0.000 1.607 3.657
test 3.4528 1.422 2.429 0.016 0.649 6.256
pre_centered:test -0.9985 0.728 -1.371 0.172 -2.435 0.438
==============================================================================
Omnibus: 14.283 Durbin-Watson: 2.289
Prob(Omnibus): 0.001 Jarque-Bera (JB): 24.704
Skew: -0.375 Prob(JB): 4.32e-06
Kurtosis: 4.549 Cond. No. 5.13
==============================================================================
Here's some more specific questions:
- What are the differences between these methods?
- Which one is the most appropriate for this case?
- Why do the P values vary so widely?
- How would you interpret 4. (the interaction term regression)?
P.S:: I've already read a lot of blogs and papers regarding the general topic before posting this. However, there seem to be diverging opinions. (e.g. some people write that you shouldn't apply regression methods for RCTs because the assumptions are not satisfied, some people think that in most cases this is fine). So basically, this has confused be more than it has helped. Moreover, I have found many theoretical and general examples but only very few applied ones and none was exactly my case.
/e: This paper does a pretty similar comparison of methods. Unfortunately, their data is different as they have two follow up measurements.
test
depends on the value ofpre
:test = 13.5859 + (-0.9985 * pre)
. Pre has a range of values in the data. For each of those, the coefficient fortest
would be different. By centeringpre
(mean=0) we get the average treatment effect as a coefficient fortest
. $\endgroup$