Skip to contents

Applies a screening rule and returns one row per input trial: the keep decision, the probability behind it, the rule's label, and the reason for a flag. The four columns are the same whichever rule went in, so changing the rule changes one word and nothing around it.

Usage

rt_screen(
  rt,
  rule,
  response = NULL,
  .by = NULL,
  policy = c("threshold", "probabilistic"),
  threshold = 0.5
)

Arguments

rt

Numeric vector of response times in seconds. NA is allowed; non-positive values are an error.

rule

A rule object; see rules.

response

Optional response coding of the same length as rt, given as numeric 0/1, logical, or a character or factor using labels such as "correct"/"error" or "upper"/"lower". Required by rule_ewma() and by rule_mixture() with use_accuracy = TRUE.

.by

Optional grouping of the same length as rt: a vector, factor, list of vectors, or data frame. Rules are fitted separately within each group. NULL treats all trials as one group.

policy

Exclusion policy. "threshold" keeps a trial when its probability of validity exceeds threshold. "probabilistic" keeps it with that probability, drawing once per trial.

threshold

Cut for policy = "threshold", in [0, 1]. Ignored under the probabilistic policy.

Value

A data.frame with one row per element of rt, in input order:

.keep

logical; keep this trial under the stated policy.

.prob

numeric; the probability that the trial came from the decision process, that is P(valid). Deterministic rules return 0 or 1. Note that bmm::flag_contaminant_rts() returns the complement.

.rule

character; the rule's label.

.reason

character; why the rule flagged the trial: "too_fast", "too_slow", "contaminant", or "missing". NA whenever the rule did not flag it, which includes trials the keep policy dropped anyway (at threshold = 1, or on a probabilistic draw against a fractional .prob). A kept trial never carries a reason.

Per-group fit diagnostics are attached as attr(x, "fits"): one row per group with .group, n_trials, n_dropped, and prop_dropped, plus whatever the rule reports (bounds, iterations, EM convergence). screen_fits() returns that table on its own.

Details

Separating the probability from the decision is deliberate. A mixture rule produces a genuine posterior probability; a cutoff produces a degenerate one. Keeping both in the same object means a probabilistic screen can feed rt_summary() as a weight vector without being forced through a threshold first, and that the same comparison machinery covers both families.

Trials with a missing response time, or a missing value in any .by component, are excluded from every rule's computation and returned with .keep = FALSE, .prob = NA, and .reason = "missing".

Under policy = "probabilistic" the decision is stochastic by design. There is no set.seed() anywhere in rtprep; reproducibility is the caller's.

Inside a data-frame pipeline

The function takes vectors and returns a data frame, so it drops into dplyr::mutate() unchanged: mutate(rt_screen(rt, rule_sd(2.5)), .by = id) splices the four columns in, and filter(.keep) then drops the flagged trials. Two things to know. attr(x, "fits") does not survive mutate(); use screen_fits() when the per-group table is what you want. And dplyr reads .keep = as its own argument, so assign the column by splicing rather than by name. When only the filter is needed, rt_keep() returns the logical vector directly.

See also

rules for the rules themselves; rt_keep() for the keep vector alone; screen_fits() for the per-group table alone; screen_compare() to apply several rules at once; rt_summary() to aggregate what survives.

Examples

rt <- c(0.12, 0.31, 0.35, 0.38, 0.42, 0.47, 0.55, 2.90)
rt_screen(rt, rule_cutoff(0.18, 2.5))
#> <rtprep screen> 8 trials, cutoff(0.18, 2.5), 1 group
#>   kept 6 (75.0%), dropped 2 (25.0%)
#>   reasons: too_fast 1, too_slow 1
#>   policy: keep where .prob > 0.5
#>   per-group diagnostics: screen_fits(), or attr(x, "fits") -- 1 row
#> 
#>   .keep .prob             .rule  .reason
#> 1 FALSE     0 cutoff(0.18, 2.5) too_fast
#> 2  TRUE     1 cutoff(0.18, 2.5)     <NA>
#> 3  TRUE     1 cutoff(0.18, 2.5)     <NA>
#> 4  TRUE     1 cutoff(0.18, 2.5)     <NA>
#> 5  TRUE     1 cutoff(0.18, 2.5)     <NA>
#> 6  TRUE     1 cutoff(0.18, 2.5)     <NA>
#> # 2 more trials; as.data.frame(x) for all of them

# rules are group-aware without the package depending on dplyr
id <- rep(c("a", "b"), each = 4)
scr <- rt_screen(rt, rule_sd(2), .by = id)
attr(scr, "fits")
#>   .group n_trials n_dropped prop_dropped center     scale       lower    upper
#> 1      a        4         0            0  0.290 0.1169045  0.05619096 0.523809
#> 2      b        4         0            0  1.085 1.2111840 -1.33736799 3.507368

# a rule that reads accuracy takes it by name
correct <- c(0, 1, 1, 1, 0, 1, 1, 1)
rt_screen(rt, rule_ewma(lambda = 0.1), response = correct)
#> <rtprep screen> 8 trials, ewma(0.1, 1.5), 1 group
#>   kept 1 (12.5%), dropped 7 (87.5%)
#>   reasons: too_fast 7
#>   policy: keep where .prob > 0.5
#>   per-group diagnostics: screen_fits(), or attr(x, "fits") -- 1 row
#> 
#>   .keep .prob          .rule  .reason
#> 1 FALSE     0 ewma(0.1, 1.5) too_fast
#> 2 FALSE     0 ewma(0.1, 1.5) too_fast
#> 3 FALSE     0 ewma(0.1, 1.5) too_fast
#> 4 FALSE     0 ewma(0.1, 1.5) too_fast
#> 5 FALSE     0 ewma(0.1, 1.5) too_fast
#> 6 FALSE     0 ewma(0.1, 1.5) too_fast
#> # 2 more trials; as.data.frame(x) for all of them