Polars Primer 06: Namespace Functions
Polars Namespaces
All code snippets use Rent_Contracts.csv as input data, sourced from lemoninabag/Rentals · Datasets at Hugging Face.
Namespaces in Polars were created to group numerous functions and operators that make sense for a specific data type (dtype) or specific context. This organizing scheme enforces Rust’s typing rules, requiring the Python API to follow suit. Generally speaking, these namespace domains can be grouped into the following:
String namespace
Temporal namespace
The List namespace
The Name namespace
Specialized namespaces
As we’ll see, we use these namespace functions in combination with the select() and pl.col() or col() operators.
1. The String Namespace (str)
This namespace includes operators that manipulate text (UTF-8) data. We’ve used this namespace in prior examples when we calculate the length of values in the "contract_id"and "contract_reg_type_en"” columns:
with_columns(pl.col("contract_id", "contract_reg_type_en")
.str.len_chars())| String Namespace | Example Operations |
|---|---|
| Case Operations | to_uppercase(), to_lowercase(), to_titlecase() |
| Search & Replace | replace("old", "new"), replace_all(), contains("pattern") |
| Splitting & Combining | split(delimiter), join(delimiter) |
| Extraction | slice(offset, length), extract(regex_group) |
Below is an example in Python using the split() function. We take the "area_name" string column and separate its values into a collection of substrings that we then store in a new column, "area_name_words".
import polars as pl
# Eagerly read the CSV into memory
df = pl.read_csv("Rent_Contracts.csv")
# Extract the contract_id and the original area_name_en,
# then create a new column with the split strings.
split_df = df.select(
"contract_id",
"area_name_en",
pl.col("area_name_en").str.split(" ").alias("area_name_words")
).head(3)
print(split_df)To accomplish the same in Rust, we’ll need to enable the Polars strings feature in Cargo.toml.
use polars::prelude::*;
fn main() -> PolarsResult<()> {
// 1. Setup the Lazy computation graph for the CSV
let lf = LazyCsvReader::new("Rent_Contracts.csv".into())
.with_has_header(true)
.finish()?;
// 2. Apply the str().split() operator
// We use lit(" ") to convert the Rust string slice into a Polars Expression
let df = lf.select([
col("contract_id"),
col("area_name_en"),
col("area_name_en")
.str()
.split(lit(" "))
.alias("area_name_words"),
])
.limit(3) // Limit to the first 3 rows
.collect()?;
println!("{:?}", df);
Ok(())
}shape: (3, 3)
┌───────────────┬────────────────────┬───────────────────────────┐
│ contract_id ┆ area_name_en ┆ area_name_words │
│ --- ┆ --- ┆ --- │
│ str ┆ str ┆ list[str] │
╞═══════════════╪════════════════════╪═══════════════════════════╡
│ CRT2128114436 ┆ Al Merkadh ┆ ["Al", "Merkadh"] │
│ CNT1786418853 ┆ Jabal Ali First ┆ ["Jabal", "Ali", "First"] │
│ CNT2126820013 ┆ Nad Al Shiba First ┆ ["Nad", "Al", … "First"] │
└───────────────┴────────────────────┴───────────────────────────┘2. The Temporal Namespace (dt)
When we need to manipulate Date, Time, Datetime and Duration data types, we turn to the Temporal Namespace.
| Temporal Namespace | Example Operations |
|---|---|
| Component Extraction | year(), month(), day(), hour(), weekday() |
| Formatting | to_string("%y-%m-%d") |
| Time Series Math | truncate("1w"), offset_by("id") |
| Aggregation | mean(), max(), min() |
Let’s look at a time series math operation, offset_by(). in the example below, we’ll use it to increment the contract_start_date values by one month. Since this column has a string datatype, we first convert into a temporal datatype. Once that’s done, the next operation in the chain, offset_by() increases the temporal value by a month. The last operation in the chain, head(), limits the output.
Note that for the Rust example to compile, we need to include the strings, the temporal and the offset_by features in our Cargo.toml.
import polars as pl
# Eagerly read the CSV into memory
df = pl.read_csv("Rent_Contracts.csv")
# 1. Parse the string date using the expected format (e.g., YYYY-MM-DD)
# 2. Apply the offset_by("1mo") temporal math
offset_df = df.select(
"contract_id",
pl.col("contract_start_date")
.str.to_date("%Y-%m-%d")
.alias("parsed_start_date"),
pl.col("contract_start_date")
.str.to_date("%Y-%m-%d")
.dt.offset_by("1mo")
.alias("start_date_plus_1mo")
).head(3)
print(offset_df)use polars::prelude::*;
fn main() -> PolarsResult<()> {
// 1. Setup the Lazy computation graph for the CSV
let lf = LazyCsvReader::new("Rent_Contracts.csv".into())
.with_has_header(true)
.finish()?;
// 2. Define our date parsing options (assuming YYYY-MM-DD format)
let date_options = StrptimeOptions {
format: Some("%Y-%m-%d".into()),
strict: false,
exact: true,
cache: true,
};
// 3. Apply the parsing and dt().offset_by() operators
let df = lf.select([
col("contract_id"),
col("contract_start_date")
.str()
.to_date(date_options.clone())
.alias("parsed_start_date"),
col("contract_start_date")
.str()
.to_date(date_options)
.dt()
// Like the split operator, we wrap the string in lit()
.offset_by(lit("1mo"))
.alias("start_date_plus_1mo"),
])
.limit(3) // Limit to the first 3 rows
.collect()?;
println!("{:?}", df);
Ok(())
}shape: (3, 3)
┌───────────────┬───────────────────┬─────────────────────┐
│ contract_id ┆ parsed_start_date ┆ start_date_plus_1mo │
│ --- ┆ --- ┆ --- │
│ str ┆ date ┆ date │
╞═══════════════╪═══════════════════╪═════════════════════╡
│ CRT2128114436 ┆ 2025-12-31 ┆ 2026-01-31 │
│ CNT1786418853 ┆ 2025-12-31 ┆ 2026-01-31 │
│ CNT2126820013 ┆ 2025-12-31 ┆ 2026-01-31 │
└───────────────┴───────────────────┴─────────────────────┘3. The List Namespace (list)
The List Namespace allows us to manipulate columns that are of data type list.
| List Namespace | Example Operations |
|---|---|
| Information | lengths(), contains(value) |
| Manipulation | reverse(), sort(), unique(), first(), last() |
| List Math | sum(), max(), min() |
| Explode/Join to String | join(delimtier) |
As an example, we pick the reverse() function. We first split each area_name_en value into a list of strings before passing it to reverse(), updating the order of the list elements.
import polars as pl
# Eagerly read the CSV into memory
df = pl.read_csv("Rent_Contracts.csv")
# 1. Split the string to create a List column
# 2. Access the list namespace and apply reverse()
reversed_df = df.select(
"contract_id",
pl.col("area_name_en")
.str.split(" ")
.alias("original_list"),
pl.col("area_name_en")
.str.split(" ")
.list.reverse()
.alias("reversed_list")
).head(3)
print(reversed_df)For the Rust example to work, we need to enable the Polars list_eval feature in Cargo.toml.
use polars::prelude::*;
fn main() -> PolarsResult<()> {
// 1. Setup the Lazy computation graph for the CSV
let lf = LazyCsvReader::new("Rent_Contracts.csv".into())
.with_has_header(true)
.finish()?;
// 2. Chain str().split() to create the list, then evaluate a reversal
let df = lf.select([
col("contract_id"),
col("area_name_en")
.str()
.split(lit(" "))
.alias("original_list"),
col("area_name_en")
.str()
.split(lit(" "))
.list()
// Evaluate a reverse operation on the elements of the list.
.eval(element().reverse())
.alias("reversed_list"),
])
.limit(3) // Limit to the first 3 rows
.collect()?;
println!("{:?}", df);
Ok(())
}shape: (3, 3)
┌───────────────┬───────────────────────────┬─────────────────────────────┐
│ contract_id ┆ original_list ┆ reversed_list │
│ --- ┆ --- ┆ --- │
│ str ┆ list[str] ┆ list[str] │
╞═══════════════╪═══════════════════════════╪═════════════════════════════╡
│ CRT2128114436 ┆ ["Al", "Merkadh"] ┆ ["Merkadh", "Al"] │
│ CNT1786418853 ┆ ["Jabal", "Ali", "First"] ┆ ["First", "Ali", "Jabal"] │
│ CNT2126820013 ┆ ["Nad", "Al", … "First"] ┆ ["First", "Shiba", … "Nad"] │
└───────────────┴───────────────────────────┴─────────────────────────────┘The Python API is more streamlined because the list().reverse() operation comes wrapped in syntactic sugar. With Rust we must explicitly instruct Polars to execute an eval() to reverse the list. In this added step, element()evaluates to a Poiars Expr obrject that acs as a stand-in for the collection of elements.
Note that the Polars element() operator applies only to list() in the context of eval(). It’s not intended to refer to columns in general. For that we use col() instead to apply an expression.
4. The Name Namespace (name)
The functions in the Name Namespace manipulate column metadata, whether in singular or bulk.
| Name Namespace | Example Operations |
|---|---|
| Renaming | keep(), map() |
| Affixes | suffix("suffix"), prefix("prefix") |
As an example for the Name namespace, we’ll discuss the use of the keep() function.
Whenever Polars encounters binary expressions, it automatically takes the name of the left-most operand and assigns this as the name of the resulting column. Therefore when Polars multiplies a literal value to columns of numeric type, such as the expression pl.lit(100) * cs.numeric(), Polars renames the resulting column to literal.
For example the following code snippet yields the tabel below. Note the name of the second column. It isn’t "contract_amount".
normalized_df = df.select(
"contract_id",
pl.lit(100) * "contract_amount"
).head(3)shape: (3, 2)
┌───────────────┬──────────┐
│ contract_id ┆ literal │
│ --- ┆ --- │
│ str ┆ i64 │
╞═══════════════╪══════════╡
│ CRT2128114436 ┆ 5300000 │
│ CNT1786418853 ┆ 10000000 │
│ CNT2126820013 ┆ 12500000 │
└───────────────┴──────────┘Problems arise when we follow the multiplication with another binary operation. For example:
normalized_df = df.select(
"contract_id",
pl.lit(100) * "contract_amount",
pl.lit(100) * "actual_area"
).head(3)Python raises the following error:
polars.exceptions.DuplicateError: projections contained duplicate output name ‘literal’. It’s possible that multiple expressions are returning the same default column name. If this is the case, try renaming the columns with .alias(“new_name”) to avoid duplicate column names.
Essentially the select() statement produces a two-column DataFrame, each one named literal. Rust flags the duplication as a conflict. The keep() function helps us avoid this complication.
import polars as pl
import polars.selectors as cs
df = pl.read_csv("Rent_Contracts.csv")
# .name.keep() prevents Polars from renaming all selected columns to 'literal'
normalized_df = df.select(
"contract_id",
(pl.lit(100) * "contract_amount").name.keep(),
(pl.lit(100) * "actual_area").name.keep()
).head(3)
print(normalized_df)With thekeep() method, we ensure that Polars carries over their respective column names, avoiding the error.
use polars::prelude::*;
fn main() -> PolarsResult<()> {
let lf = LazyCsvReader::new("Rent_Contracts.csv".into())
.with_has_header(true)
.finish()?;
// The name namespace is accessed via .name() in Rust
let df = lf.select([
col("contract_id"),
(lit(100) * polars::prelude::Expr::Selector(cols(["contract_amount", "actual_area"])))
.name()
.keep()
])
.limit(3)
.collect()?;
println!("{:?}", df);
Ok(())
}shape: (3, 3)
┌───────────────┬─────────────────┬─────────────┐
│ contract_id ┆ contract_amount ┆ actual_area │
│ --- ┆ --- ┆ --- │
│ str ┆ i64 ┆ f64 │
╞═══════════════╪═════════════════╪═════════════╡
│ CRT2128114436 ┆ 5300000 ┆ 3000.0 │
│ CNT1786418853 ┆ 10000000 ┆ 8900.0 │
│ CNT2126820013 ┆ 12500000 ┆ 12300.0 │
└───────────────┴─────────────────┴─────────────┘Note that multiplication is commutative. Instead of pl.lit(100) * "contract_amount" we could have written "contract_amount" * pl.lit(100) and avoided the need for keep(). However not all operations are commutative. In inverse operations (1/x) or when subtracting numerical values (100 - x), Polars names the resulting column to literal. Hence the need to use keep() in these cases.
5. Other Namespaces (Array, Categorical, Struct)
The Array Namespace (arr) is similar to the list but offers only fixed-sized collections. Operations on arr are highly performant. We won’t say any more about this namespace.
The Categorical Namespace (cat) offers a dictionary extension used to map integers to strings. The goal behind this is to replace strings in a column with integers, a more performant and memory-saving representation. With the help of the Categoriical namespace, Polars manages the integer assignment as dictionary keys, along with any translation between integer and strings.
Our example below explores this system. Here metro_enum maps the strings in metro_categories to u32 integers. As the Polars threads process the data stream and read chunks of nearest_metro_en strings, they synchronize with one another and use this global enumeration to populate the nearest_metro_code column.
The list of strings in metro_categories, extracted from the nearest_metro_en column beforehand, plays a key role in enforcing determinism across execution runs. This hard-coded list of strings, along with the enum dictionary, establishes a global string-to-integer mapping. Absent that, the Rayon threads that Polars employs would need to fall back on an Arc integer counter. As the threads process the data stream, they would rely on thie Arc to synchronize any access to the data structure that maps string to integver. Depending on the outcome of thread scheduling, each program run would produce a different string-to-integer mapping, resulting in varying nearest_metro_code associated with each nearest_metro_en.
import polars as pl
# 1. Define the exact, pre-determined list of categories (excluding None)
# The order of this list permanently dictates the underlying physical u32 codes.
metro_categories = [
'Union Metro Station', 'UAE Exchange Metro Station', 'Trade Centre Metro Station',
'Terminal 3 ', 'Sharaf Dg Metro Station', 'Salah Al Din Metro Station',
'STADIUM Metro Station', 'Rashidiya Metro Station', 'Palm Jumeirah',
'Palm Deira Metro Stations', 'Oud Metha Metro Station', 'Noor Bank Metro Station',
'Nakheel Metro Station', 'Mina Seyahi', 'Media City', 'Marina Towers',
'Marina Mall Metro Station', 'Knowledge Village', 'Jumeirah Lakes Towers',
'Jumeirah Beach Residency', 'Jumeirah Beach Resdency', 'Ibn Battuta Metro Station',
'Healthcare City Metro Station', 'Harbour Tower', 'GGICO Metro Station',
'First Abu Dhabi Bank Metro Station', 'Financial Centre', 'Etisalat Metro Station',
'Emirates Towers Metro Station', 'Emirates Metro Station', 'ENERGY Metro Station',
'Dubai Marina', 'Dubai Internet City', 'Deira City Centre', 'Damac Properties',
'DANUBE Metro Station', 'Creek Metro Station', 'Business Bay Metro Station',
'Burjuman Metro Station', 'Buj Khalifa Dubai Mall Metro Station',
'Baniyas Square Metro Station', 'Al Sufouh', 'Al Rigga Metro Station',
'Al Ras Metro Station', 'Al Qusais Metro Station', 'Al Qiyadah Metro Station',
'Al Nahda Metro Station', 'Al Jafiliya Metro Station', 'Al Jadaf Metro Station',
'Al Ghubaiba Metro Station', 'Al Fahidi Metro Station',
'Airport Terminal 1 Metro Station', 'Airport Free Zone', 'Abu Hail Metro Station',
'Abu Baker Al Siddique Metro Station', 'ADCB Metro Station'
]
# 2. Create the strict Enum data type
metro_enum = pl.Enum(metro_categories)
df = pl.read_csv("Rent_Contracts.csv")
# 3. Cast using the Enum type to guarantee 100% deterministic physical codes
categorical_df = df.select(
"contract_id",
pl.col("nearest_metro_en").cast(metro_enum),
pl.col("nearest_metro_en")
.cast(metro_enum)
.cat.physical()
.alias("nearest_metro_code")
).unique(
subset="nearest_metro_en", keep="first", maintain_order=True
).sort("nearest_metro_code", descending=True).head(8)
print(categorical_df)use polars::prelude::*;
use polars::datatypes::{Categories, CategoricalPhysical};
fn main() -> PolarsResult<()> {
let lf = LazyCsvReader::new("Rent_Contracts.csv".into())
.with_has_header(true)
.finish()?;
// 1. Define the exact, pre-determined list of categories
let metro_categories = vec![
"Union Metro Station", "UAE Exchange Metro Station", "Trade Centre Metro Station",
"Terminal 3 ", "Sharaf Dg Metro Station", "Salah Al Din Metro Station",
"STADIUM Metro Station", "Rashidiya Metro Station", "Palm Jumeirah",
"Palm Deira Metro Stations", "Oud Metha Metro Station", "Noor Bank Metro Station",
"Nakheel Metro Station", "Mina Seyahi", "Media City", "Marina Towers",
"Marina Mall Metro Station", "Knowledge Village", "Jumeirah Lakes Towers",
"Jumeirah Beach Residency", "Jumeirah Beach Resdency", "Ibn Battuta Metro Station",
"Healthcare City Metro Station", "Harbour Tower", "GGICO Metro Station",
"First Abu Dhabi Bank Metro Station", "Financial Centre", "Etisalat Metro Station",
"Emirates Towers Metro Station", "Emirates Metro Station", "ENERGY Metro Station",
"Dubai Marina", "Dubai Internet City", "Deira City Centre", "Damac Properties",
"DANUBE Metro Station", "Creek Metro Station", "Business Bay Metro Station",
"Burjuman Metro Station", "Buj Khalifa Dubai Mall Metro Station",
"Baniyas Square Metro Station", "Al Sufouh", "Al Rigga Metro Station",
"Al Ras Metro Station", "Al Qusais Metro Station", "Al Qiyadah Metro Station",
"Al Nahda Metro Station", "Al Jafiliya Metro Station", "Al Jadaf Metro Station",
"Al Ghubaiba Metro Station", "Al Fahidi Metro Station",
"Airport Terminal 1 Metro Station", "Airport Free Zone", "Abu Hail Metro Station",
"Abu Baker Al Siddique Metro Station", "ADCB Metro Station"
];
// 2. Initialize with U32 instead of UInt32
let categories = Categories::new("metro_enum".into(), "metro_namespace".into(), CategoricalPhy
sical::U32);
let mapping = categories.mapping();
// Explicitly populate the bidirectional dictionary to guarantee determinism
for cat in &metro_categories {
mapping.insert_cat(cat)?;
}
// 3. Freeze the categories and drop the Some() wrappers to match the 0.55+ tuple variant
let enum_dtype = DataType::Enum(categories.freeze(), mapping.clone());
// 4. Apply selections, stable unique filtering, and sorting using the strict Enum type
let df = lf.select([
col("contract_id"),
col("nearest_metro_en")
.cast(enum_dtype.clone()),
col("nearest_metro_en")
.cast(enum_dtype)
.cast(DataType::UInt32) // Bypass .cat().physical() and extract the integer directly
.alias("nearest_metro_code")
])
.unique_stable_generic(
Some(vec![col("nearest_metro_en")]),
UniqueKeepStrategy::First
)
.sort(
["nearest_metro_code"],
SortMultipleOptions::default().with_order_descending(true)
)
.limit(8)
.collect()?;
println!("{:?}", df);
Ok(())
}shape: (8, 3)
┌───────────────┬─────────────────────────────────┬────────────────────┐
│ contract_id ┆ nearest_metro_en ┆ nearest_metro_code │
│ --- ┆ --- ┆ --- │
│ str ┆ enum ┆ u8 │
╞═══════════════╪═════════════════════════════════╪════════════════════╡
│ CNT2128489587 ┆ null ┆ null │
│ CNT2122424527 ┆ ADCB Metro Station ┆ 55 │
│ CNT2128529007 ┆ Abu Baker Al Siddique Metro St… ┆ 54 │
│ CNT2126343530 ┆ Abu Hail Metro Station ┆ 53 │
│ CNT2123423191 ┆ Airport Free Zone ┆ 52 │
│ CNT2129112737 ┆ Airport Terminal 1 Metro Stati… ┆ 51 │
│ CNT2126250649 ┆ Al Fahidi Metro Station ┆ 50 │
│ CNT2127546869 ┆ Al Ghubaiba Metro Station ┆ 49 │
└───────────────┴─────────────────────────────────┴────────────────────┘The Struct Namespace (struct) handles data stream that comes in hierarchical and nested format, such as JSON. Polars treats a column of struct data type as a dictionary, allowing us to interact with the data it stores. That’s all we have to say about the structnamespace.