Library / SDK
ankane/ruby-polars avatar
ankane/ruby-polars

Ruby Polars: DataFrames for Ruby through a native extension

Blazingly fast DataFrames for Ruby

997 stars48 forksRubyMIT

At a glance

What is it?
polars-df puts the Polars engine behind a Ruby API, with lazy scan and collect pipelines, readers for CSV, Parquet and Active Record, and a native Rust extension. The catch is that the Ruby surface trails the Python API, and the Iceberg, Delta Lake and Arrow paths are still marked experimental.
Who is it for?
Ruby Polars fits a Ruby team that already thinks in Polars terms and wants that engine without adding a Python service to the deployment path. It fits badly if you need the newest Polars features the day they land, because this is a port and the project's own advice is to translate Python tutorials by hand.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 2 days ago.
What is it written in?
Mainly Ruby, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A DataFrame engine for Ruby, not a Ruby reimplementation

Ruby Polars fills a gap in the Ruby ecosystem: the language ships no tabular structure with columnar types, lazy evaluation or file scanning. The gem is published on RubyGems as polars-df, and the repository layout shows how little of the work happens in Ruby. The Cargo.toml file declares a Cargo workspace whose only member is ext/polars, and the top-level entries place ext/ next to lib/. The query engine is therefore a native extension written in Rust that gets compiled and loaded from Ruby, and the objects you manipulate are views onto that engine rather than Ruby arrays of Ruby arrays. Anyone arriving from pandas should adjust their expectations: a column is not a Ruby Array, and arithmetic on a column produces a column expression, not a Ruby value. The README states the design contract in a single line, 'This library follows the Polars Python API', which makes the Python documentation the reference for method names and semantics. The licence covering this repository's code is MIT, and the project is not archived.

scan_csv then collect is the whole lazy model

Every lazy workflow here has the same two steps: declare a scan, then execute the plan. The README gives this example:

ruby
Polars.scan_csv("iris.csv")
  .filter(Polars.col("sepal_length") > 5)
  .group_by("species")
  .agg(Polars.all.sum)
  .collect

The scan functions are used with a trailing collect, which shows that the read is deferred until that call rather than performed when the chain is built. The same split exists for Parquet, NDJSON, IPC, Delta and Iceberg, so a team can describe a query first and pay for the full read only when it runs. If you want the rows in memory immediately, the eager readers Polars.read_csv and Polars.read_parquet skip the planning stage altogether. That is the mental model in one line: scanning is free until you collect, and everything after a collect behaves like an ordinary in-memory frame.

Adding polars-df to a Gemfile and compiling the ext directory

Installation is a single line, and the project documentation is equally brief about it:

ruby
gem "polars-df"

Because the ext/ directory and the Cargo workspace are part of the gem, installing it pulls a native extension onto your machine, so a Rust build is part of the setup rather than an optional extra. The repository documents the development loop it expects, and the same tasks apply when you build from source:

bash
git clone https://github.com/ankane/ruby-polars.git
cd ruby-polars
bundle install
bundle exec rake compile
bundle exec rake test
bundle exec rake test:docs

The build badge in the project points at a GitHub Actions workflow, so the compile and test tasks run on pushes. The repository publishes no GitHub releases, which means there is no release archive to download and no versioned binary to vendor. Deployment is the gem plus the toolchain needed to compile it.

read_database takes a relation or a SQL string

The database path is the one that will matter to most Rails codebases, and two forms are documented:

ruby
Polars.read_database(User.all)
# or
Polars.read_database("SELECT * FROM users")

One form takes an Active Record relation, the other takes a SQL string, so both generated and hand-written SQL are entry points into a frame. What the documentation does not describe is how the relation is executed, whether it is fetched eagerly, or how column types are inferred from the adapter, and those are exactly the details to probe before relying on it in a report job. The rest of the input list is long: CSV, Parquet, JSON, NDJSON, Arrow IPC, Avro, a plain hash, an array of hashes, an array of series, and the experimental Iceberg and Delta Lake readers. A frame can also be built directly in memory through Polars::DataFrame.new, which is what you would use for small hand-assembled results.

Schemas are explicit, and Object and Unknown say little

Types are declared rather than guessed, and a schema can be passed when a frame is constructed:

ruby
Polars::DataFrame.new(data, schema: {"a" => Polars::Int32, "b" => Polars::Float32})

The supported list covers booleans, decimals, three float widths, signed integers from Int8 through Int128, unsigned integers up to UInt128, strings including Categorical and Enum, temporal types, nested types, and a final group of Binary, Object, Null and Unknown. Two names in that last group deserve attention, since Object and Unknown describe the absence of a concrete storage format rather than a format you can rely on. Reading types back is cheap, df.schema for the whole frame and df["a"].dtype for one column, and casting is explicit with df["a"].cast(Polars::Int32). Nothing in the documentation sets out implicit conversion rules for a CSV column that mixes integers and text, so parsing surprises will surface in your own code.

Charts live in a separate vega gem, not in polars-df

Visualisation is not part of this library. It lives in a separate project, vega-ruby, and the gem to add is:

ruby
gem "vega"

Once that gem is present, a frame plots directly:

ruby
df.plot.line("a", "b")

The chart types listed are line, pie, column, bar, area and scatter, with grouping through a color argument and stacked output through a stacked flag on column and bar charts. A single series can be plotted on its own, df["a"].plot.hist, with hist, kde and line available for series. The split is worth noting during evaluation, because the data layer and the chart layer are separate gems from separate repositories, even though the same maintainer publishes them. A team that only reads and aggregates CSV files needs neither the plotting gem nor the other companions, while a team that wants charts accepts a second dependency with its own version to track.

Iceberg, Delta Lake and Arrow are the experimental edges

Three parts of the surface are labelled experimental. Iceberg needs the iceberg-ruby project, Delta Lake needs deltalake-rb, and the Arrow export additionally requires nanoarrow, which is called out in the conversion section while the rest of that section is unqualified. The call shapes are not symmetrical either, since Polars.scan_iceberg expects a table object rather than a path, while the Delta Lake reader takes a directory. Beyond those, the whole surface is a port of another project's API, and the project states that Python tutorials can often be converted to Ruby. Often is the operative word. A plan built around a Python method that has not been ported leaves a gap, and the search for the Ruby equivalent in CHANGELOG.md at the top level becomes part of the work rather than an occasional chore.

The real alternative is Python Polars, or SQL in the database

The honest alternative is not another Ruby library. It is Python Polars, and the difference in approach is easy to state. There the reference implementation and the reference documentation are the same thing, so a new method and its guide arrive together. Here the Ruby port is a second surface over the same engine, so a method that has not been ported yet is the gap you will hit. The second option is to push the work back into the database and let SQL do the aggregation, which keeps the data where it already lives and removes the gem entirely, at the cost of the lazy plan and the local file readers. Which one wins depends on where the data sits: for files on a worker, this gem is the option; for a warehouse, SQL usually wins on maintenance cost. A third path, translating Python tutorials by hand as the project suggests, is reasonable for a one-off script and unworkable for a large analytics codebase.

Editorial conclusion

Ruby Polars fits a Ruby team that already thinks in Polars terms and wants that engine without adding a Python service to the deployment path. It fits badly if you need the newest Polars features the day they land, because this is a port and the project's own advice is to translate Python tutorials by hand. Before adopting it, check CHANGELOG.md for the specific methods you need and confirm the native extension builds inside your deployment image. The last push to the repository was on 2026-10-16, so the surface is still moving, but it moves one ported method at a time.

Frequently asked questions

How do I install Ruby Polars in a Ruby project?

The project documentation gives one line for the installation, adding gem "polars-df" to the application Gemfile. The gem carries a native extension built from the ext/ directory, so a Rust toolchain is part of the install.

What is the difference between Polars.read_csv and Polars.scan_csv in Ruby?

read_csv reads the file straight into memory. scan_csv is the lazy form, and the project documentation uses it with a trailing collect, so the read happens when the plan is executed rather than when it is declared.

Can Ruby Polars read rows from Active Record?

Yes. Polars.read_database accepts an Active Record relation such as User.all or a SQL string such as SELECT * FROM users. The documentation does not describe how the relation is executed, so check that detail on your own stack.

Where can I see which Ruby Polars version and changes to look at?

The repository publishes no GitHub releases, so the changelog in CHANGELOG.md at the top level is the record of what changed. The last push to the repository was on 2026-10-16.

Official sources

  1. ankane/ruby-polars on GitHub
  2. Issues
  3. License: MIT
  4. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/ankane-ruby-polars.svg)](https://hysenlabs.com/projects/ankane-ruby-polars)

Was this useful?