Skip to main content

Unit management for polars

Project description

polars-units

polars-units extends Polars capabilities by adding support for physical units and unit-aware computations. If offers:

  • A new Quantity extension type for pl.DataFrame and pl.Series
  • Unit validation and parsing
  • Arithmetic operations
  • Conversions between units
  • Metadata conservation when writing or reading Parquet files through recently supported Arrow extension types

How to use

Installation

uv add polars-units
# or
pip install polars-units

Usage

Creating a DataFrame / Series

By default, polars-units uses a catalog of standard units, so that you don't need any configuration to get started: simply define a column data type to pu.Quantity, with a unit of your choice.

import polars as pl
import polars_units as pu

data = pl.DataFrame(
    [[1.2, 3.4, 6.8], [3.2, 2.4, 9.7]],
    schema={"length": pu.Quantity("m"), "time": pu.Quantity("s")},
)

print(data)

Which prints:

shape: (3, 2)
┌─────────────┬─────────────┐
│ length      ┆ time        │
│ ---         ┆ ---         │
│ ext[q['m']] ┆ ext[q['s']] │
╞═════════════╪═════════════╡
│ 1.2         ┆ 3.2         │
│ 3.4         ┆ 2.4         │
│ 6.8         ┆ 9.7         │
└─────────────┴─────────────┘

INFO - Under the hood, pu.Quantity is an extension type that stores values as pl.Float64 as well as unit metadata, that is why the shown type is ext[...].

Complex units

As long as all their components are known to polars-units, you can build composed units using /, . and exponents. Metric prefixes are automatically parsed.

>>> pu.Quantity("m")
Quantity(m)
>>> pu.Quantity("m2/s.V^5.km-3")   # `^` is optional
Quantity(m^2.s^-1.V^5.km^-3)

Casting to / from numeric columns

You can cast any pl.Float64 column to pu.Quantity by using pl.Expr.ext.to():

>>> data = pl.DataFrame([1.2, 4.9], schema={"distance": pl.Float64})
>>> data

shape: (2, 1)
┌──────────┐
 distance 
 ---      
 f64      
╞══════════╡
 1.2      
 4.9      
└──────────┘

>>> data.with_columns(pl.col("distance").ext.to(pu.Quantity("km")))

shape: (2, 1)
┌──────────────┐
 distance     
 ---          
 ext[q['km']] 
╞══════════════╡
 1.2          
 4.9          
└──────────────┘

NOTE - For now, only Float64 columns are supported, if you have a column of another numeric type, you need to cast it to Float64 before converting it to a Quantity column.

In the same way, you can convert a Quantity column back to Float64 by using pl.Expr.ext.storage():

>>> data = pl.DataFrame([1.2, 4.9], schema={"distance": pu.Quantity("km")})
>>> data.with_columns(pl.col("distance").ext.storage())

shape: (2, 1)
┌──────────┐
 distance 
 ---      
 f64      
╞══════════╡
 1.2      
 4.9      
└──────────┘

Conversions

polars-units introduces a new namespace unit, that groups all operations related to units. You can convert to another unit via the to method:

>>> data.with_columns(pl.col("length").unit.to("cm"))

shape: (3, 2)
┌──────────────┬─────────────┐
 length        time        
 ---           ---         
 ext[q['cm']]  ext[q['s']] 
╞══════════════╪═════════════╡
 120.0         3.2         
 340.0         2.4         
 680.0         9.7         
└──────────────┴─────────────┘

Arithmetic

Arithmetic operators (+, *...) are not yet supported for extension types, but their corresponding methods are present in the unit namespace.

Operator Method
+ .add()
- .sub()
* .mul()
/ .div()
>>> data.with_columns(pl.col("length").unit.div("time").alias("speed"))

┌─────────────┬─────────────┬──────────────────┐
 length       time         speed            
 ---          ---          ---              
 ext[q['m']]  ext[q['s']]  ext[q['m.s^-1']] 
╞═════════════╪═════════════╪══════════════════╡
 1.2          3.2          0.375            
 3.4          2.4          1.416667         
 6.8          9.7          0.701031         
└─────────────┴─────────────┴──────────────────┘

You can only add or substract units that have the same dimension.

length = pl.Series([1.2, 4.9], dtype=pu.Quantity("m"))
width = pl.Series([42.0, 18.5], dtype=pu.Quantity("cm"))
time = pl.Series([1.0, 2.0], dtype=pu.Quantity("min"))

length.unit.add(width)

If units differ, the one in the first expression is used:

shape: (2,)
Series: '' [ext[q['m']]]
[
        1.62
        5.085
]

If you try to add units with different dimensions, an error is raised to prevent the operation:

>>> length.unit.add(time)

polars.exceptions.ComputeError: the plugin failed with message: Dimensions of both units must be the same:
        > left: Dimension { length: 0, current: 0, luminosity: 0, mass: 0, amount: 0, temperature: 0, time: 1 },
        > right: Dimension { length: 1, current: 0, luminosity: 0, mass: 0, amount: 0, temperature: 0, time: 0 }

NOTE - For now, only operations between units are allowed, operations between units and scalars is planned, but not yet implemented.

Advanced usage

Core objects

Internally, polars-units uses a few classes to manipulate units, and check the validity of operations. You can use these classes to build your own sets of units.

Dimension

In the International System of Units, there are seven fundamental physical dimensions that are use to construct every unit.

Quantity Symbol (in polars-units) Associated unit
Time T second s
Length L meter m
Mass M kilogram kg
Electric current I ampere A
Themodynamic temperature Th kelvin K
Amount of substance N mole mol
Luminous intensity J candela cd

Dimensions are represented by the Dimension class, and are used to verify the validity of operations between units (you cannot add kilograms and meters, for example).

You can build Dimension objects by passing a dictionary:

speed_dimension = pu.Dimension({pu.BaseDimension.LENGTH: 1, pu.BaseDimension.TIME: -1})

Or you can directly pass a string that will be parsed:

speed_dimension = pu.Dimension("L/T") # or "L.T^-1"

BaseUnit

A base unit is a single unit (e.g. meter m, Volt V, minute min), possibly associated with a prefix (kilo k, milli m). It it used as a base component of composed units, such as m/s². In polars-units, a BaseUnit has several attributes:

  • A symbol, that is used for display,
  • A name,
  • A prefix (optional),
  • A formula (see Dimensions),
  • si_offset and si_factor, used for conversions between units.

You can define new units by creating new BaseUnit objects:

yard = pu.BaseUnit(symbol="yd", name="yard", dimension="L", si_factor=1 / 0.9144)
foot = pu.BaseUnit(symbol="ft", name="foot", dimension="L", si_factor=1 / 0.3048)

Unit

Unit objects represent combinations of base units and their exponent in a formula. For example, a speed in km/h has a km component with exponent 1, and a h component with exponent -1. Quantity-type columns store Unit objects internally to perform operations. They are almost never instanciated directly, they are rather built trough Registry objects.

Registries

In order to perform validation on units, polars-units relies on pu.Registry objects. They contain a list of valid units and their properties. By default, polars-units uses a pu.DefaultRegistry(), which includes a catalog of usual SI units (see Catalog). If you want to extend the pool of available units, you need to configure a Registry with your own BaseUnits.

Extend the default registry

By instanciating a new DefaultRegistry, you can benefit from both the default units, and your own.

yard = pu.BaseUnit(symbol="yd", name="yard", si_factor=1 / 0.9144)
foot = pu.BaseUnit(symbol="ft", name="foot", si_factor=1 / 0.3048)

registry = pu.DefaultRegistry([yard, foot])

You need to set the global registry before creating new Quantity columns based on your units:

pu.config.registry = registry

Once the configuration is set to the new registry, you can use these new units in your program:

>>> pl.Series([1.2, 4.9], dtype=pu.Quantity("m")).unit.to("yd")

shape: (2,)
Series: '' [ext[q['yd']]]
[
        1.09728
        4.48056
]

Create a new registry

If you want to define your own set of units from scratch, you can use the Registry class instead:

registry = pu.Registry([yard, foot])

pu.config.registry = registry   # Use your new registry

Now only your units will be registered:

>>> length = pl.Series([1.2, 4.9], dtype=pu.Quantity("m"))

...
polars_units.utils.exceptions.UnitNotFoundError: Unit with symbol 'm' not found in registry.

Catalog

The following table lists all units included in polars-units by default.

Dimensions are noted using the following symbols: $L$ = Length, $I$ = Current, $J$ = Luminosity, $M$ = Mass, $N$ = Amount of substance, $\Theta$ = Temperature, $T$ = Time.

SI base units

Symbol Name Dimension Notes
m Meter $L$ SI base unit
A Ampere $I$ SI base unit
cd Candela $J$ SI base unit
kg Kilogram $M$ SI base unit
mol Mole $N$ SI base unit
K Kelvin $\Theta$ SI base unit
s Second $T$ SI base unit
°C Degree Celsius $\Theta$
°F Degree Fahrenheit $\Theta$
min Minute $T$
h Hour $T$
rad Radian $-$ Dimensionless
sr Steradian $-$ Dimensionless
Hz Hertz $T^{-1}$
N Newton $M \cdot L \cdot T^{-2}$
Pa Pascal $M \cdot L^{-1} \cdot T^{-2}$
J Joule $M \cdot L^2 \cdot T^{-2}$
W Watt $M \cdot L^2 \cdot T^{-3}$
C Coulomb $I \cdot T$
V Volt $M \cdot L^2 \cdot I^{-1} \cdot T^{-3}$
F Farad $M^{-1} \cdot L^{-2} \cdot I^2 \cdot T^4$
Ω / ohm Ohm $M \cdot L^2 \cdot I^{-2} \cdot T^{-3}$ Both symbols are equivalent
S Siemens $M^{-1} \cdot L^{-2} \cdot I^2 \cdot T^3$
Wb Weber $M \cdot L^2 \cdot I^{-1} \cdot T^{-2}$
T Tesla $M \cdot I^{-1} \cdot T^{-2}$
H Henry $M \cdot L^2 \cdot I^{-2} \cdot T^{-2}$
lm Lumen $J$
lx Lux $J \cdot L^{-2}$
Bq Becquerel $T^{-1}$
Gy Gray $L^2 \cdot T^{-2}$
Sv Sievert $L^2 \cdot T^{-2}$
kat Katal $N \cdot T^{-1}$

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

polars_units-0.1.0a4-cp310-abi3-win_amd64.whl (4.7 MB view details)

Uploaded CPython 3.10+Windows x86-64

polars_units-0.1.0a4-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (5.3 MB view details)

Uploaded CPython 3.10+manylinux: glibc 2.17+ x86-64

polars_units-0.1.0a4-cp310-abi3-macosx_11_0_arm64.whl (4.4 MB view details)

Uploaded CPython 3.10+macOS 11.0+ ARM64

File details

Details for the file polars_units-0.1.0a4-cp310-abi3-win_amd64.whl.

File metadata

  • Download URL: polars_units-0.1.0a4-cp310-abi3-win_amd64.whl
  • Upload date:
  • Size: 4.7 MB
  • Tags: CPython 3.10+, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.11 {"installer":{"name":"uv","version":"0.11.11","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for polars_units-0.1.0a4-cp310-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 ea7d1b5098c4fc482a6d3c0a0cb4a244bf707705a8d1fb5ce0d8a6bd74c9699a
MD5 1b31bceba60e5885570e47a769af0331
BLAKE2b-256 191be3229ed0b1d32ea94d24e875205301308eca64820b89d1957b1de721d079

See more details on using hashes here.

File details

Details for the file polars_units-0.1.0a4-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

  • Download URL: polars_units-0.1.0a4-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
  • Upload date:
  • Size: 5.3 MB
  • Tags: CPython 3.10+, manylinux: glibc 2.17+ x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.11 {"installer":{"name":"uv","version":"0.11.11","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for polars_units-0.1.0a4-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 734dcc885d8a49dce2a043c4ffbec71bc7ce2ef7096f2d3243cce65a8abb4951
MD5 f031a0dc6ce66a07b6030e1d2c24ea3e
BLAKE2b-256 1c51c29581b9c9c677bc8473849c77b6dede28f953b03ed2e7450b6717bf010f

See more details on using hashes here.

File details

Details for the file polars_units-0.1.0a4-cp310-abi3-macosx_11_0_arm64.whl.

File metadata

  • Download URL: polars_units-0.1.0a4-cp310-abi3-macosx_11_0_arm64.whl
  • Upload date:
  • Size: 4.4 MB
  • Tags: CPython 3.10+, macOS 11.0+ ARM64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.11 {"installer":{"name":"uv","version":"0.11.11","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for polars_units-0.1.0a4-cp310-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 055bdb2e7ae9b6b9ad9ce1316e0375af2121f65c24bf3b8317e4d2319ea53218
MD5 8d60098a46f96189fb8b78657b08494c
BLAKE2b-256 a745eb2addc8b2a7a343aff287fe3c5d751d9cef6725bafe3071e20d923b33a1

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page