Data & Workflow · Tables, pipelines, and notebooks
phibble
A typed, immutable data frame for PHP, with readr-style input and output.
composer require phpstats/phibble
- Requires
- mbstring extension PDO extension
- Optional
- phit extension (native CSV reading (0.1.1 or later) and chunked CSV reading (0.2.0 or later)) php_polars extension (the Polars backend (PolarsTable)) illuminate/database (the Laravel macros toDataFrame() and phibble())
phibble is a data frame library for PHP that follows R's tibble and readr. A DataFrame holds typed columns, treats null as NA, and never changes in place: every operation returns a new table.
Columns are float, int, bool, string, factor, date, datetime, or list-columns. Types are guessed as a table loads and can be declared per column instead, and casts follow R's as.*() rules. Ordered factors and the forcats functions are included, and printing and glimpse() lay a table out the way tibble does.
Tables load from CSV with readr's options and type guessing, from JSON, from PDO statements, and from Laravel, Eloquent, or Doctrine query builders, so an analysis can begin at the application's own database. phibble depends on neither Laravel nor Doctrine.
select, filter, mutate, arrange, groupBy, summarize, the joins, and tidyr's pivots follow dplyr 1.1 semantics, and Phibble\Expr records each computation as an inspectable, serializable tree. Three backends implement one Table contract: DataFrame in memory, SqlTable compiling the same verbs to SQLite, PostgreSQL, or MySQL, and PolarsTable running them in Polars. All three are part of phibble, and PolarsTable uses the php_polars extension from phpolars. phibble is the table type that phplyr, phit, phiz, and CmdStanPHP read or return.
Example
use function Phibble\Expr\{col, desc, mean, n, sum};
$orders = \Phibble\DataFrame::fromCsv('orders.csv', types: [
'channel' => 'factor',
'coupon' => 'bool',
'returned' => 'bool',
]);
echo $orders
->filter(col('returned')->not())
->mutate(['item_price' => col('total')->div(col('items'))])
->groupBy(['channel'])
->summarize([
'orders' => n(),
'revenue' => sum(col('total')),
'item_price' => mean(col('item_price')),
'coupons' => mean(col('coupon')),
], groups: 'drop')
->arrange([desc(col('revenue'))]);
Output · 3 × 5
channel orders revenue item_price coupons
<fct> <int> <dbl> <dbl> <dbl>
1 web 211 19450. 32.9 0.327
2 store 109 12065. 41.8 0.174
3 phone 65 9024. 51.2 0.0615