Contrast patterns identify conditions under which
numeric variables show statistically significant differences. In
nuggets, this family is represented by three related
functions:
dig_baseline_contrasts() for testing whether a variable
differs from a chosen baseline value under a condition,dig_complement_contrasts() for comparing rows
satisfying a condition with the remaining rows,dig_paired_baseline_contrasts() for comparing two
paired variables inside a condition.These pattern families answer different questions:
Before going further, load the packages used in this vignette:
For the overall package workflow, see
vignette("nuggets").
To demonstrate all three contrast types, we prepare a version of
iris that contains:
iris_contrasts <- iris |>
mutate(long_sepal = Sepal.Length >= median(Sepal.Length),
wide_petal = Petal.Width >= median(Petal.Width),
length_gap = Sepal.Length - Petal.Length,
width_gap = Sepal.Width - Petal.Width,
sepal_ratio = Sepal.Length / Sepal.Width,
petal_ratio = Petal.Length / Petal.Width) |>
partition(Species)
head(iris_contrasts, n = 3)
#> # A tibble: 3 × 13
#> Sepal.Length Sepal.Width Petal.Length Petal.Width long_sepal wide_petal
#> <dbl> <dbl> <dbl> <dbl> <lgl> <lgl>
#> 1 5.1 3.5 1.4 0.2 FALSE FALSE
#> 2 4.9 3 1.4 0.2 FALSE FALSE
#> 3 4.7 3.2 1.3 0.2 FALSE FALSE
#> length_gap width_gap sepal_ratio petal_ratio `Species=setosa`
#> <dbl> <dbl> <dbl> <dbl> <lgl>
#> 1 3.7 3.3 1.46 7 TRUE
#> 2 3.5 2.8 1.63 7 TRUE
#> 3 3.4 3 1.47 6.5 TRUE
#> `Species=versicolor` `Species=virginica`
#> <lgl> <lgl>
#> 1 FALSE FALSE
#> 2 FALSE FALSE
#> 3 FALSE FALSEThe Species factor is expanded into dummy predicates,
while the logical helper columns remain available as additional
condition predicates. The numeric columns are then used as the variables
being tested.
For more information on creating predicate columns, see
vignette("data-preparation").
The contrast functions use slightly different argument names, but they all rely on the same idea:
condition selects columns from which conditions are
generated,vars selects numeric variables for one-sample or
two-sample contrasts,xvars and yvars select paired numeric
variables.These arguments accept tidyselect expressions, so you can target specific sets of predicates and variables without manually listing every column.
Baseline contrasts search for conditions under which
a numeric variable differs from a chosen baseline value
h0.
The basic scheme is:
var != h0 | condition
Here we test whether two derived gap variables differ from zero
inside the discovered subgroups. With method = "t", the
underlying test is stats::t.test()
(one-sample, testing whether the mean equals h0):
baseline_result <- dig_baseline_contrasts(iris_contrasts,
condition = where(is.logical),
vars = c(length_gap, width_gap),
min_length = 1,
max_length = 2,
min_support = 0.2,
method = "t",
max_p_value = 0.01)
head(baseline_result, n = 6)
#> # A tibble: 6 × 15
#> condition support var estimate statistic df
#> <chr> <dbl> <chr> <dbl> <dbl> <dbl>
#> 1 {wide_petal} 0.567 length_gap 1.29 25.9 84
#> 2 {wide_petal} 0.567 width_gap 1.16 28.8 84
#> 3 {long_sepal,wide_petal} 0.473 length_gap 1.30 23.5 70
#> 4 {long_sepal,wide_petal} 0.473 width_gap 1.13 25.9 70
#> 5 {Species=versicolor,wide_petal} 0.233 length_gap 1.66 26.1 34
#> 6 {Species=versicolor,wide_petal} 0.233 width_gap 1.45 34.2 34
#> p_value n conf_lo conf_hi stderr alternative method comment
#> <dbl> <int> <dbl> <dbl> <dbl> <chr> <chr> <chr>
#> 1 9.35e-42 85 1.19 1.39 0.0500 two.sided One Sample t-test ""
#> 2 2.70e-45 85 1.08 1.24 0.0401 two.sided One Sample t-test ""
#> 3 7.01e-35 71 1.19 1.42 0.0556 two.sided One Sample t-test ""
#> 4 1.44e-37 71 1.04 1.22 0.0437 two.sided One Sample t-test ""
#> 5 4.65e-24 35 1.53 1.79 0.0637 two.sided One Sample t-test ""
#> 6 6.60e-28 35 1.37 1.54 0.0425 two.sided One Sample t-test ""
#> condition_length
#> <int>
#> 1 1
#> 2 1
#> 3 2
#> 4 2
#> 5 2
#> 6 2This result tells us under which conditions the mean gap is significantly different from zero.
condition - the generated condition,
support - relative frequency of the condition,var - the tested variable,estimate - the estimated mean difference from the
baseline,statistic - the test statistic (determined by
method argument),df - degrees of freedom for the test,p_value - significance of the test,n - number of rows in the corresponding sub-data,conf_lo, conf_hi - confidence-interval
bounds,stderr - standard error of the estimate,condition_length - number of predicates in the
condition,alternative, method, comment
- additional information about the test.If you prefer a rank-based one-sample test, use
method = "wilcox". This applies stats::wilcox.test()
(Wilcoxon signed-rank test), which tests whether the pseudo-median
equals h0:
baseline_wilcox <- dig_baseline_contrasts(iris_contrasts,
condition = starts_with("Species"),
vars = length_gap,
min_length = 1,
max_length = 1,
min_support = 0.2,
method = "wilcox",
max_p_value = 0.01)
baseline_wilcox
#> # A tibble: 3 × 13
#> condition support var estimate statistic p_value n
#> <chr> <dbl> <chr> <dbl> <dbl> <dbl> <int>
#> 1 {Species=setosa} 0.333 length_gap 3.55 1275 7.62e-10 50
#> 2 {Species=versicolor} 0.333 length_gap 1.70 1275 7.45e-10 50
#> 3 {Species=virginica} 0.333 length_gap 1.05 1275 7.48e-10 50
#> conf_lo conf_hi alternative
#> <dbl> <dbl> <chr>
#> 1 3.45 3.65 two.sided
#> 2 1.60 1.80 two.sided
#> 3 0.950 1.15 two.sided
#> method comment condition_length
#> <chr> <chr> <int>
#> 1 Wilcoxon signed rank test with continuity correction "" 1
#> 2 Wilcoxon signed rank test with continuity correction "" 1
#> 3 Wilcoxon signed rank test with continuity correction "" 1This is useful when you want a method that is less sensitive to departures from normality.
Complement contrasts compare a subgroup with the rest of the dataset. Their scheme is:
(var | condition) != (var | not condition)
This is often the most natural contrast pattern when you want to know
whether a condition identifies an unusual subgroup. With
method = "t", the underlying test is stats::t.test()
(two-sample Welch t-test, comparing the means of the condition subgroup
and its complement):
complement_result <- dig_complement_contrasts(iris_contrasts,
condition = where(is.logical),
vars = c(Sepal.Length, Petal.Length, petal_ratio),
min_length = 1,
max_length = 2,
min_support = 0.2,
method = "t",
max_p_value = 0.01)
head(complement_result, n = 6)
#> # A tibble: 6 × 17
#> condition support var estimate_x estimate_y statistic
#> <chr> <dbl> <chr> <dbl> <dbl> <dbl>
#> 1 {wide_petal} 0.567 Sepal.Length 6.39 5.13 14.9
#> 2 {wide_petal} 0.567 Petal.Length 5.10 2.01 20.6
#> 3 {wide_petal} 0.567 petal_ratio 2.92 6.13 -8.89
#> 4 {long_sepal,wide_petal} 0.473 Sepal.Length 6.56 5.20 17.6
#> 5 {long_sepal,wide_petal} 0.473 Petal.Length 5.26 2.41 17.2
#> 6 {long_sepal,wide_petal} 0.473 petal_ratio 2.91 5.57 -8.09
#> df p_value n_x n_y conf_lo conf_hi stderr alternative
#> <dbl> <dbl> <int> <int> <dbl> <dbl> <dbl> <chr>
#> 1 146. 4.86e-31 85 65 1.10 1.43 0.0849 two.sided
#> 2 109. 2.51e-39 85 65 2.79 3.39 0.150 two.sided
#> 3 65.7 7.11e-13 85 65 -3.92 -2.48 0.360 two.sided
#> 4 135. 1.02e-36 71 79 1.21 1.52 0.0776 two.sided
#> 5 119. 4.11e-34 71 79 2.52 3.18 0.165 two.sided
#> 6 81.2 4.95e-12 71 79 -3.31 -2.00 0.328 two.sided
#> method comment condition_length
#> <chr> <chr> <int>
#> 1 Welch Two Sample t-test "" 1
#> 2 Welch Two Sample t-test "" 1
#> 3 Welch Two Sample t-test "" 1
#> 4 Welch Two Sample t-test "" 2
#> 5 Welch Two Sample t-test "" 2
#> 6 Welch Two Sample t-test "" 2The output contains separate estimates for the subgroup and its complement:
estimate_x average (or median) value of selected
variable for rows satisfying the condition,estimate_y average (or median) value of selected
variable for rows not satisfying the condition,n_x and n_y for the corresponding sample
sizes.dig_complement_contrasts() also supports
method = "var" for testing whether the variability in one
group differs from the variability in its complement. This uses stats::var.test()
(F-test of equality of variances):
complement_var <- dig_complement_contrasts(iris_contrasts,
condition = starts_with("Species"),
vars = Petal.Length,
min_length = 1,
max_length = 1,
min_support = 0.2,
method = "var",
max_p_value = 0.01)
complement_var
#> # A tibble: 3 × 14
#> condition support var estimate statistic p_value n_x
#> <chr> <dbl> <chr> <dbl> <dbl> <dbl> <int>
#> 1 {Species=setosa} 0.333 Petal.Length 0.0442 0.0442 1.57e-22 50
#> 2 {Species=versicolor} 0.333 Petal.Length 0.0503 0.0503 2.96e-21 50
#> 3 {Species=virginica} 0.333 Petal.Length 0.145 0.145 2.28e-11 50
#> n_y conf_lo conf_hi alternative method comment
#> <int> <dbl> <dbl> <chr> <chr> <chr>
#> 1 100 0.0277 0.0736 two.sided F test to compare two variances ""
#> 2 100 0.0315 0.0837 two.sided F test to compare two variances ""
#> 3 100 0.0907 0.241 two.sided F test to compare two variances ""
#> condition_length
#> <int>
#> 1 1
#> 2 1
#> 3 1This is helpful when a subgroup is not mainly distinguished by a higher or lower mean, but by being more or less variable.
Paired baseline contrasts compare two numeric variables observed on the same rows under generated conditions.
The scheme is:
(xvar - yvar) != 0 | condition
This is appropriate for paired measurements such as “before vs
after”, “left vs right”, or two alternative measurements recorded for
the same case. With method = "t", the underlying test is stats::t.test()
(paired t-test, testing whether the mean difference equals zero):
paired_result <- dig_paired_baseline_contrasts(iris_contrasts,
condition = where(is.logical),
xvars = c(Sepal.Length, Sepal.Width),
yvars = c(Petal.Length, Petal.Width),
min_length = 1,
max_length = 1,
min_support = 0.2,
method = "t",
max_p_value = 0.01)
head(paired_result, n = 6)
#> # A tibble: 6 × 16
#> condition support xvar yvar estimate statistic df
#> <chr> <dbl> <chr> <chr> <dbl> <dbl> <dbl>
#> 1 {wide_petal} 0.567 Sepal.Length Petal.Length 1.29 25.9 84
#> 2 {wide_petal} 0.567 Sepal.Length Petal.Width 4.61 76.1 84
#> 3 {wide_petal} 0.567 Sepal.Width Petal.Length -2.16 -29.3 84
#> 4 {wide_petal} 0.567 Sepal.Width Petal.Width 1.16 28.8 84
#> 5 {long_sepal} 0.513 Sepal.Length Petal.Length 1.38 20.2 76
#> 6 {long_sepal} 0.513 Sepal.Length Petal.Width 4.74 80.4 76
#> p_value n conf_lo conf_hi stderr alternative method comment
#> <dbl> <int> <dbl> <dbl> <dbl> <chr> <chr> <chr>
#> 1 9.35e-42 85 1.19 1.39 0.0500 two.sided Paired t-test ""
#> 2 3.05e-79 85 4.49 4.73 0.0606 two.sided Paired t-test ""
#> 3 6.75e-46 85 -2.31 -2.02 0.0737 two.sided Paired t-test ""
#> 4 2.70e-45 85 1.08 1.24 0.0401 two.sided Paired t-test ""
#> 5 2.45e-32 77 1.24 1.51 0.0680 two.sided Paired t-test ""
#> 6 2.65e-75 77 4.62 4.85 0.0589 two.sided Paired t-test ""
#> condition_length
#> <int>
#> 1 1
#> 2 1
#> 3 1
#> 4 1
#> 5 1
#> 6 1The result reports the condition, the selected variable pair
(xvar, yvar), the estimated difference, test
statistic, p-value, and sample size.
For a paired rank-based alternative, use the Wilcoxon signed-rank
test by setting method = "wilcox". This calls stats::wilcox.test()
with paired = TRUE, testing whether the pseudo-median of
the pairwise differences equals zero:
paired_wilcox <- dig_paired_baseline_contrasts(iris_contrasts,
condition = starts_with("Species"),
xvars = Sepal.Length,
yvars = Petal.Length,
min_length = 1,
max_length = 1,
min_support = 0.2,
method = "wilcox",
max_p_value = 0.01)
paired_wilcox
#> # A tibble: 3 × 14
#> condition support xvar yvar estimate statistic
#> <chr> <dbl> <chr> <chr> <dbl> <dbl>
#> 1 {Species=setosa} 0.333 Sepal.Length Petal.Length 3.55 1275
#> 2 {Species=versicolor} 0.333 Sepal.Length Petal.Length 1.70 1275
#> 3 {Species=virginica} 0.333 Sepal.Length Petal.Length 1.05 1275
#> p_value n conf_lo conf_hi alternative
#> <dbl> <int> <dbl> <dbl> <chr>
#> 1 7.62e-10 50 3.45 3.65 two.sided
#> 2 7.45e-10 50 1.60 1.80 two.sided
#> 3 7.48e-10 50 0.950 1.15 two.sided
#> method comment condition_length
#> <chr> <chr> <int>
#> 1 Wilcoxon signed rank test with continuity correction "" 1
#> 2 Wilcoxon signed rank test with continuity correction "" 1
#> 3 Wilcoxon signed rank test with continuity correction "" 1All three contrast functions support the usual search controls:
min_length, max_length for condition
complexity,min_support, max_support for subgroup
size,max_results to stop long searches early,max_p_value to keep only statistically significant
results.The three contrast types complement each other:
In practice, it is often useful to start with complement contrasts to locate interesting subgroups, then refine the analysis with baseline or paired contrasts depending on the scientific question.
When reading discovered contrast patterns, keep the following points in mind:
estimate indicates a stronger
effect,p_value reflects statistical evidence for the chosen
alternative, but should be interpreted with caution due to multiple
comparisons (see below),support and n describe how much data
contributed to the pattern,A typical contrast-pattern search tests many condition–variable combinations simultaneously. When hundreds of tests are run at level 0.05, several spurious discoveries are expected by chance alone, even if no true effect exists.
The patterns returned by the dig_*_contrasts() functions
are therefore best understood as generated hypotheses -
promising associations that deserve further scrutiny - rather than as
confirmed findings. This is known as the problem of simultaneous
statistical inference or multiple comparisons.
A standard remedy is to adjust the p-values to control either the family-wise error rate (FWER) or the false discovery rate (FDR):
R’s built-in p.adjust() function supports both families.
The example below applies Holm correction (FWER) and Benjamini–Hochberg
correction (FDR) to the result of a complement-contrast search:
complement_result$p_holm <- p.adjust(complement_result$p_value, method = "holm")
complement_result$p_bh <- p.adjust(complement_result$p_value, method = "BH")
complement_result[, c("condition", "var", "p_value", "p_holm", "p_bh")]
#> # A tibble: 26 × 5
#> condition var p_value p_holm p_bh
#> <chr> <chr> <dbl> <dbl> <dbl>
#> 1 {wide_petal} Sepal.Length 4.86e-31 7.78e-30 1.15e-30
#> 2 {wide_petal} Petal.Length 2.51e-39 6.28e-38 3.27e-38
#> 3 {wide_petal} petal_ratio 7.11e-13 6.40e-12 1.03e-12
#> 4 {long_sepal,wide_petal} Sepal.Length 1.02e-36 2.34e-35 6.62e-36
#> 5 {long_sepal,wide_petal} Petal.Length 4.11e-34 7.82e-33 1.34e-33
#> 6 {long_sepal,wide_petal} petal_ratio 4.95e-12 3.47e-11 6.44e-12
#> 7 {Species=versicolor,wide_petal} Sepal.Length 3.41e- 3 3.41e- 3 3.41e- 3
#> 8 {Species=versicolor,wide_petal} Petal.Length 6.34e- 6 1.90e- 5 6.87e- 6
#> 9 {Species=versicolor,wide_petal} petal_ratio 2.31e- 8 1.15e- 7 2.73e- 8
#> 10 {Species=virginica,wide_petal} Sepal.Length 6.32e-17 8.85e-16 1.17e-16
#> # ℹ 16 more rowsAfter adjustment, you can filter by the corrected p-values:
complement_result[complement_result$p_bh < 0.05, ]
#> # A tibble: 26 × 19
#> condition support var estimate_x estimate_y
#> <chr> <dbl> <chr> <dbl> <dbl>
#> 1 {wide_petal} 0.567 Sepal.Length 6.39 5.13
#> 2 {wide_petal} 0.567 Petal.Length 5.10 2.01
#> 3 {wide_petal} 0.567 petal_ratio 2.92 6.13
#> 4 {long_sepal,wide_petal} 0.473 Sepal.Length 6.56 5.20
#> 5 {long_sepal,wide_petal} 0.473 Petal.Length 5.26 2.41
#> 6 {long_sepal,wide_petal} 0.473 petal_ratio 2.91 5.57
#> 7 {Species=versicolor,wide_petal} 0.233 Sepal.Length 6.11 5.76
#> 8 {Species=versicolor,wide_petal} 0.233 Petal.Length 4.45 3.55
#> 9 {Species=versicolor,wide_petal} 0.233 petal_ratio 3.12 4.67
#> 10 {Species=virginica,wide_petal} 0.333 Sepal.Length 6.59 5.47
#> statistic df p_value n_x n_y conf_lo conf_hi stderr alternative
#> <dbl> <dbl> <dbl> <int> <int> <dbl> <dbl> <dbl> <chr>
#> 1 14.9 146. 4.86e-31 85 65 1.10 1.43 0.0849 two.sided
#> 2 20.6 109. 2.51e-39 85 65 2.79 3.39 0.150 two.sided
#> 3 -8.89 65.7 7.11e-13 85 65 -3.92 -2.48 0.360 two.sided
#> 4 17.6 135. 1.02e-36 71 79 1.21 1.52 0.0776 two.sided
#> 5 17.2 119. 4.11e-34 71 79 2.52 3.18 0.165 two.sided
#> 6 -8.09 81.2 4.95e-12 71 79 -3.31 -2.00 0.328 two.sided
#> 7 2.99 110. 3.41e- 3 35 115 0.117 0.575 0.116 two.sided
#> 8 4.70 133. 6.34e- 6 35 115 0.522 1.28 0.192 two.sided
#> 9 -5.99 118. 2.31e- 8 35 115 -2.06 -1.04 0.258 two.sided
#> 10 10.1 98.9 6.32e-17 50 100 0.898 1.34 0.110 two.sided
#> method comment condition_length p_holm p_bh
#> <chr> <chr> <int> <dbl> <dbl>
#> 1 Welch Two Sample t-test "" 1 7.78e-30 1.15e-30
#> 2 Welch Two Sample t-test "" 1 6.28e-38 3.27e-38
#> 3 Welch Two Sample t-test "" 1 6.40e-12 1.03e-12
#> 4 Welch Two Sample t-test "" 2 2.34e-35 6.62e-36
#> 5 Welch Two Sample t-test "" 2 7.82e-33 1.34e-33
#> 6 Welch Two Sample t-test "" 2 3.47e-11 6.44e-12
#> 7 Welch Two Sample t-test "" 2 3.41e- 3 3.41e- 3
#> 8 Welch Two Sample t-test "" 2 1.90e- 5 6.87e- 6
#> 9 Welch Two Sample t-test "" 2 1.15e- 7 2.73e- 8
#> 10 Welch Two Sample t-test "" 2 8.85e-16 1.17e-16
#> # ℹ 16 more rowsIn a large exploratory search you may prefer the FDR approach (BH) because it keeps more patterns visible while still limiting the expected fraction of false discoveries. Use FWER control (Holm) when you need stronger guarantees.
This vignette introduced the main contrast-pattern workflows in
nuggets:
For related material, see:
vignette("data-preparation") for preparing predicate
columns,vignette("conditional-correlations") for subgroup-based
correlation analysis,vignette("custom-patterns") for custom statistical
pattern searches,vignette("nuggets") for the package overview.