polars-rows-iter 0.13.2

Library for easy and convenient row iteration of polars dataframes
Documentation

polars-rows-iter

Library for simple and convenient row iteration of polars dataframes

This crate provides two main approaches for iterating over DataFrame rows:

  • Struct-based iteration using #[derive(FromDataFrameRow)] - best for complex rows with many columns
  • Tuple-based iteration using the df_rows_iter! macro - best for quick, simple iterations

Example with tuple-based iteration:

For simple use cases where you don't need a dedicated struct, use the df_rows_iter! macro to iterate over rows as tuples:

use polars::prelude::*;
use polars_rows_iter::*;

fn main() {
    let df = df!(
        "name" => ["Alice", "Bob", "Charlie"],
        "age" => [25i32, 30, 35],
        "score" => [Some(95.5f64), None, Some(87.0)]
    ).unwrap();

    let score_col = format!("sco{}", "re"); // dynamic column name

    let iter = df_rows_iter!(
        &df,
        "name" => &str,
        "age" => i32,
        score_col => Option<f64>
    ).unwrap();

    for row in iter {
        let (name, age, score) = row.unwrap();
        println!("{name}: age {age}, score {score:?}");
    }
}

The macro supports tuples of up to 10 elements. Each element is specified as column_name => Type. Column names can be string literals or any expression that implements AsRef<str>.

Example with static column names:

use polars::prelude::*;
use polars_rows_iter::*;

fn main() {
    #[derive(Debug, FromDataFrameRow)]
    #[derive(PartialEq)] // for assert_eq
    struct MyRow<'a>
    {
        #[column("col_a")]
        a: i32,
        // the column name defaults to the field name if no explicit name given
        col_b: &'a str,
        col_c: String,
        #[column("col_d")]
        optional: Option<f64>
    }

    let df = df!(
            "col_a" => [1i32, 2, 3, 4, 5],
            "col_b" => ["a", "b", "c", "d", "e"],
            "col_c" => ["A", "B", "C", "D", "E"],
            "col_d" => [Some(1.0f64), None, None, Some(2.0), Some(3.0)]
        ).unwrap();

    let rows_iter = df.rows_iter::<MyRow>().unwrap(); // ready to use row iterator
    // collect to vector for assert_eq
    let rows_vec = rows_iter.collect::<PolarsResult<Vec<MyRow>>>().unwrap();

    assert_eq!(
        rows_vec,
        [
            MyRow { a: 1, col_b: "a", col_c: "A".to_string(), optional: Some(1.0) },
            MyRow { a: 2, col_b: "b", col_c: "B".to_string(), optional: None },
            MyRow { a: 3, col_b: "c", col_c: "C".to_string(), optional: None },
            MyRow { a: 4, col_b: "d", col_c: "D".to_string(), optional: Some(2.0) },
            MyRow { a: 5, col_b: "e", col_c: "E".to_string(), optional: Some(3.0) },
        ]
    );
}

Example with dynamic column names:

use polars::prelude::*;
use polars_rows_iter::*;

const ID: &str = "id";

#[derive(Debug, FromDataFrameRow)]
#[derive(PartialEq)] // for assert_eq
struct MyRow<'a> {
    #[column(ID)]
    id: i32,
    value_b: &'a str,
    value_c: String,
    optional: Option<f64>,
}

fn create_dataframe() -> PolarsResult<DataFrame> {
    df!(
        "id" => [1i32, 2, 3, 4, 5],
        "col_b" => ["a", "b", "c", "d", "e"],
        "col_c" => ["A", "B", "C", "D", "E"],
        "col_d" => [Some(1.0f64), None, None, Some(2.0), Some(3.0)]
    )
}

fn main() {
    let df = create_dataframe().unwrap();

    let value_b_column_name = "col_b".to_string();
    let value_c_column_name = "col_c";

    let rows_iter = df
        .rows_iter_with_columns::<MyRow>(|columns| {
            columns
                .value_b(&value_b_column_name)
                .value_c(value_c_column_name)
                .optional("col_d")
        })
        .unwrap(); // ready to use row iterator

    // collect to vector for assert_eq
    let rows_vec = rows_iter.collect::<PolarsResult<Vec<MyRow>>>().unwrap();

    assert_eq!(
        rows_vec,
        [
            MyRow { id: 1, value_b: "a", value_c: "A".to_string(), optional: Some(1.0) },
            MyRow { id: 2, value_b: "b", value_c: "B".to_string(), optional: None },
            MyRow { id: 3, value_b: "c", value_c: "C".to_string(), optional: None },
            MyRow { id: 4, value_b: "d", value_c: "D".to_string(), optional: Some(2.0) },
            MyRow { id: 5, value_b: "e", value_c: "E".to_string(), optional: Some(3.0) },
        ]
    );
}

Example with column name transformations:

You can use attributes to automatically transform field names to match your DataFrame's column naming conventions:

use polars::prelude::*;
use polars_rows_iter::*;

fn main() {
    // Transformations are applied in order:
    // 1. prefix("api_"): adds "api_" before field name
    // 2. convert_case(Pascal): converts snake_case to PascalCase
    // 3. postfix("_field"): adds "_field" after field name
    //
    // So field "user_name" becomes column "api_UserName_field"
    #[derive(Debug, FromDataFrameRow)]
    #[from_dataframe(prefix("api_"), convert_case(Pascal), postfix("_field"))]
    struct ApiRow<'a> {
        user_id: i32,
        user_name: &'a str,
        is_active: bool,
    }

    let df = df!(
        "api_UserId_field" => [1i32, 2, 3],
        "api_UserName_field" => ["Alice", "Bob", "Charlie"],
        "api_IsActive_field" => [true, false, true]
    ).unwrap();

    for row in df.rows_iter::<ApiRow>().unwrap() {
        println!("{:?}", row.unwrap());
    }
}

Supported case conversions (from the convert_case crate): Upper, Lower, Title, Toggle, Camel, Pascal, UpperCamel, Snake, UpperSnake, ScreamingSnake, Kebab, Cobol, UpperKebab, Train, Flat, UpperFlat, Alternating

Version Compatibility

polars-rows-iter Polars
v0.13.2 0.53.0
v0.13.1 0.53.0
v0.13.0 0.52.0
v0.12.1 0.52.0
v0.12.0 0.52.0
v0.11.1 0.52.0
v0.11.0 0.51.0
v0.10.0 0.51.0
v0.9.8 0.51.0
v0.9.7 0.50.0
v0.9.6 0.49.1
v0.9.5 0.48.1
v0.9.4 0.48.0
v0.9.3 0.47.1
v0.9.2 0.46.0
v0.9.1 0.45.1
v0.8.0 0.45.1
v0.7.0 0.45.0
v0.6.0 0.44.2
v0.5.0 0.44.2
v0.4.0 0.44.2
v0.3.0 0.44.2
v0.2.0 0.44.2