phibble

Data & Workflow · Tables, pipelines, and notebooks

phibble

A typed, immutable data frame for PHP, with readr-style input and output.

Install
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

Run it live

All vignettes →