Screening, trimming, and aggregating response time data before fitting an evidence accumulation model.
The package was written for a tutorial manuscript on response time preprocessing. That manuscript and the simulation studies behind it are held back until the paper is published; this repository carries the package itself.
Every response time analysis makes an exclusion decision, and most inherit it by convention: 200 ms and 3 s because that is what the last paper did, or ±2.5 SD because that is what the field does. The consequences are invisible in the data the researcher can see, and they are not small: contaminant trials bias evidence accumulation model parameters, and which contaminants a method can reach depends on how far they sit from the RT distribution the decision process itself produces.
The methods to deal with this exist and are scattered. What does not
exist is a way to compare them, because every implementation returns a
different kind of object: a trimmed data frame, a vector of per-trial
probabilities, a set of summary statistics. rtprep gives them one
interface, so you can ask what a different cutoff would have removed,
and how far two defensible choices disagree on your own data.
That last one matters most. Contamination is invisible in your own data; simulating it is the only way to check whether your pipeline did what you thought it did.
# from CRAN
install.packages("rtprep")
# the development version, from GitHub
# install.packages("remotes")
# remotes::install_github("GidonFrischkorn/rtprep")
install.packages("rtprep", repos = c('https://gidonfrischkorn.r-universe.dev', 'https://cloud.r-project.org'))
Every rule is a constructor, one engine applies it, and the result has the same four columns whichever rule went in:
library(rtprep)
set.seed(1)
dat <- r_contaminated(400, process = "mixed", rate = 0.1)
scr <- rt_screen(dat$rt, rule_sd(2.5))
head(scr)
#> <rtprep screen> 6 trials, sd(2.5, mean, sd)
#> kept 6 (100.0%), dropped 0 (0.0%)
#> policy: keep where .prob > 0.5
#> per-group diagnostics: dropped by subsetting; screen_fits() to refit
#>
#> .keep .prob .rule .reason
#> 1 TRUE 1 sd(2.5, mean, sd) <NA>
#> 2 TRUE 1 sd(2.5, mean, sd) <NA>
#> 3 TRUE 1 sd(2.5, mean, sd) <NA>
#> 4 TRUE 1 sd(2.5, mean, sd) <NA>
#> 5 TRUE 1 sd(2.5, mean, sd) <NA>
#> 6 TRUE 1 sd(2.5, mean, sd) <NA>
# how much each rule would have removed, and how much the removed sets overlap
cmp <- screen_compare(
dat$rt,
list(cutoff = rule_cutoff(0.18, 3), sd = rule_sd(2.5), mad = rule_mad(2.5))
)
cmp
#> <rtprep comparison> 400 trials, 3 rules
#>
#> cutoff dropped 0.0%
#> sd dropped 3.8%
#> mad dropped 10.8%
#>
#> least agreement: cutoff vs mad, 89.2% of decisions (Jaccard 0.00)
Because r_contaminated() carries the ground truth, the screen can be
scored rather than trusted:
table(flagged = !scr$.keep, contaminant = dat$contaminant)
#> contaminant
#> flagged FALSE TRUE
#> FALSE 357 28
#> TRUE 2 13
rtprep implements its screening rules itself and carries no runtime
dependency on other preprocessing packages. Where reference
implementations exist, namely
trimr for the trimming families
and bmm for the mixture EM and EZ
aggregation, the test suite checks equivalence against them.
Manuscript in preparation. Until it appears, cite the package:
citation("rtprep").
GPL (>= 2)