Guide
Getting started
Installing
phpolars runs on PHP 8.3, 8.4, and 8.5, non-thread-safe (NTS) builds, on Linux and macOS. Install it with PIE, PHP’s extension installer:
pie install phpstats/phpolarsOn Linux on x86_64 and arm64 with glibc 2.34 or later, PIE downloads a prebuilt binary. On other platforms, including macOS, it builds the extension from source, which needs a stable Rust toolchain and libclang and takes several minutes. PIE then enables the extension in php.ini. Under PHP-FPM, restart FPM afterwards so that its workers load the extension. Deployment covers servers, Laravel Forge, Laravel Herd, and manual installs.
Checking the installation
php -r 'echo phpversion("php_polars"), PHP_EOL;'The command prints the installed version, such as 0.2.1. It checks the CLI. PHP-FPM reads its own php.ini, so check a web request separately, for example with extension_loaded('php_polars') in a page.
Imports
Classes live in Phpolars, and functions are namespaced, so import them:
use Phpolars\{DataFrame, Series};
use function Phpolars\{col, lit, when, read_csv};A first query
$sales = read_csv('sales.csv');
echo $sales
->select(
'id',
'product',
col('units')->fillNull(0),
['revenue' => col('units')->mul(col('price'))->round(2)],
when(col('price')->ge(5))->then(lit('premium'))->otherwise(lit('standard'))->alias('tier'),
)
->head(5);shape: (5, 5)
┌─────┬─────────┬───────┬─────────┬──────────┐
│ id ┆ product ┆ units ┆ revenue ┆ tier │
│ --- ┆ --- ┆ --- ┆ --- ┆ --- │
│ i64 ┆ str ┆ i64 ┆ f64 ┆ str │
╞═════╪═════════╪═══════╪═════════╪══════════╡
│ 1 ┆ banana ┆ 10 ┆ 70.3 ┆ premium │
│ 2 ┆ cherry ┆ 0 ┆ 0.0 ┆ standard │
│ 3 ┆ apple ┆ 2 ┆ 7.68 ┆ standard │
│ 4 ┆ apple ┆ 12 ┆ 88.08 ┆ premium │
│ 5 ┆ apple ┆ 1 ┆ 6.05 ┆ premium │
└─────┴─────────┴───────┴─────────┴──────────┘
Strings passed where py-polars expects an expression input, such as select(), name columns. Use lit() for a string value there. In comparisons and arithmetic, strings are literals: col('store')->ne('S1').
Filtering
$sales = read_csv('sales.csv');
// Several predicates are combined with and; null counts as false.
echo $sales->filter(
col('product')->isIn(['apple', 'cherry']),
col('units')->isBetween(5, 8),
col('store')->ne('S1'),
)->head(5);shape: (5, 6)
┌─────┬────────────┬───────┬─────────┬───────┬───────┐
│ id ┆ date ┆ store ┆ product ┆ units ┆ price │
│ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │
│ i64 ┆ str ┆ str ┆ str ┆ i64 ┆ f64 │
╞═════╪════════════╪═══════╪═════════╪═══════╪═══════╡
│ 12 ┆ 2024-03-17 ┆ S2 ┆ apple ┆ 8 ┆ 3.85 │
│ 16 ┆ 2024-02-24 ┆ S3 ┆ apple ┆ 8 ┆ 5.07 │
│ 20 ┆ 2024-03-26 ┆ S4 ┆ cherry ┆ 7 ┆ 5.72 │
│ 22 ┆ 2024-03-05 ┆ S2 ┆ apple ┆ 5 ┆ 8.23 │
│ 24 ┆ 2024-03-26 ┆ S3 ┆ cherry ┆ 6 ┆ 2.2 │
└─────┴────────────┴───────┴─────────┴───────┴───────┘
Grouping
$sales = read_csv('sales.csv');
echo $sales->groupBy(['store', 'product'], maintainOrder: true)
->agg(
len()->alias('rows'),
col('units')->sum(),
col('units')->nullCount()->alias('missing_units'),
col('price')->max()->alias('top_price'),
)
->sort(['store', 'product'])
->head(6), "\n";
// Shorthands aggregate every non-key column.
echo $sales->select('store', 'units', 'price')->groupBy('store')->mean()->sort('store');shape: (6, 6)
┌───────┬─────────┬──────┬───────┬───────────────┬───────────┐
│ store ┆ product ┆ rows ┆ units ┆ missing_units ┆ top_price │
│ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │
│ str ┆ str ┆ u32 ┆ i64 ┆ u32 ┆ f64 │
╞═══════╪═════════╪══════╪═══════╪═══════════════╪═══════════╡
│ S1 ┆ apple ┆ 17 ┆ 98 ┆ 0 ┆ 7.23 │
│ S1 ┆ banana ┆ 15 ┆ 81 ┆ 0 ┆ 8.98 │
│ S1 ┆ cherry ┆ 12 ┆ 90 ┆ 0 ┆ 8.07 │
│ S1 ┆ date ┆ 13 ┆ 109 ┆ 0 ┆ 8.57 │
│ S2 ┆ apple ┆ 11 ┆ 47 ┆ 0 ┆ 8.23 │
│ S2 ┆ banana ┆ 14 ┆ 69 ┆ 1 ┆ 6.92 │
└───────┴─────────┴──────┴───────┴───────────────┴───────────┘
shape: (4, 3)
┌───────┬──────────┬──────────┐
│ store ┆ units ┆ price │
│ --- ┆ --- ┆ --- │
│ str ┆ f64 ┆ f64 │
╞═══════╪══════════╪══════════╡
│ S1 ┆ 6.631579 ┆ 4.010702 │
│ S2 ┆ 5.282609 ┆ 4.237843 │
│ S3 ┆ 6.282609 ┆ 4.877347 │
│ S4 ┆ 4.883721 ┆ 4.074186 │
└───────┴──────────┴──────────┘
groupBy() follows py-polars and does not keep group order unless maintainOrder: true is passed.
Lazy queries
A DataFrame runs each method immediately. A LazyFrame records operations and runs them, optimized, when collect() is called. scan_csv() starts a lazy query from a file, so Polars reads only the columns and rows that the query needs:
$query = scan_csv('sales.csv')
->filter(col('units')->gt(5))
->groupBy('store')
->agg(col('units')->sum())
->sort('store');
echo $query->explain(), "\n\n";
echo $query->collect();SORT BY [col("store")]
AGGREGATE[maintain_order: false]
[col("units").sum()] BY [col("store")]
FROM
Csv SCAN [sales.csv]
PROJECT 2/6 COLUMNS
SELECTION: col("units") > 5
ESTIMATED ROWS: 201
shape: (4, 2)
┌───────┬───────┐
│ store ┆ units │
│ --- ┆ --- │
│ str ┆ i64 │
╞═══════╪═══════╡
│ S1 ┆ 335 │
│ S2 ┆ 199 │
│ S3 ┆ 230 │
│ S4 ┆ 136 │
└───────┴───────┘
Missing values
phpolars follows Polars: a missing value is null, aggregates such as sum() and mean() skip it, and null sorts first. phibble and phplyr follow R and dplyr instead. phibble and phplyr compares the two.