## Hypothesis testing : Causal Interaction 
## using 1:1:1:1 matching 

# Y(0,0), Y(0,1), Y(1,0), Y(1,1) : monotonicity assumption
# Case 1: 0, 0, 0, 0 (no effect)
# Case 2: 0, 0, 0, 1 (interaction)
# Case 3: 0, 0, 1, 1 (treatment 1 effect only)
# Case 4: 0, 1, 0, 1 (treatment 2 effect only)
# Case 5: 0, 1, 1, 1 (both treatment effects)
# Case 6: 1, 1, 1, 1 (no effect)


## Initial parameter setting
## Each "Case k" -> q_k 
## q1 + q2 + q3 + q4 + q5 + q6 = 1

## Note that 
# p00 = q6
# p01 = q6 + q5 + q4
# p10 = q6 + q5 + q3
# p11 = q6 + q5 + q4 + q3 + q2

q6 = 0.1
q5 = 0.1
q4 = 0.05
q3 = 0.05
q2 = 0.2
q1 = 1 - q2 - q3 - q4 - q5 - q6
q.vec = c(q1, q2, q3, q4, q5, q6)

## Science table generation 
n = 200

case.matrix = rbind(c(0,0,0,0),
                    c(0,0,0,1),
                    c(0,0,1,1),
                    c(0,1,0,1),
                    c(0,1,1,1),
                    c(1,1,1,1))

q.vec.positive = which(q.vec > 0)

po.mat = c()
for(i in 1:length(q.vec.positive)){
  po.mat = rbind(po.mat, t(replicate(round(4*n*q.vec[q.vec.positive[i]]), case.matrix[q.vec.positive[i], ])))
}

## data generation 
sample.index = sample(1:(4*n), 4*n, replace = F)

obs.y00 = po.mat[sample.index[1:n], 1]
obs.y01 = po.mat[sample.index[(n+1):(2*n)], 2]
obs.y10 = po.mat[sample.index[(2*n+1):(3*n)], 3]
obs.y11 = po.mat[sample.index[(3*n+1):(4*n)], 4]
obs.ymat = cbind(obs.y00, obs.y01, obs.y10, obs.y11) # observed y matrix for all matched sets

apply(obs.ymat, 2, mean)


###############################
z1 = c(rep(0, 2*n), rep(1, 2*n))
z2 = c(rep(0, n), rep(1, n), rep(0, n), rep(1, n))
y = c(obs.y00, obs.y01, obs.y10, obs.y11)

fit.linear = lm(y ~ z1*z2)
summary(fit.linear)


################################
## make 2x4 contingency table
colnames(obs.ymat) = c("z00", "z01", "z10", "z11")

con.tab = cbind(table(obs.ymat[,1]), table(obs.ymat[,2]), table(obs.ymat[,3]), table(obs.ymat[,4]))
colnames(con.tab) = colnames(obs.ymat)
con.tab


#################################
## Exhaustive search: make a grid
# Y(0,0), Y(0,1), Y(1,0), Y(1,1) : monotonicity assumption
# Case 1: 0, 0, 0, 0 (no effect)
# Case 2: 0, 0, 0, 1 (interaction)
# Case 3: 0, 0, 1, 1 (treatment 1 effect only)
# Case 4: 0, 1, 0, 1 (treatment 2 effect only)
# Case 5: 0, 1, 1, 1 (both treatment effects)
# Case 6: 1, 1, 1, 1 (no effect)

# Suppose there is no "case 2" & "Case 5". 
# number of "Case 3" = A1
# number of "Case 4" = A2

A1.vec = seq(0, n, by = 1)
A2.vec = seq(0, n, by = 1)
Agrid = expand.grid(A1.vec, A2.vec)

## select compatible combinations
Agrid = Agrid[Agrid[,2] <= con.tab[2,2], ]
Agrid = Agrid[Agrid[,1] <= con.tab[2,3], ]
Agrid = Agrid[Agrid[,1] + Agrid[,2] <= con.tab[2,4], ]

index.grid.remove = which((Agrid[,2] == con.tab[2,2]) & (Agrid[,1] == con.tab[2,3]) & (Agrid[,1] + Agrid[,2] == con.tab[2,4]))
if(length(index.grid.remove) > 0){
  Agrid = Agrid[-index.grid.remove,]
}

pval.vec = rep(NA, dim(Agrid)[1])

for(i in 1:dim(Agrid)[1]){
  a1 = Agrid[i,1]
  a2 = Agrid[i,2]
  new.con.tab = con.tab
  new.con.tab[1,2] = con.tab[1,2] + a2
  new.con.tab[2,2] = con.tab[2,2] - a2
  new.con.tab[1,3] = con.tab[1,3] + a1
  new.con.tab[2,3] = con.tab[2,3] - a1
  new.con.tab[1,4] = con.tab[1,4] + a1 + a2 
  new.con.tab[2,4] = con.tab[2,4] - a1 - a2 
  pval.vec[i] = fisher.test(x = new.con.tab, simulate.p.value = F)$p.value
  #pval.vec[i] = chisq.test(x = new.con.tab)$p.value. ## when n is large, use chisq.test instead!
}

#################################
## Draw p-values over the grid
library(ggplot2)
library(dplyr)
Agrid = Agrid %>% 
  mutate(pval = pval.vec)

Agrid %>% 
  ggplot(aes(Var1, Var2)) + geom_point(aes(colour = pval), size = 1, alpha = 1) + 
  geom_point(data = filter(Agrid, pval <= 0.05), size = 1, colour = "grey70") +
  labs(x = expression(A[1]), y = expression(A[2]), color = "p-value")

## 
