Dan::Polars
WORK IN PROGRESS
THIS MODULE IS EXPERIMENTAL AND SUBJECT TO CHANGE WITHOUT NOTICE
raku Dan::Polars - WIP
This new module binds Raku Dan to Polars via Raku NativeCall / Rust FFI.
The following broad capabilities are included:
Polars structures (Series & DataFrames)
Polars lazy queries & expressions
raku Dan features (accessors, dtypes, base methods)
broad datatype support
concurrency
The aim is to emulate the examples in the Polars User Guide
Installation
Based on the Dockerfile chain (1) FROM (2)
docker run -it p6steve/raku-dan:polars
cd ~/raku-Dan-Polars/bin
(this repo was cloned to load test_data)./synopsis-dan-polars.raku
or./nutshell.raku
(see bin/setup.raku for manual / development install steps)
You are welcome to plunder the Dockerfiles for how to set up your own environment.
Nutshell
use Dan;
use Dan::Polars;
sub starwars {
my \sw = DataFrame.new;
sw.read_csv("test_data/dfStarwars.csv");
sw
}
my $obj = starwars;
$obj .= select( [ <species mass height>>>.&col ] ) ;
$obj .= groupby([ <species> ]) ;
$obj .= sort( {$obj[$++]<species>, $obj[$++]<mass>} )[*].reverse^;
$obj.show;
shape: (87, 3)
โโโโโโโโโโโโโโโโโโฌโโโโโโโฌโโโโโโโโโ
โ species โ mass โ height โ
โ --- โ --- โ --- โ
โ str โ str โ str โ
โโโโโโโโโโโโโโโโโโชโโโโโโโชโโโโโโโโโก
โ Zabrak โ NA โ 171 โ
โโโโโโโโโโโโโโโโโโผโโโโโโโผโโโโโโโโโค
โ Zabrak โ 80 โ 175 โ
โโโโโโโโโโโโโโโโโโผโโโโโโโผโโโโโโโโโค
โ Yoda's species โ 17 โ 66 โ
โโโโโโโโโโโโโโโโโโผโโโโโโโผโโโโโโโโโค
โ Xexto โ NA โ 122 โ
โโโโโโโโโโโโโโโโโโผโโโโโโโผโโโโโโโโโค
โ ... โ ... โ ... โ
โโโโโโโโโโโโโโโโโโผโโโโโโโผโโโโโโโโโค
โ Chagrian โ NA โ 196 โ
โโโโโโโโโโโโโโโโโโผโโโโโโโผโโโโโโโโโค
โ Cerean โ 82 โ 198 โ
โโโโโโโโโโโโโโโโโโผโโโโโโโผโโโโโโโโโค
โ Besalisk โ 102 โ 198 โ
โโโโโโโโโโโโโโโโโโผโโโโโโโผโโโโโโโโโค
โ Aleena โ 15 โ 79 โ
โโโโโโโโโโโโโโโโโโดโโโโโโโดโโโโโโโโโ
#say ~$obj.Dan-DataFrame; # coerce to a vanilla Dan::DataFrame (e.g. to say all rows)
#say ~$obj.to-dataset; # or export to a dataset for ad hoc data munging
datasets are used by raku Data::... modules
Synopsis
Dan::Polars is a specialization of raku Dan. Checkout the Dan synopsis for base Series and DataFrame operations. The following covers the additional features that are specific to Dan::Polars.
Each Dan::Polars object (Series or DataFrame) contains a pointer to its Rust Polars "shadow".
Polars does not implement indexes, so any attempt to set a row index will be ignored.
Dan::Polars only exposes the Polars lazy API and quietly calls
.lazy
and.collect
as needed.
use Dan;
use Dan::Polars;
my \df = DataFrame.new;
df.read_csv("../dan/src/iris.csv");
# ---------------------------------------
my $se = df.column("sepal.length");
$se.head;
# a Series...
shape: (5,)
Series: 'sepal.length' [f64]
[
5.1
4.9
4.7
4.6
5.0
]
# ---------------------------------------
df.select([col("sepal.length"), col("variety")]).head;
# a DataFrame...
shape: (5, 2)
โโโโโโโโโโโโโโโโฌโโโโโโโโโโ
โ sepal.length โ variety โ
โ --- โ --- โ
โ f64 โ str โ
โโโโโโโโโโโโโโโโชโโโโโโโโโโก
โ 5.1 โ Setosa โ
โโโโโโโโโโโโโโโโผโโโโโโโโโโค
โ 4.9 โ Setosa โ
โโโโโโโโโโโโโโโโผโโโโโโโโโโค
โ 4.7 โ Setosa โ
โโโโโโโโโโโโโโโโผโโโโโโโโโโค
โ 4.6 โ Setosa โ
โโโโโโโโโโโโโโโโผโโโโโโโโโโค
โ 5.0 โ Setosa โ
โโโโโโโโโโโโโโโโดโโโโโโโโโโ
# ---------------------------------------
df.groupby(["variety"]).agg([col("petal.length").sum]).head;
# -- or --
my $expr;
$expr = col("petal.length");
$expr .= sum;
df.groupby(["variety"]).agg([$expr]).head;
# An Expression takes the form Fn(Series --> Series) {} ...
# a DataFrame...
shape: (2, 2)
โโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโ
โ variety โ petal.length โ
โ --- โ --- โ
โ str โ f64 โ
โโโโโโโโโโโโโโชโโโโโโโโโโโโโโโก
โ Versicolor โ 141.4 โ
โโโโโโโโโโโโโโผโโโโโโโโโโโโโโโค
โ Setosa โ 73.1 โ
โโโโโโโโโโโโโโดโโโโโโโโโโโโโโโ
# ---------------------------------------
# Here are some unary expressions; the ```.alias``` method can be used to rename cols...
my @exprs;
@exprs.push: col("petal.length").sum;
#@exprs.push: col("sepal.length").mean;
#@exprs.push: col("sepal.length").min;
#@exprs.push: col("sepal.length").max;
#@exprs.push: col("sepal.length").first;
#@exprs.push: col("sepal.length").last;
#@exprs.push: col("sepal.length").unique;
#@exprs.push: col("sepal.length").count;
#@exprs.push: col("sepal.length").forward_fill;
#@exprs.push: col("sepal.length").backward_fill;
@exprs.push: col("sepal.length").reverse;
@exprs.push: col("sepal.length").std.alias("std");
#@exprs.push: col("sepal.length").var;
df.groupby(["variety"]).agg(@exprs).head;
shape: (2, 4)
โโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโ
โ variety โ petal.length โ sepal.length โ std โ
โ --- โ --- โ --- โ --- โ
โ str โ f64 โ list[f64] โ f64 โ
โโโโโโโโโโโโโโชโโโโโโโโโโโโโโโชโโโโโโโโโโโโโโโโโโโโโโชโโโโโโโโโโโก
โ Versicolor โ 141.4 โ [5.8, 5.5, ... 7.0] โ 0.539255 โ
โโโโโโโโโโโโโโผโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโค
โ Setosa โ 73.1 โ [5.0, 5.3, ... 5.1] โ 0.3524 โ
โโโโโโโโโโโโโโดโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโ
# ---------------------------------------
# use col("*") to select all...
df.select([col("*").exclude(["sepal.width"])]).head;
df.select([col("*").sum]).head;
# ---------------------------------------
# Can do Expression math...
df.select([
col("sepal.length"),
col("petal.length"),
(col("petal.length") + (col("sepal.length"))).alias("add"),
(col("petal.length") - (col("sepal.length"))).alias("sub"),
(col("petal.length") * (col("sepal.length"))).alias("mul"),
(col("petal.length") / (col("sepal.length"))).alias("div"),
(col("petal.length") % (col("sepal.length"))).alias("mod"),
(col("petal.length") div (col("sepal.length"))).alias("floordiv"),
]).head;
shape: (5, 8)
โโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโฌโโโโโโฌโโโโโโโฌโโโโโโโฌโโโโโโโโโโโฌโโโโโโฌโโโโโโโโโโโ
โ sepal.length โ petal.length โ add โ sub โ mul โ div โ mod โ floordiv โ
โ --- โ --- โ --- โ --- โ --- โ --- โ --- โ --- โ
โ f64 โ f64 โ f64 โ f64 โ f64 โ f64 โ f64 โ f64 โ
โโโโโโโโโโโโโโโโชโโโโโโโโโโโโโโโชโโโโโโชโโโโโโโชโโโโโโโชโโโโโโโโโโโชโโโโโโชโโโโโโโโโโโก
โ 5.1 โ 1.4 โ 6.5 โ -3.7 โ 7.14 โ 0.2745 โ 1.4 โ 0.2745 โ
โโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโผโโโโโโผโโโโโโโผโโโโโโโผโโโโโโโโโโโผโโโโโโผโโโโโโโโโโโค
โ 4.9 โ 1.4 โ 6.3 โ -3.5 โ 6.86 โ 0.285714 โ 1.4 โ 0.285714 โ
โโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโผโโโโโโผโโโโโโโผโโโโโโโผโโโโโโโโโโโผโโโโโโผโโโโโโโโโโโค
โ 4.7 โ 1.3 โ 6.0 โ -3.4 โ 6.11 โ 0.276596 โ 1.3 โ 0.276596 โ
โโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโผโโโโโโผโโโโโโโผโโโโโโโผโโโโโโโโโโโผโโโโโโผโโโโโโโโโโโค
โ 4.6 โ 1.5 โ 6.1 โ -3.1 โ 6.9 โ 0.326087 โ 1.5 โ 0.326087 โ
โโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโผโโโโโโผโโโโโโโผโโโโโโโผโโโโโโโโโโโผโโโโโโผโโโโโโโโโโโค
โ 5.0 โ 1.4 โ 6.4 โ -3.6 โ 7.0 โ 0.28 โ 1.4 โ 0.28 โ
โโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโดโโโโโโดโโโโโโโดโโโโโโโดโโโโโโโโโโโดโโโโโโดโโโโโโโโโโโ
# ---------------------------------------
# And use literals...
df.select([
col("sepal.length"),
col("petal.length"),
(col("petal.length") + 7).alias("add7"),
(7 - col("petal.length")).alias("sub7"),
(col("petal.length") * 2.2).alias("mul"),
(2.2 / (col("sepal.length"))).alias("div"),
(col("sepal.length") % 2).alias("mod"),
(col("sepal.length") div 0.1).alias("floordiv"),
]).head;
shape: (5, 8)
โโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโฌโโโโโโโฌโโโโโโโฌโโโโโโโฌโโโโโโโโโโโฌโโโโโโฌโโโโโโโโโโโ
โ sepal.length โ petal.length โ add7 โ sub7 โ mul โ div โ mod โ floordiv โ
โ --- โ --- โ --- โ --- โ --- โ --- โ --- โ --- โ
โ f64 โ f64 โ f64 โ f64 โ f64 โ f64 โ f64 โ f64 โ
โโโโโโโโโโโโโโโโชโโโโโโโโโโโโโโโชโโโโโโโชโโโโโโโชโโโโโโโชโโโโโโโโโโโชโโโโโโชโโโโโโโโโโโก
โ 5.1 โ 1.4 โ 8.4 โ 5.6 โ 3.08 โ 0.431373 โ 1.1 โ 51.0 โ
โโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโผโโโโโโโผโโโโโโโผโโโโโโโผโโโโโโโโโโโผโโโโโโผโโโโโโโโโโโค
โ 4.9 โ 1.4 โ 8.4 โ 5.6 โ 3.08 โ 0.4489 โ 0.9 โ 49.0 โ
โโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโผโโโโโโโผโโโโโโโผโโโโโโโผโโโโโโโโโโโผโโโโโโผโโโโโโโโโโโค
โ 4.7 โ 1.3 โ 8.3 โ 5.7 โ 2.86 โ 0.468085 โ 0.7 โ 47.0 โ
โโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโผโโโโโโโผโโโโโโโผโโโโโโโผโโโโโโโโโโโผโโโโโโผโโโโโโโโโโโค
โ 4.6 โ 1.5 โ 8.5 โ 5.5 โ 3.3 โ 0.478261 โ 0.6 โ 46.0 โ
โโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโผโโโโโโโผโโโโโโโผโโโโโโโผโโโโโโโโโโโผโโโโโโผโโโโโโโโโโโค
โ 5.0 โ 1.4 โ 8.4 โ 5.6 โ 3.08 โ 0.44 โ 1.0 โ 50.0 โ
โโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโดโโโโโโโดโโโโโโโดโโโโโโโดโโโโโโโโโโโดโโโโโโดโโโโโโโโโโโ
# ---------------------------------------
# There is a variant of with_column (for Series) and with_columns (for Expressions)
df.with_column($se.rename("newcol")).head;
df.with_columns([col("variety").alias("newnew")]).head;
Notes:
Most methods such as queries on the Raku object are applied to the shadow and the data remains on the Rust side for performance reasons. Exceptions are accessors and map operations which require the data to be synched manually to the Raku side using the
.flood
method and, when done, to be sent back Rustwards with.flush
. Sort & grep methods (in their current incarnations) also implicitly use .flood/.flush to sync.On reflection, the vanilla Dan splice & concat methods are not a good fit for Polars which has the simpler Series.append, DataFrame.vstack|.hstack and DataFrame.join methods. The new plan is to implement these Polars methods here (v0.2) and then to replace Dan and Dan::Pandas splice & concat with the Polars equivalent in the forthcoming As::Query role.
To avoid synching for
say ~df
operations, the.show
method applies Rust println! to STDOUT.For import and export, the
se.Dan-Series
anddf.Dan-DataFrame
methods will coerce to the raku-only Dan equivalent. You can goSeries.new(Dan::Series:D --> Dan::Polars::Series)
andDataFrame.new(Dan::DataFrame:D --> Dan::Polars::DataFrame)
.The method
df.to-dataset
is provided to, err, make a dataset for various raku Data:: library compatibility
Design Notes
lazy
Polars implements both lazy and eager APIs, these are functionally similar. For simplicity, Dan::Polars offers only the most efficient: lazy API. It has better query optimisation with low additional overhead.
auto-lazy
In Rust & Python Polars, lazy must be explicitly requested with .lazy .. .collect
methods around expressions. In contrast, Dan::Polars auto-generates the .lazy .. .collect
quietly for concise syntax.
pure
Polars Expressions are a function mapping from a series to a series (or mathematically Fn(Series) -> Series
). As expressions have a Series as an input and a Series as an output then it is straightforward to do a pipeline of expressions.
opaque
In general each raku object (Dan::Polars::Series, Dan::Polars::DataFrame) maintains a unique pointer to a rust container (SeriesC, DataFrameC) and they contain a shadow Rust Polars Struct. Methods invoked on the raku object are then proxied over to the Rust Polars shadow.
dynamic lib.so
A connection is made via Raku Nativecall to Rust FFI using a ``lib.so` dymanic library or equivalent.
data transfer
Usually no data needs to be transferred from Raku to Rust (or vice versa). For example, a raku script can command a Rust Polars DataFrame to be read from a csv file, apply expressions and output the result. The data items all remain on the Rust side of the connection.
TODOs
v0.1
Dan API
Dan::Series base methods
Dan::DataFrame base methods
Dan Accessors
Dan sort & grep (s3)
Polars Structs / Modules
Polars::Series base methods
Polars::DataFrame base methods
.push/.pull (set-new/get-data)
better value return
Polars Exprs (s2)
unary exprs
operators
Documentation
synopsis
refactor /bin
Test
This will then provide a basis for ...
v0.2
Dan splice & concat (s1) as hstack, vstack, join
drop col
non-null, etc.
This will then provide a basis for design Dan::As::Query v0.1 for Dan and Dan::Pandas and review Dan API slice & concat, immutability, refactor...
v0.3...
datetime
unique_stable
expr arity > 1
'over' expr
clone (then retest h2o-par)
immutability
reset @data after load rc (also to Pandas)
v0.4...
strip / fold Index
cross join (aka cross product)
pivot / cross-tabluate
map & apply (jit DSL style)
apply over multiple cols
ternary if-then-else (Dan::As::Ternary)
str operations (Dan::As::Str)
chunked transfer
serde
(c) Henley Cloud Consulting Ltd.