Suppose we conduct experiment with complete randomization, obtain the assignment \(W = (1,0,0,0,0,0,1,1)'\), and the observed outcome for all units are \(Y^{\text{obs}} = (14, 6, 4, 5, 6, 7, 10, 9)'\).

W <- c(1,0,0,0,0,0,1,1)

Yobs <- c(14, 6, 4, 5, 6, 7, 10, 9) 

# Under the sharp null of no causal effect
Y0 <- Y1 <- Yobs
## observed test statistics value, here you can also replace the difference-in-means estimator with other types of test statistics
tau_obs <- mean(Yobs[W==1]) - mean(Yobs[W==0])

### get all possible assignments
treated_all = combn(8, 3)
W_all = matrix(0, nrow = 56, ncol = 8)
for(m in 1:56){
  W_all[m, treated_all[,m]] = 1
}
dim(W_all)
[1] 56  8
tau_rand <- apply(W_all, 1, function(w) mean(Y1[w==1]) - mean(Y0[w==0]))
hist(tau_rand, breaks = 10, main="Distribution under sharp Null of no individual causal effect", xlab="Obs Diff in Means")

hist(tau_rand, breaks = 10, main="Distribution under sharp Null of no individual causal effect", xlab="Obs Diff in Means")
abline(v = tau_obs, col = "red")


## calculate p-value 
p <- mean(abs(tau_rand) >= abs(tau_obs))
print(p)
[1] 0.01785714
# Under the sharp null of causal effect is 1
Y0 <- Y1 <- rep(NA, length(Yobs))
Y0[W == 0] <- Yobs[W == 0]
Y0[W == 1] <- Yobs[W == 1] - 1

Y1[W == 1] <- Yobs[W == 1]
Y1[W == 0] <- Yobs[W == 0] + 1

## recompute the distribution of Y1[w = 1] - Y0[w = 0] - 1
tau_rand <- apply(W_all, 1, function(w) mean(Y1[w==1]) - mean(Y0[w==0]))
p = mean(abs(tau_rand - 1) >= abs(tau_obs - 1))
print(p)
[1] 0.01785714
# need to write a function that compute p-value for any tau
getPvalue <- function(tau, Yobs, W) {
  Y0 <- Y1 <- rep(NA, length(Yobs))
  Y0[W == 0] <- Yobs[W == 0]
  Y0[W == 1] <- Yobs[W == 1] - tau

  Y1[W == 1] <- Yobs[W == 1]
  Y1[W == 0] <- Yobs[W == 0] + tau

  ## recompute the distribution of Y1[w = 1] - Y0[w = 0] - 1
  tau_rand <- apply(W_all, 1, function(w) mean(Y1[w==1]) - mean(Y0[w==0]))
  p = mean(abs(tau_rand - tau) >= abs(tau_obs - tau))
  return(p)
}

## Compute the p-value for a seqeunce of tau
tau_seq <- seq(-20, 20, by = 1)
pvalue_seq <- sapply(tau_seq, getPvalue, Yobs, W)
names(pvalue_seq) <- tau_seq
print(pvalue_seq, digits = 3)
   -20    -19    -18    -17    -16    -15    -14    -13    -12    -11    -10     -9     -8 
0.0179 0.0179 0.0179 0.0179 0.0179 0.0179 0.0179 0.0179 0.0179 0.0179 0.0179 0.0179 0.0179 
    -7     -6     -5     -4     -3     -2     -1      0      1      2      3      4      5 
0.0179 0.0179 0.0179 0.0179 0.0179 0.0179 0.0179 0.0179 0.0179 0.0357 0.1786 0.3929 0.8571 
     6      7      8      9     10     11     12     13     14     15     16     17     18 
0.6964 0.3571 0.1429 0.0536 0.0357 0.0179 0.0179 0.0179 0.0179 0.0179 0.0179 0.0179 0.0179 
    19     20 
0.0179 0.0179 

A rough 95% confidence interval is [2, 10]. You may want a more refined confidence interval by finding the cutoff that is close to 0.05.

## find a better lower bound
tau_seq1 <- seq(2, 3, by = 0.1)
pvalue_seq1 <- sapply(tau_seq1, getPvalue, Yobs, W)
names(pvalue_seq1) <- tau_seq1
print(pvalue_seq1, digits = 3)
     2    2.1    2.2    2.3    2.4    2.5    2.6    2.7    2.8    2.9      3 
0.0357 0.0357 0.0357 0.0357 0.0357 0.0357 0.0357 0.0357 0.0357 0.0357 0.1786 
## find a better upper bound

tau_seq2 <- seq(9, 10, by = 0.1)
pvalue_seq2 <- sapply(tau_seq2, getPvalue, Yobs, W)
names(pvalue_seq2) <- tau_seq2
print(pvalue_seq2, digits = 3)
     9    9.1    9.2    9.3    9.4    9.5    9.6    9.7    9.8    9.9     10 
0.0536 0.0357 0.0357 0.0357 0.0357 0.0357 0.0357 0.0357 0.0357 0.0357 0.0357 

A more refined 95% CI is [2.9, 9.1]

rr ## Say we want M = 10000 random samples of the treatment assignments ### get M random samples of possible assignments getPvalueMC <- function(tau, Yobs, W, M) { Y0 <- Y1 <- rep(NA, length(Yobs)) Y0[W == 0] <- Yobs[W == 0] Y0[W == 1] <- Yobs[W == 1] - tau

Y1[W == 1] <- Yobs[W == 1] Y1[W == 0] <- Yobs[W == 0] + tau

## recompute the distribution of Y1[w = 1] - Y0[w = 0] - 1 tau_rand <- apply(W_all, 1, function(w) mean(Y1[w==1]) - mean(Y0[w==0]))

set.seed(0) tau_rand <- sapply(1:M, function(i) { w <- sample(W) return(mean(Y1[w==1]) - mean(Y0[w==0])) }) p = mean(abs(tau_rand - tau) >= abs(tau_obs - tau)) return(p) }

getPvalueMC(0, Yobs, W, 10000)

[1] 0.0171
LS0tCnRpdGxlOiAiQW4gZXhhbXBsZSB0byBjb21wdXRlIEZpc2hlcidzIGV4YWN0IHAtdmFsdWUgYW5kIENJIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgpTdXBwb3NlIHdlIGNvbmR1Y3QgZXhwZXJpbWVudCB3aXRoIGNvbXBsZXRlIHJhbmRvbWl6YXRpb24sIG9idGFpbiB0aGUgYXNzaWdubWVudCAkVyA9ICgxLDAsMCwwLDAsMCwxLDEpJyQsIGFuZCB0aGUgb2JzZXJ2ZWQgb3V0Y29tZSBmb3IgYWxsIHVuaXRzIGFyZSAkWV57XHRleHR7b2JzfX0gPSAoMTQsIDYsIDQsIDUsIDYsIDcsIDEwLCA5KSckLiAgCgotIEltcHV0ZSBhbGwgdGhlIG1pc3NpbmcgcG90ZW50aWFsIG91dGNvbWVzIHVuZGVyIHRoZSBzaGFycCBudWxsIGh5cG90aGVzaXMgb2Ygbm8gaW5kaXZpZHVhbCBjYXVzYWwgZWZmZWN0cy4gCgpgYGB7cn0KVyA8LSBjKDEsMCwwLDAsMCwwLDEsMSkKCllvYnMgPC0gYygxNCwgNiwgNCwgNSwgNiwgNywgMTAsIDkpIAoKIyBVbmRlciB0aGUgc2hhcnAgbnVsbCBvZiBubyBjYXVzYWwgZWZmZWN0ClkwIDwtIFkxIDwtIFlvYnMKYGBgCgotIEFzc3VtZSB0aGUgc2hhcnAgbnVsbCBoeXBvdGhlc2lzIG9mIG5vIGluZGl2aWR1YWwgY2F1c2FsIGVmZmVjdHMuIFdoYXQgaXMgdGhlIGRpc3RyaWJ1dGlvbiBvZiB0aGUgZGlmZmVyZW5jZS1pbi1tZWFucyBlc3RpbWF0b3IgJFxoYXR7XHRhdX0kLiAKCmBgYHtyfQojIyBvYnNlcnZlZCB0ZXN0IHN0YXRpc3RpY3MgdmFsdWUsIGhlcmUgeW91IGNhbiBhbHNvIHJlcGxhY2UgdGhlIGRpZmZlcmVuY2UtaW4tbWVhbnMgZXN0aW1hdG9yIHdpdGggb3RoZXIgdHlwZXMgb2YgdGVzdCBzdGF0aXN0aWNzCnRhdV9vYnMgPC0gbWVhbihZb2JzW1c9PTFdKSAtIG1lYW4oWW9ic1tXPT0wXSkKCiMjIyBnZXQgYWxsIHBvc3NpYmxlIGFzc2lnbm1lbnRzCnRyZWF0ZWRfYWxsID0gY29tYm4oOCwgMykKV19hbGwgPSBtYXRyaXgoMCwgbnJvdyA9IDU2LCBuY29sID0gOCkKZm9yKG0gaW4gMTo1Nil7CiAgV19hbGxbbSwgdHJlYXRlZF9hbGxbLG1dXSA9IDEKfQpkaW0oV19hbGwpCgp0YXVfcmFuZCA8LSBhcHBseShXX2FsbCwgMSwgZnVuY3Rpb24odykgbWVhbihZMVt3PT0xXSkgLSBtZWFuKFkwW3c9PTBdKSkKaGlzdCh0YXVfcmFuZCwgYnJlYWtzID0gMTAsIG1haW49IkRpc3RyaWJ1dGlvbiB1bmRlciBzaGFycCBOdWxsIG9mIG5vIGluZGl2aWR1YWwgY2F1c2FsIGVmZmVjdCIsIHhsYWI9Ik9icyBEaWZmIGluIE1lYW5zIikKYGBgCgoKCi0gSG93IGV4dHJlbWUgaXMgdGhlIG9ic2VydmVkIGRpZmZlcmVuY2UtaW4tbWVhbnMgZXN0aW1hdG9yICRcaGF0e1x0YXV9XntcdGV4dHtvYnN9fSQ/IFdoYXQgaXMgdGhlIHByb2JhYmlsaXR5IG9mIGhhdmluZyBhdCBsZWFzdCBhcyBleHRyZW1lIGFzIHRoZSBvYnNlcnZlZCBvbmU/IE1vcmUgcmlnb3JvdXNseSwgc3BlY2lmeSB5b3VyIHRlc3Qgc3RhdGlzdGljIGFuZCByZXBvcnQgeW91ciByYW5kb21pemF0aW9uIHRlc3QgJHAkLXZhbHVlLiAKCmBgYHtyfQpoaXN0KHRhdV9yYW5kLCBicmVha3MgPSAxMCwgbWFpbj0iRGlzdHJpYnV0aW9uIHVuZGVyIHNoYXJwIE51bGwgb2Ygbm8gaW5kaXZpZHVhbCBjYXVzYWwgZWZmZWN0IiwgeGxhYj0iT2JzIERpZmYgaW4gTWVhbnMiKQphYmxpbmUodiA9IHRhdV9vYnMsIGNvbCA9ICJyZWQiKQoKIyMgY2FsY3VsYXRlIHAtdmFsdWUgCnAgPC0gbWVhbihhYnModGF1X3JhbmQpID49IGFicyh0YXVfb2JzKSkKcHJpbnQocCkKYGBgCgotIE9idGFpbiBGaXNoZXIncyBleGFjdCBwLXZhbHVlIHVuZGVyIHRoZSBzaGFycCBudWxsIGh5cG90aGVzaXMgdGhhdCB0aGUgaW5kaXZpZHVhbCB0cmVhdG1lbnQgZWZmZWN0cyBhcmUgYWxsIDEuIAoKYGBge3J9CiMgVW5kZXIgdGhlIHNoYXJwIG51bGwgb2YgY2F1c2FsIGVmZmVjdCBpcyAxClkwIDwtIFkxIDwtIHJlcChOQSwgbGVuZ3RoKFlvYnMpKQpZMFtXID09IDBdIDwtIFlvYnNbVyA9PSAwXQpZMFtXID09IDFdIDwtIFlvYnNbVyA9PSAxXSAtIDEKClkxW1cgPT0gMV0gPC0gWW9ic1tXID09IDFdClkxW1cgPT0gMF0gPC0gWW9ic1tXID09IDBdICsgMQoKIyMgcmVjb21wdXRlIHRoZSBkaXN0cmlidXRpb24gb2YgWTFbdyA9IDFdIC0gWTBbdyA9IDBdIC0gMQp0YXVfcmFuZCA8LSBhcHBseShXX2FsbCwgMSwgZnVuY3Rpb24odykgbWVhbihZMVt3PT0xXSkgLSBtZWFuKFkwW3c9PTBdKSkKcCA9IG1lYW4oYWJzKHRhdV9yYW5kIC0gMSkgPj0gYWJzKHRhdV9vYnMgLSAxKSkKcHJpbnQocCkKYGBgCgotIENhbGN1bGF0ZSB0aGUgcmFuZG9taXphdGlvbiAkcCQtdmFsdWUgZm9yIGEgc2VxdWVuY2Ugb2Ygc2hhcnAgbnVsbCBoeXBvdGhlc2VzIG9mIGFkZGl0aXZlIHRyZWF0bWVudCBlZmZlY3RzIG9mICRcey0yMCwtMTksIFxsZG90cywgMTksMjBcfSQsIGFuZCBnZXQgdGhlICQ5NVwlJCBjb25maWRlbmNlIGludGVydmFsCgpgYGB7cn0KIyBuZWVkIHRvIHdyaXRlIGEgZnVuY3Rpb24gdGhhdCBjb21wdXRlIHAtdmFsdWUgZm9yIGFueSB0YXUKZ2V0UHZhbHVlIDwtIGZ1bmN0aW9uKHRhdSwgWW9icywgVykgewogIFkwIDwtIFkxIDwtIHJlcChOQSwgbGVuZ3RoKFlvYnMpKQogIFkwW1cgPT0gMF0gPC0gWW9ic1tXID09IDBdCiAgWTBbVyA9PSAxXSA8LSBZb2JzW1cgPT0gMV0gLSB0YXUKCiAgWTFbVyA9PSAxXSA8LSBZb2JzW1cgPT0gMV0KICBZMVtXID09IDBdIDwtIFlvYnNbVyA9PSAwXSArIHRhdQoKICAjIyByZWNvbXB1dGUgdGhlIGRpc3RyaWJ1dGlvbiBvZiBZMVt3ID0gMV0gLSBZMFt3ID0gMF0gLSAxCiAgdGF1X3JhbmQgPC0gYXBwbHkoV19hbGwsIDEsIGZ1bmN0aW9uKHcpIG1lYW4oWTFbdz09MV0pIC0gbWVhbihZMFt3PT0wXSkpCiAgcCA9IG1lYW4oYWJzKHRhdV9yYW5kIC0gdGF1KSA+PSBhYnModGF1X29icyAtIHRhdSkpCiAgcmV0dXJuKHApCn0KCiMjIENvbXB1dGUgdGhlIHAtdmFsdWUgZm9yIGEgc2VxZXVuY2Ugb2YgdGF1CnRhdV9zZXEgPC0gc2VxKC0yMCwgMjAsIGJ5ID0gMSkKcHZhbHVlX3NlcSA8LSBzYXBwbHkodGF1X3NlcSwgZ2V0UHZhbHVlLCBZb2JzLCBXKQpuYW1lcyhwdmFsdWVfc2VxKSA8LSB0YXVfc2VxCnByaW50KHB2YWx1ZV9zZXEsIGRpZ2l0cyA9IDMpCmBgYAoKQSByb3VnaCA5NSUgY29uZmlkZW5jZSBpbnRlcnZhbCBpcyBbMiwgMTBdLiBZb3UgbWF5IHdhbnQgYSBtb3JlIHJlZmluZWQgY29uZmlkZW5jZSBpbnRlcnZhbCBieSBmaW5kaW5nIHRoZSBjdXRvZmYgdGhhdCBpcyBjbG9zZSB0byAwLjA1LgpgYGB7cn0KIyMgZmluZCBhIGJldHRlciBsb3dlciBib3VuZAp0YXVfc2VxMSA8LSBzZXEoMiwgMywgYnkgPSAwLjEpCnB2YWx1ZV9zZXExIDwtIHNhcHBseSh0YXVfc2VxMSwgZ2V0UHZhbHVlLCBZb2JzLCBXKQpuYW1lcyhwdmFsdWVfc2VxMSkgPC0gdGF1X3NlcTEKcHJpbnQocHZhbHVlX3NlcTEsIGRpZ2l0cyA9IDMpCgojIyBmaW5kIGEgYmV0dGVyIHVwcGVyIGJvdW5kCgp0YXVfc2VxMiA8LSBzZXEoOSwgMTAsIGJ5ID0gMC4xKQpwdmFsdWVfc2VxMiA8LSBzYXBwbHkodGF1X3NlcTIsIGdldFB2YWx1ZSwgWW9icywgVykKbmFtZXMocHZhbHVlX3NlcTIpIDwtIHRhdV9zZXEyCnByaW50KHB2YWx1ZV9zZXEyLCBkaWdpdHMgPSAzKQpgYGAKCkEgbW9yZSByZWZpbmVkIDk1JSBDSSBpcyBbMi45LCA5LjFdCgotIFdoZW4gdGhlIHNhbXBsZSBzaXplIGlzIGxhcmdlLCBpdCBpcyBpbXBvc3NpYmxlIHRvIGVudW1lcmF0ZSBhbGwgcG9zc2libGUgcmFuZG9taXphdGlvbnMuIEhvdyB3b3VsZCB5b3UgZ2V0IGFuIGFwcHJveGltYXRpb24gZm9yIHRoZSByYW5kb21pemF0aW9uIHRlc3QgJHAkLXZhbHVlPwpgYGB7cn0KIyMgU2F5IHdlIHdhbnQgTSA9IDEwMDAwIHJhbmRvbSBzYW1wbGVzIG9mIHRoZSB0cmVhdG1lbnQgYXNzaWdubWVudHMKIyMjIGdldCBNIHJhbmRvbSBzYW1wbGVzIG9mIHBvc3NpYmxlIGFzc2lnbm1lbnRzCmdldFB2YWx1ZU1DIDwtIGZ1bmN0aW9uKHRhdSwgWW9icywgVywgTSkgewogIFkwIDwtIFkxIDwtIHJlcChOQSwgbGVuZ3RoKFlvYnMpKQogIFkwW1cgPT0gMF0gPC0gWW9ic1tXID09IDBdCiAgWTBbVyA9PSAxXSA8LSBZb2JzW1cgPT0gMV0gLSB0YXUKCiAgWTFbVyA9PSAxXSA8LSBZb2JzW1cgPT0gMV0KICBZMVtXID09IDBdIDwtIFlvYnNbVyA9PSAwXSArIHRhdQoKICAjIyByYW5kb21seSBzYW1wbGUgdGhlIHRyZWF0bWVudCBhc3NpZ25tZW50IHZlY3RvciBmb2xsb3dpbmcgdGhlIGNvbXBsZXRlIHJhbmRvbWl6YXRpb24gbWVjaGFuaXNtCiAgCiAgc2V0LnNlZWQoMCkKICB0YXVfcmFuZCA8LSBzYXBwbHkoMTpNLCBmdW5jdGlvbihpKSB7CiAgICB3IDwtIHNhbXBsZShXKQogICAgcmV0dXJuKG1lYW4oWTFbdz09MV0pIC0gbWVhbihZMFt3PT0wXSkpCiAgfSkKICBwID0gbWVhbihhYnModGF1X3JhbmQgLSB0YXUpID49IGFicyh0YXVfb2JzIC0gdGF1KSkKICByZXR1cm4ocCkKfQoKZ2V0UHZhbHVlTUMoMCwgWW9icywgVywgMTAwMDApCgpgYGAK