Reference
Single-table functions
Rows
filter() keeps rows where every condition is true. Conditions are row closures, vector helpers such as between() or if_any(), or the name of a logical column. NA counts as false. filter_out() drops the rows where every condition is true and keeps rows where a condition is NA, so filter() and filter_out() with the same conditions split a table without overlap.
$schools = DataFrame::fromCsv('schools.csv');
// Several conditions are combined with AND; NA counts as false.
echo $schools |>
filter(fn ($r) => $r->prior_score > 75, between('study_hours', 2, 6)) |>
head(4);
// filter_out() drops the rows where every condition is true.
echo "\n", $schools |> filter_out(fn ($r) => $r->prior_score < 70) |> count();$schools = DataFrame::fromCsv('schools.csv');
// Several conditions are combined with AND; NA counts as false.
echo phplyr($schools)
->filter(fn ($r) => $r->prior_score > 75, between('study_hours', 2, 6))
->head(4);
// filter_out() drops the rows where every condition is true.
echo "\n", phplyr($schools)->filterOut(fn ($r) => $r->prior_score < 70)->count();# A tibble: 0 × 4
# ℹ 4 variables: school <chr>, study_hours <dbl>, prior_score <dbl>,
# score <dbl>
# A tibble: 1 × 1
n
<int>
1 59
In a grouped table, the second closure argument is the row’s group, and vector helpers are evaluated per group:
// Inside a group, the second closure argument is the group's table, and
// vector helpers such as max() are evaluated per group.
echo DataFrame::fromCsv('schools.csv') |>
group_by('school') |>
filter(fn ($r, $g) => $r->score === max('score')($g)) |>
select('school', 'score') |>
head(4);// Inside a group, the second closure argument is the group's table, and
// vector helpers such as max() are evaluated per group.
echo phplyr(DataFrame::fromCsv('schools.csv'))
->groupBy('school')
->filter(fn ($r, $g) => $r->score === max('score')($g))
->select('school', 'score')
->head(4);# A tibble: 4 × 2
# Groups: school [4]
school score
<chr> <dbl>
1 school-01 91.0
2 school-02 92.1
3 school-03 84.0
4 school-04 83.9
arrange() sorts rows. It is stable and puts NA last. Keys are column names, desc(), or expressions, which are evaluated over the whole table. It ignores grouping unless you pass _byGroup: true.
echo DataFrame::fromCsv('housing.csv') |>
arrange('bedrooms', desc(fn ($r) => $r->monthly_rent / $r->area_m2));echo phplyr(DataFrame::fromCsv('housing.csv'))
->arrange('bedrooms', desc(fn ($r) => $r->monthly_rent / $r->area_m2));# A tibble: 8 × 3
area_m2 bedrooms monthly_rent
<dbl> <int> <dbl>
1 45.7 1 1509.
2 37 1 960.
3 49.1 1 1268.
4 57.4 2 1805.
5 66.4 2 2022.
6 74.4 2 1879.
7 93.9 3 2387.
8 92.4 3 2240.
distinct() keeps unique rows over the given columns, or over all columns. Named arguments add computed columns first, and _keepAll: true keeps every column from the first row of each combination.
The slice family picks rows within each group:
| Function | Rows |
|---|---|
slice(1, 3), slice(-1) |
By 1-based position; negative positions drop rows. |
slice_head(n:), slice_tail(n:) |
The first or last rows. |
slice_min(x, n:), slice_max(x, n:) |
The rows with the smallest or largest values, with ties kept unless withTies: false. |
slice_sample(n:, replace:, weightBy:) |
Random rows. Seed with mt_srand(). |
head(n) |
The first rows of the whole table, ignoring groups. |
top_n(n, wt), top_frac(prop, wt) |
Superseded by slice_max(). Rows ranked within n, in table order. |
sample_n(size), sample_frac(size) |
Superseded by slice_sample(). |
add_row(x: 1, _before:, _after:) |
New rows of values; columns not given are NA. |
Each takes prop: in place of n:, and _by:.
$schools = DataFrame::fromCsv('schools.csv') |> group_by('school');
// The two best students in each of the first three schools.
echo $schools |>
slice_max('score', n: 2) |>
filter(fn ($r) => $r->school <= 'school-03') |>
select('school', 'score');$schools = phplyr(DataFrame::fromCsv('schools.csv'))->groupBy('school')->get();
// The two best students in each of the first three schools.
echo phplyr($schools)
->sliceMax('score', n: 2)
->filter(fn ($r) => $r->school <= 'school-03')
->select('school', 'score');# A tibble: 6 × 2
# Groups: school [3]
school score
<chr> <dbl>
1 school-01 91.0
2 school-01 88.2
3 school-02 92.1
4 school-02 88.7
5 school-03 84.0
6 school-03 83.1
Columns
select() keeps columns by name or with tidyselect helpers. An associative array renames while selecting. rename() renames with new: 'old' arguments, rename_with() applies a function to selected names, and relocate() moves columns to the front or _before / _after another column.
$schools = DataFrame::fromCsv('schools.csv');
echo implode(', ', ($schools |> select('school', ends_with('score')))->columns()), "\n";
echo implode(', ', ($schools |> select(except('school')))->columns()), "\n";
echo implode(', ', ($schools |> select(where('numeric')))->columns()), "\n";
echo implode(', ', ($schools |> select(['hours' => 'study_hours'], everything()))->columns()), "\n";
echo implode(', ', ($schools |> relocate('score', _before: 'study_hours'))->columns()), "\n";
echo implode(', ', ($schools |> rename_with(strtoupper(...), contains('score')))->columns()), "\n";$schools = DataFrame::fromCsv('schools.csv');
echo implode(', ', (phplyr($schools)->select('school', ends_with('score'))->get())->columns()), "\n";
echo implode(', ', (phplyr($schools)->select(except('school'))->get())->columns()), "\n";
echo implode(', ', (phplyr($schools)->select(where('numeric'))->get())->columns()), "\n";
echo implode(', ', (phplyr($schools)->select(['hours' => 'study_hours'], everything())->get())->columns()), "\n";
echo implode(', ', (phplyr($schools)->relocate('score', _before: 'study_hours')->get())->columns()), "\n";
echo implode(', ', (phplyr($schools)->renameWith(strtoupper(...), contains('score'))->get())->columns()), "\n";school, prior_score, score
study_hours, prior_score, score
study_hours, prior_score, score
hours, school, prior_score, score
school, score, study_hours, prior_score
school, study_hours, PRIOR_SCORE, SCORE
mutate() adds or replaces columns. A value can be a row closure, a vector helper, a list with one value per row, a scalar, or null to drop the column. Later arguments see the columns made by earlier ones.
echo DataFrame::fromCsv('housing.csv') |>
mutate(
rent_per_m2: fn ($r) => round($r->monthly_rent / $r->area_m2, 2),
size: if_else(fn ($r) => $r->area_m2 >= 60, 'large', 'small'),
rank: min_rank(desc('monthly_rent')),
) |>
arrange('rank');echo phplyr(DataFrame::fromCsv('housing.csv'))
->mutate(
rent_per_m2: fn ($r) => round($r->monthly_rent / $r->area_m2, 2),
size: if_else(fn ($r) => $r->area_m2 >= 60, 'large', 'small'),
rank: min_rank(desc('monthly_rent')),
)
->arrange('rank');# A tibble: 8 × 6
area_m2 bedrooms monthly_rent rent_per_m2 size rank
<dbl> <int> <dbl> <dbl> <chr> <int>
1 93.9 3 2387. 25.4 large 1
2 92.4 3 2240. 24.2 large 2
3 66.4 2 2022. 30.5 large 3
4 74.4 2 1879. 25.3 large 4
5 57.4 2 1805. 31.4 small 5
6 45.7 1 1509. 33.0 small 6
7 49.1 1 1268. 25.8 small 7
8 37 1 960. 25.9 small 8
mutate() takes dplyr’s options: _by groups for one call, _keep: 'none' keeps only the grouping and new columns, and _before or _after place the new columns. transmute() is mutate() that keeps only the new columns.
$housing = DataFrame::fromCsv('housing.csv');
// _keep: 'none' keeps only new columns (and grouping columns).
echo $housing |> mutate(rent_k: fn ($r) => $r->monthly_rent / 1000, _keep: 'none') |> head(3);
// _by groups for this call only; mean() is recycled within each group.
echo "\n", $housing |>
mutate(vs_bedroom_avg: fn ($r, $g) => $r->monthly_rent - mean('monthly_rent')($g), _by: 'bedrooms', _after: 'bedrooms') |>
head(4);$housing = DataFrame::fromCsv('housing.csv');
// _keep: 'none' keeps only new columns (and grouping columns).
echo phplyr($housing)->mutate(rent_k: fn ($r) => $r->monthly_rent / 1000, _keep: 'none')->head(3);
// _by groups for this call only; mean() is recycled within each group.
echo "\n", phplyr($housing)
->mutate(vs_bedroom_avg: fn ($r, $g) => $r->monthly_rent - mean('monthly_rent')($g), _by: 'bedrooms', _after: 'bedrooms')
->head(4);# A tibble: 3 × 1
rent_k
<dbl>
1 1.88
2 1.81
3 2.02
# A tibble: 4 × 4
area_m2 bedrooms vs_bedroom_avg monthly_rent
<dbl> <int> <dbl> <dbl>
1 74.4 2 -22.9 1879.
2 57.4 2 -97.2 1805.
3 66.4 2 120. 2022.
4 49.1 1 22.3 1268.
pull() returns one column as a PHP list. It takes a name or a position (1-based, negative from the end, the last column by default) and an optional name: column whose values become the keys.
$housing = DataFrame::fromCsv('housing.csv');
echo $housing |> distinct('bedrooms') |> arrange('bedrooms');
echo "\n", json_encode($housing |> arrange('area_m2') |> pull('monthly_rent')), "\n";
echo json_encode($housing |> pull(-1)), "\n";$housing = DataFrame::fromCsv('housing.csv');
echo phplyr($housing)->distinct('bedrooms')->arrange('bedrooms');
echo "\n", json_encode(phplyr($housing)->arrange('area_m2')->pull('monthly_rent')), "\n";
echo json_encode(phplyr($housing)->pull(-1)), "\n";# A tibble: 3 × 1
bedrooms
<int>
1 1
2 2
3 3
[959.51,1509.24,1267.85,1805.03,2022.29,1879.25,2240.1,2386.54]
[1879.25,1805.03,2022.29,1267.85,1509.24,2240.1,959.51,2386.54]
Summaries
summarize() returns one row per group. summarise() is the same function. Each named argument is a summary helper or a closure that receives the group’s table and returns one value. With several grouping columns, the result drops the last one, as in dplyr. _groups accepts 'drop_last', 'drop', 'keep', and 'rowwise'.
echo DataFrame::fromCsv('schools.csv') |>
summarize(
students: n(),
schools: n_distinct('school'),
median_hours: median('study_hours'),
q90_score: quantile('score', 0.9),
iqr_score: iqr('score'),
top_school: fn ($g) => ($g |> slice_max('score', n: 1) |> pull('school'))[0],
);echo phplyr(DataFrame::fromCsv('schools.csv'))
->summarize(
students: n(),
schools: n_distinct('school'),
median_hours: median('study_hours'),
q90_score: quantile('score', 0.9),
iqr_score: iqr('score'),
top_school: fn ($g) => (phplyr($g)->sliceMax('score', n: 1)->pull('school'))[0],
);# A tibble: 1 × 6
students schools median_hours q90_score iqr_score top_school
<int> <int> <dbl> <dbl> <dbl> <chr>
1 250 12 6.66 86.1 15.3 school-06
reframe() is summarize() for summaries that return several values. Results are recycled to a common length within each group, and the output is ungrouped.
echo DataFrame::fromCsv('schools.csv') |>
filter(fn ($r) => $r->school <= 'school-02') |>
group_by('school') |>
reframe(
p: [0.25, 0.5, 0.75],
score: fn ($g) => array_map(fn ($p) => Stats::quantile($g->column('score'), $p), [0.25, 0.5, 0.75]),
);echo phplyr(DataFrame::fromCsv('schools.csv'))
->filter(fn ($r) => $r->school <= 'school-02')
->groupBy('school')
->reframe(
p: [0.25, 0.5, 0.75],
score: fn ($g) => array_map(fn ($p) => Stats::quantile($g->column('score'), $p), [0.25, 0.5, 0.75]),
);# A tibble: 6 × 3
school p score
<chr> <dbl> <dbl>
1 school-01 0.25 67.6
2 school-01 0.5 72.5
3 school-01 0.75 76.9
4 school-02 0.25 65.3
5 school-02 0.5 70.3
6 school-02 0.75 81.0
count() counts rows per combination of columns within any existing groups and keeps the original grouping. wt: sums a column instead, sort: true orders by the count, and name: names the count column. tally() counts within the current groups, and add_count() and add_tally() add the count as a column.
$schools = DataFrame::fromCsv('schools.csv') |>
mutate(band: case_when(
[fn ($r) => $r->study_hours < 4, 'light'],
[fn ($r) => $r->study_hours < 8, 'moderate'],
default: 'heavy',
));
echo $schools |> count('band', sort: true);
echo "\n", $schools |> count('band', wt: 'study_hours', name: 'hours');$schools = phplyr(DataFrame::fromCsv('schools.csv'))
->mutate(band: case_when(
[fn ($r) => $r->study_hours < 4, 'light'],
[fn ($r) => $r->study_hours < 8, 'moderate'],
default: 'heavy',
))
->get();
echo phplyr($schools)->count('band', sort: true);
echo "\n", phplyr($schools)->count('band', wt: 'study_hours', name: 'hours');# A tibble: 3 × 2
band n
<chr> <int>
1 heavy 95
2 moderate 90
3 light 65
# A tibble: 3 × 2
band hours
<chr> <dbl>
1 heavy 953.
2 light 159.
3 moderate 540.
glimpse() prints a compact summary of each column and returns the table unchanged.
From phpstats/phplyr@d319dc5.