phplyr

Reference

Vector helpers

Vector helpers are evaluated once per group on whole columns. Use them as mutate(), filter(), and summarize() arguments. They follow R’s NA rules: a summary returns NA when its input has an NA unless naRm: true, ranks keep NA, and cumulative functions stay NA after the first NA.

The first argument is the input column. A string there is a column name. It can also be col('x'), another helper, or a row closure. Later arguments are values. ->map(fn ($value, $r) => ...) transforms a helper’s result row by row.

Summaries

Helper Value
n() Rows in the group
n_distinct('x', 'y') Distinct values or combinations
sum, mean, median As in R
sd, var_ (alias variance) Sample statistics (n − 1)
quantile('x', 0.9), iqr('x') Type 7 quantiles, R’s default
min, max Smallest and largest values
first, last, nth('x', 2) Values by position, with default:
any, all Logical summaries with NA logic

With the phit extension loaded, mean, sd, var_, median, and quantile use it. Without it, they run in PHP. The test suite checks both against R.

Window functions

Helper Value
lag('x', n:, default:, orderBy:), lead(...) Shifted values
row_number(), row_number('x') Row numbers, or ranks with ties broken by position
min_rank, dense_rank Ranks; wrap the column in desc() to reverse
percent_rank, cume_dist Ranks scaled to [0, 1]
ntile('x', 4) Buckets of near-equal size
cumsum, cumprod, cummin, cummax, cummean Running values
cumall, cumany Running logical values
consecutive_id('x') A new id each time the value changes
order_by('t', cumsum('x')) A window function evaluated in another order
echo DataFrame::fromCsv('schools.csv') |>
    filter(fn ($r) => $r->school === 'school-01') |>
    arrange('study_hours') |>
    mutate(
        prev: lag('score'),
        change: lag('score')->map(fn ($prev, $r) => $prev === null ? null : round($r->score - $prev, 2)),
        running_mean: cummean('score'),
        rank: min_rank(desc('score')),
        pct: percent_rank('score'),
        quartile: ntile('score', 4),
    ) |>
    select('study_hours', 'score', 'prev', 'change', 'running_mean', 'rank', 'pct', 'quartile') |>
    head(8);
echo phplyr(DataFrame::fromCsv('schools.csv'))
    ->filter(fn ($r) => $r->school === 'school-01')
    ->arrange('study_hours')
    ->mutate(
        prev: lag('score'),
        change: lag('score')->map(fn ($prev, $r) => $prev === null ? null : round($r->score - $prev, 2)),
        running_mean: cummean('score'),
        rank: min_rank(desc('score')),
        pct: percent_rank('score'),
        quartile: ntile('score', 4),
    )
    ->select('study_hours', 'score', 'prev', 'change', 'running_mean', 'rank', 'pct', 'quartile')
    ->head(8);
# A tibble: 8 × 8
  study_hours score  prev change running_mean  rank    pct quartile
        <dbl> <dbl> <dbl>  <dbl>        <dbl> <int>  <dbl>    <int>
1        1.31  63.7  NA    NA            63.7    15 0.176         1
2        2.7   67.2  63.7   3.54         65.5    14 0.235         1
3        2.74  60.4  67.2  -6.89         63.8    17 0.0588        1
4        3.71  68.5  60.4   8.19         65.0    13 0.294         2
5        4.38  60.1  68.5  -8.41         64.0    18 0             1
6        4.89  72.8  60.1  12.6          65.5     9 0.529         2
7        5.21  69.7  72.8  -3.09         66.1    12 0.353         2
8        5.51  72.1  69.7   2.48         66.8    10 0.471         2

Conditionals and NA

Helper Use
if_else(cond, yes, no, missing:) Vectorized if/else
case_when([cond, value], ..., default:) The first true condition wins
replace_when('x', [cond, value], ...) case_when() that keeps x where nothing matches
recode_values('x', [from, to], ..., default:) Map values; also from: / to: lookup lists
replace_values('x', [from, to], ...) recode_values() that keeps unmatched values
case_match('x', [from, to], ..., default:) dplyr’s deprecated form of recode_values()
recode('x', a: 'A', _default:, _missing:) dplyr’s superseded recoder, matching values by name
when_any(...), when_all(...) Elementwise OR and AND across conditions
coalesce('x', 0) The first non-NA value
na_if('x', value) NA where x equals the value
between('x', lo, hi) Inclusive range test
near('x', y) Equality within floating-point tolerance
echo DataFrame::fromRows([
    ['state' => 'NC', 'visits' => 3],
    ['state' => 'NYC', 'visits' => null],
    ['state' => 'CA', 'visits' => 12],
    ['state' => null, 'visits' => 0],
]) |>
    mutate(
        name: recode_values('state', ['NC', 'North Carolina'], [['NY', 'NYC'], 'New York'], default: 'other'),
        visits: coalesce('visits', 0),
        busy: if_else(fn ($r) => $r->visits > 5, 'yes', 'no'),
        capped: replace_when('visits', [fn ($r) => $r->visits > 10, 10]),
        any_flag: when_any(fn ($r) => $r->state === 'CA', fn ($r) => $r->visits === 0),
    );
echo phplyr(DataFrame::fromRows([
    ['state' => 'NC', 'visits' => 3],
    ['state' => 'NYC', 'visits' => null],
    ['state' => 'CA', 'visits' => 12],
    ['state' => null, 'visits' => 0],
]))
    ->mutate(
        name: recode_values('state', ['NC', 'North Carolina'], [['NY', 'NYC'], 'New York'], default: 'other'),
        visits: coalesce('visits', 0),
        busy: if_else(fn ($r) => $r->visits > 5, 'yes', 'no'),
        capped: replace_when('visits', [fn ($r) => $r->visits > 10, 10]),
        any_flag: when_any(fn ($r) => $r->state === 'CA', fn ($r) => $r->visits === 0),
    );
# A tibble: 4 × 6
  state visits name           busy  capped any_flag
  <chr>  <int> <chr>          <chr>  <int> <lgl>   
1 NC         3 North Carolina no         3 FALSE   
2 NYC        0 New York       no         0 TRUE    
3 CA        12 other          yes       10 TRUE    
4 <NA>       0 other          no         0 TRUE

From phpstats/phplyr@d319dc5.