1#[derive(Debug, Clone)]
10pub struct Series {
11 pub data: Vec<f64>,
13}
14
15impl Series {
16 pub fn new(data: Vec<f64>) -> Self {
27 Self { data }
28 }
29
30 pub fn len(&self) -> usize {
32 self.data.len()
33 }
34
35 pub fn is_empty(&self) -> bool {
37 self.data.is_empty()
38 }
39
40 pub fn min(&self) -> Option<f64> {
45 self.data
46 .iter()
47 .copied()
48 .filter(|v| v.is_finite())
49 .reduce(f64::min)
50 }
51
52 pub fn max(&self) -> Option<f64> {
57 self.data
58 .iter()
59 .copied()
60 .filter(|v| v.is_finite())
61 .reduce(f64::max)
62 }
63
64 pub fn bounds(&self) -> Option<(f64, f64)> {
67 Some((self.min()?, self.max()?))
68 }
69}
70
71pub trait IntoSeries {
82 fn into_series(self) -> Series;
84}
85
86impl IntoSeries for Vec<f64> {
89 fn into_series(self) -> Series {
91 Series::new(self)
92 }
93}
94
95impl IntoSeries for &[f64] {
96 fn into_series(self) -> Series {
98 Series::new(self.to_vec())
99 }
100}
101
102impl IntoSeries for &Vec<f64> {
103 fn into_series(self) -> Series {
105 Series::new(self.clone())
106 }
107}
108
109impl<const N: usize> IntoSeries for [f64; N] {
110 fn into_series(self) -> Series {
112 Series::new(self.to_vec())
113 }
114}
115
116impl<const N: usize> IntoSeries for &[f64; N] {
117 fn into_series(self) -> Series {
119 Series::new(self.to_vec())
120 }
121}
122
123impl IntoSeries for Series {
126 fn into_series(self) -> Series {
128 self
129 }
130}
131
132impl IntoSeries for std::ops::Range<i32> {
135 fn into_series(self) -> Series {
146 Series::new(self.map(|v| v as f64).collect())
147 }
148}
149
150impl IntoSeries for Vec<i32> {
153 fn into_series(self) -> Series {
155 Series::new(self.into_iter().map(|v| v as f64).collect())
156 }
157}
158
159impl IntoSeries for &[i32] {
160 fn into_series(self) -> Series {
162 Series::new(self.iter().map(|&v| v as f64).collect())
163 }
164}
165
166#[cfg(feature = "ndarray")]
169mod ndarray_impls {
170 use super::{IntoSeries, Series};
171 use ndarray::{Array1, ArrayView1};
172
173 impl IntoSeries for &Array1<f64> {
176 fn into_series(self) -> Series {
181 match self.as_slice() {
182 Some(slice) => Series::new(slice.to_vec()),
183 None => Series::new(self.iter().copied().collect()),
184 }
185 }
186 }
187
188 impl IntoSeries for Array1<f64> {
189 fn into_series(self) -> Series {
194 if self.is_standard_layout() {
195 let len = self.len();
196 let (vec, offset) = self.into_raw_vec_and_offset();
197 let offset = offset.unwrap_or(0);
198 if offset == 0 && vec.len() == len {
199 Series::new(vec)
200 } else {
201 Series::new(vec[offset..offset + len].to_vec())
202 }
203 } else {
204 Series::new(self.iter().copied().collect())
205 }
206 }
207 }
208
209 impl IntoSeries for ArrayView1<'_, f64> {
210 fn into_series(self) -> Series {
215 match self.as_slice() {
216 Some(slice) => Series::new(slice.to_vec()),
217 None => Series::new(self.iter().copied().collect()),
218 }
219 }
220 }
221
222 macro_rules! impl_into_series_ndarray_cast {
225 ($t:ty) => {
226 impl IntoSeries for &Array1<$t> {
227 fn into_series(self) -> Series {
228 Series::new(self.iter().map(|&v| v as f64).collect())
229 }
230 }
231
232 impl IntoSeries for Array1<$t> {
233 fn into_series(self) -> Series {
234 Series::new(self.iter().map(|&v| v as f64).collect())
235 }
236 }
237
238 impl IntoSeries for ArrayView1<'_, $t> {
239 fn into_series(self) -> Series {
240 Series::new(self.iter().map(|&v| v as f64).collect())
241 }
242 }
243 };
244 }
245
246 impl_into_series_ndarray_cast!(f32);
247 impl_into_series_ndarray_cast!(i32);
248 impl_into_series_ndarray_cast!(i64);
249}
250
251#[cfg(feature = "polars")]
254mod polars_impls {
255 use super::{Categories, IntoCategories, IntoSeries, Series};
256 use polars::prelude::*;
257
258 fn extract_numeric(series: &polars::prelude::Series) -> Vec<f64> {
261 let ca = series.cast(&DataType::Float64).unwrap_or_else(|_| {
262 panic!(
263 "plotkit-polars: cannot cast series {:?} (dtype {:?}) to Float64",
264 series.name(),
265 series.dtype()
266 )
267 });
268 let ca = ca
269 .f64()
270 .expect("cast to Float64 always yields f64 chunked array");
271 ca.into_iter().map(|opt| opt.unwrap_or(f64::NAN)).collect()
272 }
273
274 impl IntoSeries for &polars::prelude::Series {
275 fn into_series(self) -> Series {
281 Series::new(extract_numeric(self))
282 }
283 }
284
285 impl IntoSeries for polars::prelude::Series {
286 fn into_series(self) -> Series {
289 (&self).into_series()
290 }
291 }
292
293 impl IntoCategories for &polars::prelude::Series {
294 fn into_categories(self) -> Categories {
297 let ca = self.str().unwrap_or_else(|_| {
298 panic!(
299 "plotkit-polars: series {:?} (dtype {:?}) is not a string type",
300 self.name(),
301 self.dtype()
302 )
303 });
304 let labels: Vec<String> = ca
305 .into_iter()
306 .map(|opt| opt.unwrap_or("null").to_owned())
307 .collect();
308 Categories::new(labels)
309 }
310 }
311}
312
313impl IntoSeries for Vec<f32> {
316 fn into_series(self) -> Series {
318 Series::new(self.into_iter().map(|v| v as f64).collect())
319 }
320}
321
322impl IntoSeries for &[f32] {
323 fn into_series(self) -> Series {
325 Series::new(self.iter().map(|&v| v as f64).collect())
326 }
327}
328
329#[derive(Debug, Clone)]
339pub struct Categories {
340 pub labels: Vec<String>,
342}
343
344impl Categories {
345 pub fn new(labels: Vec<String>) -> Self {
347 Self { labels }
348 }
349
350 pub fn len(&self) -> usize {
352 self.labels.len()
353 }
354
355 pub fn is_empty(&self) -> bool {
357 self.labels.is_empty()
358 }
359}
360
361pub trait IntoCategories {
367 fn into_categories(self) -> Categories;
369}
370
371impl IntoCategories for &[&str] {
372 fn into_categories(self) -> Categories {
374 Categories::new(self.iter().map(|s| (*s).to_owned()).collect())
375 }
376}
377
378impl IntoCategories for Vec<String> {
379 fn into_categories(self) -> Categories {
381 Categories::new(self)
382 }
383}
384
385impl IntoCategories for &[String] {
386 fn into_categories(self) -> Categories {
388 Categories::new(self.to_vec())
389 }
390}
391
392impl IntoCategories for Vec<&str> {
393 fn into_categories(self) -> Categories {
395 Categories::new(self.into_iter().map(|s| s.to_owned()).collect())
396 }
397}
398
399#[cfg(test)]
404mod tests {
405 use super::*;
406
407 #[test]
408 fn series_from_vec_f64() {
409 let s = vec![1.0, 2.0, 3.0].into_series();
410 assert_eq!(s.len(), 3);
411 assert_eq!(s.data, vec![1.0, 2.0, 3.0]);
412 }
413
414 #[test]
415 fn series_from_slice_f64() {
416 let data: &[f64] = &[4.0, 5.0];
417 let s = data.into_series();
418 assert_eq!(s.data, vec![4.0, 5.0]);
419 }
420
421 #[test]
422 fn series_from_vec_ref() {
423 let v = vec![1.0, 2.0];
424 let s = (&v).into_series();
425 assert_eq!(s.data, vec![1.0, 2.0]);
426 }
427
428 #[test]
429 fn series_from_array() {
430 let s = [10.0, 20.0, 30.0].into_series();
431 assert_eq!(s.data, vec![10.0, 20.0, 30.0]);
432 }
433
434 #[test]
435 fn series_from_array_ref() {
436 let arr = [7.0, 8.0];
437 let s = (&arr).into_series();
438 assert_eq!(s.data, vec![7.0, 8.0]);
439 }
440
441 #[test]
442 fn series_identity() {
443 let original = Series::new(vec![1.0]);
444 let s = original.into_series();
445 assert_eq!(s.data, vec![1.0]);
446 }
447
448 #[test]
449 fn series_from_range() {
450 let s = (0..4).into_series();
451 assert_eq!(s.data, vec![0.0, 1.0, 2.0, 3.0]);
452 }
453
454 #[test]
455 fn series_from_vec_i32() {
456 let s = vec![1i32, 2, 3].into_series();
457 assert_eq!(s.data, vec![1.0, 2.0, 3.0]);
458 }
459
460 #[test]
461 fn series_from_slice_i32() {
462 let data: &[i32] = &[10, 20];
463 let s = data.into_series();
464 assert_eq!(s.data, vec![10.0, 20.0]);
465 }
466
467 #[test]
468 fn series_from_vec_f32() {
469 let s = vec![1.5f32, 2.5].into_series();
470 assert_eq!(s.data, vec![1.5f64, 2.5]);
471 }
472
473 #[test]
474 fn series_from_slice_f32() {
475 let data: &[f32] = &[0.1, 0.2];
476 let s = data.into_series();
477 assert_eq!(s.len(), 2);
478 }
479
480 #[test]
481 fn series_empty() {
482 let s = Series::new(vec![]);
483 assert!(s.is_empty());
484 assert_eq!(s.min(), None);
485 assert_eq!(s.max(), None);
486 assert_eq!(s.bounds(), None);
487 }
488
489 #[test]
490 fn series_min_max_bounds() {
491 let s = vec![3.0, 1.0, 4.0, 1.5, 9.0].into_series();
492 assert_eq!(s.min(), Some(1.0));
493 assert_eq!(s.max(), Some(9.0));
494 assert_eq!(s.bounds(), Some((1.0, 9.0)));
495 }
496
497 #[test]
498 fn series_min_max_ignores_nan() {
499 let s = vec![f64::NAN, 2.0, f64::INFINITY, 1.0, f64::NEG_INFINITY].into_series();
500 assert_eq!(s.min(), Some(1.0));
501 assert_eq!(s.max(), Some(2.0));
502 }
503
504 #[test]
505 fn categories_from_str_slice() {
506 let cats: &[&str] = &["a", "b", "c"];
507 let c = cats.into_categories();
508 assert_eq!(c.labels, vec!["a", "b", "c"]);
509 }
510
511 #[test]
512 fn categories_from_vec_string() {
513 let c = vec!["x".to_string(), "y".to_string()].into_categories();
514 assert_eq!(c.labels, vec!["x", "y"]);
515 }
516
517 #[test]
518 fn categories_from_string_slice() {
519 let v = vec!["p".to_string(), "q".to_string()];
520 let c = v.as_slice().into_categories();
521 assert_eq!(c.labels, vec!["p", "q"]);
522 }
523
524 #[test]
525 fn categories_from_vec_str_ref() {
526 let c = vec!["foo", "bar"].into_categories();
527 assert_eq!(c.labels, vec!["foo", "bar"]);
528 assert_eq!(c.len(), 2);
529 assert!(!c.is_empty());
530 }
531}