Skip to main content

plotkit_polars/
lib.rs

1//! Polars integration for plotkit.
2//!
3//! This crate activates Polars support in `plotkit-core`, providing
4//! [`IntoSeries`](plotkit_core::series::IntoSeries) and
5//! [`IntoCategories`](plotkit_core::series::IntoCategories) implementations
6//! for Polars [`Series`](polars::prelude::Series). Depend on this crate (or
7//! enable the `polars` feature on the `plotkit` umbrella crate) to pass
8//! Polars data directly to any plotkit charting function.
9//!
10//! # Supported types
11//!
12//! | Type | Conversion |
13//! |---|---|
14//! | `&Series` (numeric: f64, f32, i32, i64, u32, u64) | O(n) cast to `f64`, nulls become `NAN` |
15//! | `Series` (numeric, owned) | delegates to borrowed impl |
16//! | `&Series` (string / Utf8) | O(n) extraction to `Vec<String>`, nulls become `"null"` |
17//!
18//! # Examples
19//!
20//! ```
21//! use polars::prelude::*;
22//! use plotkit_core::series::IntoSeries;
23//!
24//! let s = Series::new("values".into(), &[1.0_f64, 2.0, 3.0]);
25//! let plotkit_series = (&s).into_series();
26//! assert_eq!(plotkit_series.data, vec![1.0, 2.0, 3.0]);
27//! ```
28
29#![deny(missing_docs)]
30
31// Re-export polars so downstream users can access the version we link against.
32pub use polars;
33
34// Re-export the core series types for convenience.
35pub use plotkit_core::series::{IntoCategories, IntoSeries, Series};
36
37// ---------------------------------------------------------------------------
38// Tests
39// ---------------------------------------------------------------------------
40
41#[cfg(test)]
42mod tests {
43    use plotkit_core::series::IntoCategories;
44    use plotkit_core::series::IntoSeries as PlotIntoSeries;
45    use polars::prelude::{
46        DataType, Float64Chunked, IntoSeries as PolarsIntoSeries, NamedFrom, Series,
47        StringChunked,
48    };
49
50    // -- IntoSeries: f64 ---------------------------------------------------
51
52    #[test]
53    fn series_from_polars_f64() {
54        let s = Series::new("a".into(), &[1.0_f64, 2.5, 3.0]);
55        let ps = PlotIntoSeries::into_series(&s);
56        assert_eq!(ps.data, vec![1.0, 2.5, 3.0]);
57    }
58
59    #[test]
60    fn series_from_polars_f64_owned() {
61        let s = Series::new("a".into(), &[4.0_f64, 5.0]);
62        let ps = PlotIntoSeries::into_series(s);
63        assert_eq!(ps.data, vec![4.0, 5.0]);
64    }
65
66    // -- IntoSeries: f32 ---------------------------------------------------
67
68    #[test]
69    fn series_from_polars_f32() {
70        let s = Series::new("b".into(), &[1.5_f32, 2.5]);
71        let ps = PlotIntoSeries::into_series(&s);
72        assert_eq!(ps.data, vec![1.5_f64, 2.5]);
73    }
74
75    // -- IntoSeries: i32 ---------------------------------------------------
76
77    #[test]
78    fn series_from_polars_i32() {
79        let s = Series::new("c".into(), &[10_i32, 20, 30]);
80        let ps = PlotIntoSeries::into_series(&s);
81        assert_eq!(ps.data, vec![10.0, 20.0, 30.0]);
82    }
83
84    // -- IntoSeries: i64 ---------------------------------------------------
85
86    #[test]
87    fn series_from_polars_i64() {
88        let s = Series::new("d".into(), &[100_i64, 200]);
89        let ps = PlotIntoSeries::into_series(&s);
90        assert_eq!(ps.data, vec![100.0, 200.0]);
91    }
92
93    // -- IntoSeries: u32 ---------------------------------------------------
94
95    #[test]
96    fn series_from_polars_u32() {
97        let s = Series::new("e".into(), &[1_u32, 2, 3]);
98        let ps = PlotIntoSeries::into_series(&s);
99        assert_eq!(ps.data, vec![1.0, 2.0, 3.0]);
100    }
101
102    // -- IntoSeries: u64 ---------------------------------------------------
103
104    #[test]
105    fn series_from_polars_u64() {
106        let s = Series::new("f".into(), &[42_u64, 99]);
107        let ps = PlotIntoSeries::into_series(&s);
108        assert_eq!(ps.data, vec![42.0, 99.0]);
109    }
110
111    // -- IntoSeries: null handling -----------------------------------------
112
113    #[test]
114    fn series_null_values_become_nan() {
115        let ca = Float64Chunked::new("g".into(), &[Some(1.0), None, Some(3.0)]);
116        let s: Series = PolarsIntoSeries::into_series(ca);
117        let ps = PlotIntoSeries::into_series(&s);
118        assert_eq!(ps.data.len(), 3);
119        assert_eq!(ps.data[0], 1.0);
120        assert!(ps.data[1].is_nan());
121        assert_eq!(ps.data[2], 3.0);
122    }
123
124    // -- IntoSeries: empty -------------------------------------------------
125
126    #[test]
127    fn series_empty_polars() {
128        let s = Series::new_empty("empty".into(), &DataType::Float64);
129        let ps = PlotIntoSeries::into_series(&s);
130        assert!(ps.is_empty());
131    }
132
133    // -- IntoCategories: string series -------------------------------------
134
135    #[test]
136    fn categories_from_polars_str() {
137        let s = Series::new("labels".into(), &["foo", "bar", "baz"]);
138        let cats = IntoCategories::into_categories(&s);
139        assert_eq!(cats.labels, vec!["foo", "bar", "baz"]);
140    }
141
142    #[test]
143    fn categories_null_becomes_null_string() {
144        let ca = StringChunked::new("h".into(), &[Some("a"), None, Some("c")]);
145        let s: Series = PolarsIntoSeries::into_series(ca);
146        let cats = IntoCategories::into_categories(&s);
147        assert_eq!(cats.labels, vec!["a", "null", "c"]);
148    }
149
150    #[test]
151    fn categories_empty_polars() {
152        let s = Series::new_empty("empty".into(), &DataType::String);
153        let cats = IntoCategories::into_categories(&s);
154        assert!(cats.is_empty());
155    }
156
157    // -- Panic on wrong dtype ----------------------------------------------
158
159    #[test]
160    fn series_string_cast_produces_nan() {
161        // Polars casts non-numeric strings to null (NAN in plotkit).
162        let s = Series::new("bad".into(), &["not", "numeric"]);
163        let ps = PlotIntoSeries::into_series(&s);
164        assert_eq!(ps.data.len(), 2);
165        assert!(ps.data[0].is_nan());
166        assert!(ps.data[1].is_nan());
167    }
168
169    #[test]
170    #[should_panic(expected = "is not a string type")]
171    fn categories_panics_on_numeric_dtype() {
172        let s = Series::new("bad".into(), &[1.0_f64, 2.0]);
173        let _ = IntoCategories::into_categories(&s);
174    }
175}