The hardware and bandwidth for this mirror is donated by METANET, the Webhosting and Full Service-Cloud Provider.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]metanet.ch.

Getting started with statim

Introduction

The conventional R idiom for statistical inference is:

<<statistical-function>>(<formula>, <data>)

It is concise for a single test, but it does not compose. Each function is its own island: a different name, different argument conventions, no shared way to express what hypothesis you are testing or to switch between estimation methods without rewriting the call entirely.

{statim} keeps that familiar idiom as its entry point, while bringing declarative, pipe-friendly grammar (much in spirit of {ggplot2} or {dplyr}). The same inference pipeline can express a classical test or a permutation test with nothing else touching.

A complete example

Before explaining each piece, here is what a {statim} pipeline looks like end to end:

  1. t-test

    sleep |>
        define_model(extra %by% group) |>
        prepare(T_TEST, .ci = 0.9) |> 
        conclude() |>
        tidy()
    #> # A tibble: 1 × 7
    #>   group estimate t_stat    df  p_val lower_90 upper_90
    #>   <chr>    <dbl>  <dbl> <dbl>  <dbl>    <dbl>    <dbl>
    #> 1 group    -1.58  -1.86  17.8 0.0794    -3.05   -0.107
  2. Linear regression

    mtcars |>
        define_model(mpg ~ .) |>
        prepare(LINEAR_REG) |>
        conclude() |>
        tidy()
    #> # A tibble: 11 × 5
    #>    term        estimate std_error statistic p_value
    #>    <chr>          <dbl>     <dbl>     <dbl>   <dbl>
    #>  1 (Intercept)  12.3      18.7        0.657  0.518 
    #>  2 cyl          -0.111     1.05      -0.107  0.916 
    #>  3 disp          0.0133    0.0179     0.747  0.463 
    #>  4 hp           -0.0215    0.0218    -0.987  0.335 
    #>  5 drat          0.787     1.64       0.481  0.635 
    #>  6 wt           -3.72      1.89      -1.96   0.0633
    #>  7 qsec          0.821     0.731      1.12   0.274 
    #>  8 vs            0.318     2.10       0.151  0.881 
    #>  9 am            2.52      2.06       1.23   0.234 
    #> 10 gear          0.655     1.49       0.439  0.665 
    #> 11 carb         -0.199     0.829     -0.241  0.812

Each step has one job. The pipeline reads like a sentence: define the model, choose the test, execute and read the output.

General Workflow

All you need to know is that the most usual usage of {statim} comes with three steps. Here’s the general anatomy of the main {statim} semantics:

# Data can be piped in or passed as argument to `define_model()`
... |>                                   # Possible extensions  
    define_model(
        <var_id>(var1, var2, ...), 
        data, ...
    ) |>                                 # 1. Model definition
    # ... |>                             # Possible extensions  
    prepare(<STAT_FN>) |>                # 2. Prepare method (lazy)
    via(...) |>                          # Optional: method variant  (*)
    state_null(<expr>) |>                # Optional: null hypothesis (*)
    # ... |>                             # Possible extensions  
    conclude() |>                        # 3. Execute
    <output_handler>()                   # e.g. tidy(), display()    

i. Layout Model Definition

Every pipeline starts with define_model(), which binds a variable description to data. The variable description is a <var_id> object — {statim}’s equivalent of ggplot2::aes(). It captures bare variable names lazily, exactly as ~ does, but resolves them in a consistent, pipeline-aware way.

The two most common <var_id> objects are:

define_model(sleep, extra %by% group)
define_model(mtcars, mpg ~ .)          # standard formula also accepted

For a full account of all built-in <var_id> objects and how to write your own, see the <var_id> objects article.

ii. Choose the test

After the model is defined, prepare() attaches a statistical method to the pipeline. Nothing is executed yet — the pipeline is lazy until conclude() is called.

sleep |>
    define_model(extra %by% group) |>
    prepare(T_TEST) |>
    # update() is optional
    update(.ci = 0.9)                  

prepare() accepts any STAT_FNT_TEST, LINEAR_REG, COR_TEST, P_TEST, and so on. If you prefer to be explicit, prepare_test() and prepare_model() are available as typed alternatives.

iii. Execute and read the output

conclude() runs the pipeline. Chain tidy() after it to get a tibble, or print() the object directly for the formatted summary.

out = conclude(sleep_tt)
print(out)
tidy(out)

Eager form

For a quick one-shot inference, every test exposes an eager form that collapses the pipeline into a single call:

T_TEST(extra %by% group, sleep, .ci = 0.9)
#> -- Summary ---------------------------------------------------------------------
#> 
#> ──────────────────────────────────────────
#>   group  estimate  t_stat    df    p_val  
#> ──────────────────────────────────────────
#>   group   -1.580   -1.861  17.780  0.079  
#> ──────────────────────────────────────────
#> 
#> 
#> -- Confidence Interval ---------------------------------------------------------
#> 
#> ─────────────────────────────
#>   group  lower_90  upper_90  
#> ─────────────────────────────
#>   group   -3.053    -0.107   
#> ─────────────────────────────
LINEAR_REG(mpg ~ ., mtcars)
#> -- Coefficients ----------------------------------------------------------------
#> 
#> ──────────────┬───────────────────────────────────────────
#>   term        │  estimate  std_error  statistic  p_value  
#> ──────────────┼───────────────────────────────────────────
#>   (Intercept) │   12.303    18.718      0.657     0.518   
#>   cyl         │   -0.111     1.045     -0.107     0.916   
#>   disp        │   0.013      0.018      0.747     0.463   
#>   hp          │   -0.021     0.022     -0.987     0.335   
#>   drat        │   0.787      1.635      0.481     0.635   
#>   wt          │   -3.715     1.894     -1.961     0.063   
#>   qsec        │   0.821      0.731      1.123     0.274   
#>   vs          │   0.318      2.105      0.151     0.881   
#>   am          │   2.520      2.057      1.225     0.234   
#>   gear        │   0.655      1.493      0.439     0.665   
#>   carb        │   -0.199     0.829     -0.241     0.812   
#> ──────────────┴───────────────────────────────────────────
#> 
#> 
#> -- Model Fit -------------------------------------------------------------------
#> 
#>              ------------------------------------------------------
#>                R Squared      :    0.87    F-statistic :    13.93
#>                Adj. R Squared :    0.81    df1         :       10
#>                Sigma          :    2.65    df2         :       21
#>                n              :      32    p-value     :   <0.001
#>                df (residual)  :      21                :         
#>              ------------------------------------------------------

The eager form is equivalent to the full pipeline for simple cases. Use the pipeline when you need via(), state_null(), or tidy().

Switching the estimation method

via() recalibrates the estimation method in a lazy pipeline. The model definition and test choice stay the same — only the method changes. Switching from a classical t-test to a permutation test is a single added line:

# Classical
sleep |>
    define_model(x_by(extra, group)) |>
    prepare_test(T_TEST) |>
    conclude()
#> 
#> == Model ======================================================================= 
#> 
#> Variable Mapper : x_by 
#> Args : extra | group 
#>     x_vars : 1 
#>     by_vars : 1 
#> 
#> == T-Test ====================================================================== 
#> 
#> -- Summary ---------------------------------------------------------------------
#> 
#> ──────────────────────────────────────────
#>   group  estimate  t_stat    df    p_val  
#> ──────────────────────────────────────────
#>   group   -1.580   -1.861  17.780  0.079  
#> ──────────────────────────────────────────
#> 
#> 
#> -- Confidence Interval ---------------------------------------------------------
#> 
#> ─────────────────────────────
#>   group  lower_95  upper_95  
#> ─────────────────────────────
#>   group   -3.365    0.206    
#> ─────────────────────────────

# Permutation: one line added
sleep |>
    define_model(x_by(extra, group)) |>
    prepare_test(T_TEST) |>
    via("permute", n = 999L) |>
    conclude()
#> 
#> == Model ======================================================================= 
#> 
#> Variable Mapper : x_by 
#> Args : extra | group 
#>     x_vars : 1 
#>     by_vars : 1 
#> 
#> == T-Test · permute ============================================================ 
#> 
#> ============================== T-test Permutation ==============================
#> 
#> 
#> -- Summary ---------------------------------------------------------------------
#> 
#> ───────────────────────────────
#>   Statistic  p-value  n_perms  
#> ───────────────────────────────
#>    -1.580     0.087     999    
#> ───────────────────────────────

This is the grammar argument in concrete form. The inferential intent does not change; only the machinery does.

Hypothesis expressions

The conventional approach in R encodes a hypothesis as a string flag: alternative = "greater". That tells you a direction, but not what parameter is being constrained or against what value. You cannot read alternative = "greater" and know whether the claim is about a mean, a proportion, or a correlation without reading the surrounding context.

{statim} provides an explicit hypothesis DSL built from <param_obj> objects and standard R comparison operators, in a form of algebraic expression. The expression names the population parameter, the relational operator, and the hypothesized value — the same three components in any textbook null hypothesis statement.

The supported operators are the built-in operators in R itself: ==, !=, <, >, <=, >=. Because state_null() captures the expression unevaluated, these operators are never executed as comparisons — they are just AST nodes that split the left and right sides of the hypothesis. Consider writing MU(extra, group == "1") >= MU(extra, group == "2") inside state_null(). This is a valid expression, and then the >= operator is automatically parsed as a value equivalent to null = "greater" / alternative = "less".

Here are the current built-in <param_obj> objects:

As an example, consider the built-in sleep dataset. It records the extra hours of sleep (extra) gained by 10 patients under each of two drugs (group). A researcher wants to know whether drug 1 produces more additional sleep than drug 2 on average.

The null hypothesis is that drug 1 is at least as effective — that is, the mean extra sleep under drug 1 is greater than or equal to that under drug 2:

\[\mu_{x|\text{group}=1} \geq \mu_{x|\text{group}=2}\]

sleep |>
    define_model(extra %by% group) |>
    prepare_test(T_TEST) |>
    state_null(
        MU(extra, group == "1") >= MU(extra, group == "2")
    ) |>
    conclude()

== Model ======================================================================= 

Variable Mapper : x_by 
Args : extra | group 
    x_vars : 1 
    by_vars : 1 

== T-Test ====================================================================== 

-- Summary ---------------------------------------------------------------------

──────────────────────────────────────────
  group  estimate  t_stat    df    p_val  
──────────────────────────────────────────
  group   -1.580   -1.861  17.780  0.040  
──────────────────────────────────────────


-- Confidence Interval ---------------------------------------------------------

─────────────────────────────
  group  lower_95  upper_95  
─────────────────────────────
  group    -Inf     -0.107   
─────────────────────────────

Any linear combination of parameters is valid on either side. For a full reference on supported operators and <param_obj> objects, see What are {statim}’s Null Hypothesis Expressions?

Conclusion

{statim} is made for few reasons:

  1. Explicitness exists and is enforced, because subjectively it makes the codes readable and maintainable. This is why the expression of null hypothesis inside state_null() is expressed as an algebraic expression. Strictness on how you type the code, as well.

  2. Assuming that you are satisfied with its API design. Don’t worry, this package makes sure it is pretty expandable and versatile. The use of S7 is only used on the surface level — formal classes/objects, type annotations, and a bit of method dispatch. See “Writing statim extensions” to learn more.

  3. {ggplot2} is primarily the inspiration behind this package, just with less layer-by-layer semantics. {dplyr} comes to the second: verbs and pipes. You see functions like rel()? They are called <var_id> mappers, and they are literally aes() albeit it does the bare minimum: they are mappers that declares the layout of the statistical inference. For instance, when rel() is used, you basically tell {statim} to analyze the relationship between x and y, then tell {statim} what type of estimation you want to perform, and then retrieve the result you want to achieve.

  4. Built-in implementations are made from the common tasks R users do every time they use R. For example, you want to perform t-test with several grouping variables, and with ... |> via("multi") from the x_by() layout, you are able to perform t-test across multiple grouping variables at once, since x_by() allows to have multiple variables for each side, x and group, to be analyzed.

  5. This package is relatively easy to use and interactive, though has a bit steeper learning curve when compared to the existing packages like {rstatix}. This is due to <var_id> mappers which changes the behavior of how the statistical inference in {statim} is done and S7 exist, and has tons of supporting documentations you can read. No need to read all of the contents unless you are planning to contribute. The semantics of this package is pretty similar to what you are gotten used to from the existing packages, namely the {tidyverse} itself.

Learn More

If you want to learn more on how to use this package, visit the following:

  1. Hypothesis Testing with {statim}
  2. Regression Modelling with {statim}
  3. ANOVA models from Linear Models with {statim}

These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.