@@ -999,3 +999,213 @@ test_that("print method shows rejection sequence", {
999999
10001000 expect_output(print(result ), " Rejection sequence" )
10011001})
1002+
1003+ # =============================================================================
1004+ # Look_back tests: H4-type (data at both analyses, crosses only at earlier)
1005+ # and H5-type (data at analysis 1 only, look_back from later analysis)
1006+ # =============================================================================
1007+
1008+ # Helper: oncology-like graph for look_back tests
1009+ gsd_onc_graph <- function () {
1010+ alpha_allocation <- c(0.01 , 0.01 , 0.004 , 0 , 0.0005 , 0.0005 )
1011+ hypotheses <- alpha_allocation / sum(alpha_allocation )
1012+ names(hypotheses ) <- c(" H1" , " H2" , " H3" , " H4" , " H5" , " H6" )
1013+ transitions <- rbind(
1014+ c(0 , 1 , 0 , 0 , 0 , 0 ),
1015+ c(0 , 0 , 0.5 , 0.5 , 0 , 0 ),
1016+ c(0 , 0 , 0 , 1 , 0 , 0 ),
1017+ c(0 , 0 , 0 , 0 , 0.5 , 0.5 ),
1018+ c(0 , 0 , 0 , 0 , 0 , 1 ),
1019+ c(0.5 , 0.5 , 0 , 0 , 0 , 0 )
1020+ )
1021+ graph_create(hypotheses , transitions )
1022+ }
1023+
1024+ gsd_onc_info_frac <- function () {
1025+ rbind(
1026+ H1 = c(185 / 295 , 245 / 295 , 1 ),
1027+ H2 = c(529 / 800 , 700 / 800 , 1 ),
1028+ H3 = c(265 / 310 , 1 , NA ),
1029+ H4 = c(675 / 750 , 1 , NA ),
1030+ H5 = c(1 , NA , NA ),
1031+ H6 = c(1 , NA , NA )
1032+ )
1033+ }
1034+
1035+ test_that(" H4-type look_back: data at both analyses, crosses only at earlier" , {
1036+ g <- gsd_onc_graph()
1037+ info_frac <- gsd_onc_info_frac()
1038+
1039+ # H4 has strong evidence at analysis 1, weak at analysis 2
1040+ # H4 starts with weight 0, gets weight after H3 rejection at analysis 2
1041+ p <- rbind(
1042+ H1 = c(0.03 , 0.0001 , 0.000001 ),
1043+ H2 = c(0.2 , 0.15 , 0.1 ),
1044+ H3 = c(0.2 , 0.001 , NA ),
1045+ H4 = c(0.0001 , 0.02 , NA ),
1046+ H5 = c(0.00001 , NA , NA ),
1047+ H6 = c(0.1 , NA , NA )
1048+ )
1049+
1050+ r_no <- graph_test_shortcut_gsd(
1051+ g , p , alpha = 0.025 , info_frac = info_frac ,
1052+ spending_fn = spending_of , look_back = FALSE
1053+ )
1054+ r_yes <- graph_test_shortcut_gsd(
1055+ g , p , alpha = 0.025 , info_frac = info_frac ,
1056+ spending_fn = spending_of , look_back = TRUE , test_values = TRUE
1057+ )
1058+
1059+ # H4 not rejected without look_back
1060+ expect_false(r_no $ outputs $ rejected [[" H4" ]])
1061+
1062+ # H4 rejected with look_back, attributed to analysis 1
1063+ expect_true(r_yes $ outputs $ rejected [[" H4" ]])
1064+ expect_equal(r_yes $ outputs $ decision_at [[" H4" ]], 2L )
1065+ expect_equal(r_yes $ outputs $ first_rejected_at [[" H4" ]], 1L )
1066+
1067+ # test_values at analysis 2: H4 has standard row (Reject=FALSE) and
1068+ # look_back row (Reject=TRUE)
1069+ tv2 <- r_yes $ test_values [[2 ]]
1070+ h4_standard <- tv2 [tv2 $ Hypothesis == " H4" & ! tv2 $ Look_back , ]
1071+ h4_lb <- tv2 [tv2 $ Hypothesis == " H4" & tv2 $ Look_back , ]
1072+
1073+ expect_equal(nrow(h4_standard ), 1 )
1074+ expect_false(h4_standard $ Reject )
1075+ expect_equal(h4_standard $ Analysis , 2L )
1076+
1077+ expect_equal(nrow(h4_lb ), 1 )
1078+ expect_true(h4_lb $ Reject )
1079+ expect_equal(h4_lb $ Analysis , 1L )
1080+ })
1081+
1082+ test_that(" H5-type look_back: single-analysis hypothesis, rejected via look_back at later analysis" , {
1083+ g <- gsd_onc_graph()
1084+ info_frac <- gsd_onc_info_frac()
1085+
1086+ # H5 has 1 analysis, p=0.0008 > initial boundary (0.0005)
1087+ # After graph propagation at analysis 2, H5 gets more weight and
1088+ # look_back finds p=0.0008 crosses the new boundary
1089+ p <- rbind(
1090+ H1 = c(0.03 , 0.0001 , 0.000001 ),
1091+ H2 = c(0.2 , 0.15 , 0.1 ),
1092+ H3 = c(0.2 , 0.001 , NA ),
1093+ H4 = c(0.0001 , 0.02 , NA ),
1094+ H5 = c(0.0008 , NA , NA ),
1095+ H6 = c(0.1 , NA , NA )
1096+ )
1097+
1098+ r_no <- graph_test_shortcut_gsd(
1099+ g , p , alpha = 0.025 , info_frac = info_frac ,
1100+ spending_fn = spending_of , look_back = FALSE
1101+ )
1102+ r_yes <- graph_test_shortcut_gsd(
1103+ g , p , alpha = 0.025 , info_frac = info_frac ,
1104+ spending_fn = spending_of , look_back = TRUE , test_values = TRUE
1105+ )
1106+
1107+ # H5 not rejected without look_back
1108+ expect_false(r_no $ outputs $ rejected [[" H5" ]])
1109+
1110+ # H5 rejected with look_back, attributed to analysis 1
1111+ expect_true(r_yes $ outputs $ rejected [[" H5" ]])
1112+ expect_equal(r_yes $ outputs $ decision_at [[" H5" ]], 2L )
1113+ expect_equal(r_yes $ outputs $ first_rejected_at [[" H5" ]], 1L )
1114+
1115+ # test_values at analysis 2: H5 has a standard row with p=NA (no data)
1116+ # and a look_back row at analysis 1 (Reject=TRUE)
1117+ tv2 <- r_yes $ test_values [[2 ]]
1118+ h5_standard <- tv2 [tv2 $ Hypothesis == " H5" & ! tv2 $ Look_back , ]
1119+ h5_lb <- tv2 [tv2 $ Hypothesis == " H5" & tv2 $ Look_back , ]
1120+
1121+ # Standard row: p is NA, Reject is FALSE
1122+ expect_equal(nrow(h5_standard ), 1 )
1123+ expect_true(is.na(h5_standard $ p ))
1124+ expect_false(h5_standard $ Reject )
1125+
1126+ # Look_back row: analysis 1, p=0.0008, Reject=TRUE
1127+ expect_equal(nrow(h5_lb ), 1 )
1128+ expect_true(h5_lb $ Reject )
1129+ expect_equal(h5_lb $ Analysis , 1L )
1130+ expect_equal(h5_lb $ p , 0.0008 )
1131+ })
1132+
1133+ test_that(" H5-type: without look_back, single-analysis hypothesis not reconsidered" , {
1134+ g <- gsd_onc_graph()
1135+ info_frac <- gsd_onc_info_frac()
1136+
1137+ p <- rbind(
1138+ H1 = c(0.03 , 0.0001 , 0.000001 ),
1139+ H2 = c(0.2 , 0.15 , 0.1 ),
1140+ H3 = c(0.2 , 0.001 , NA ),
1141+ H4 = c(0.0001 , 0.02 , NA ),
1142+ H5 = c(0.0008 , NA , NA ),
1143+ H6 = c(0.1 , NA , NA )
1144+ )
1145+
1146+ result <- graph_test_shortcut_gsd(
1147+ g , p , alpha = 0.025 , info_frac = info_frac ,
1148+ spending_fn = spending_of , look_back = FALSE , test_values = TRUE
1149+ )
1150+
1151+ # H5 not rejected
1152+ expect_false(result $ outputs $ rejected [[" H5" ]])
1153+ # H5 decision_at should be 1 (only analysis with data)
1154+ expect_equal(result $ outputs $ decision_at [[" H5" ]], 1L )
1155+
1156+ # H5 should not appear in test_values at analysis 2
1157+ tv2 <- result $ test_values [[2 ]]
1158+ expect_false(" H5" %in% tv2 $ Hypothesis )
1159+ })
1160+
1161+ test_that(" look_back with single-analysis hypothesis: not rejected when p too large" , {
1162+ g <- gsd_onc_graph()
1163+ info_frac <- gsd_onc_info_frac()
1164+
1165+ # H5 p=0.01 is too large even after propagation
1166+ p <- rbind(
1167+ H1 = c(0.03 , 0.0001 , 0.000001 ),
1168+ H2 = c(0.2 , 0.15 , 0.1 ),
1169+ H3 = c(0.2 , 0.001 , NA ),
1170+ H4 = c(0.0001 , 0.02 , NA ),
1171+ H5 = c(0.01 , NA , NA ),
1172+ H6 = c(0.1 , NA , NA )
1173+ )
1174+
1175+ result <- graph_test_shortcut_gsd(
1176+ g , p , alpha = 0.025 , info_frac = info_frac ,
1177+ spending_fn = spending_of , look_back = TRUE
1178+ )
1179+
1180+ # H5 still not rejected — p=0.01 too large even with increased weight
1181+ expect_false(result $ outputs $ rejected [[" H5" ]])
1182+ })
1183+
1184+ test_that(" look_back carries forward sequential p for hypotheses with no current data" , {
1185+ g <- gsd_onc_graph()
1186+ info_frac <- gsd_onc_info_frac()
1187+
1188+ p <- rbind(
1189+ H1 = c(0.03 , 0.0001 , 0.000001 ),
1190+ H2 = c(0.2 , 0.15 , 0.1 ),
1191+ H3 = c(0.2 , 0.001 , NA ),
1192+ H4 = c(0.0001 , 0.02 , NA ),
1193+ H5 = c(0.0008 , NA , NA ),
1194+ H6 = c(0.1 , NA , NA )
1195+ )
1196+
1197+ result <- graph_test_shortcut_gsd(
1198+ g , p , alpha = 0.025 , info_frac = info_frac ,
1199+ spending_fn = spending_of , look_back = TRUE
1200+ )
1201+
1202+ # Sequential p for H5 should be the same at analysis 1
1203+ # (only one analysis with data)
1204+ expect_equal(
1205+ result $ outputs $ sequential_p [" H5" , 1 ],
1206+ result $ outputs $ repeated_p [" H5" , 1 ]
1207+ )
1208+ # Analysis 2 and 3 should be NA for H5
1209+ expect_true(is.na(result $ outputs $ sequential_p [" H5" , 2 ]))
1210+ expect_true(is.na(result $ outputs $ sequential_p [" H5" , 3 ]))
1211+ })
0 commit comments