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
262 .cast(&DataType::Float64)
263 .unwrap_or_else(|_| {
264 panic!(
265 "plotkit-polars: cannot cast series {:?} (dtype {:?}) to Float64",
266 series.name(),
267 series.dtype()
268 )
269 });
270 let ca = ca
271 .f64()
272 .expect("cast to Float64 always yields f64 chunked array");
273 ca.into_iter().map(|opt| opt.unwrap_or(f64::NAN)).collect()
274 }
275
276 impl IntoSeries for &polars::prelude::Series {
277 fn into_series(self) -> Series {
283 Series::new(extract_numeric(self))
284 }
285 }
286
287 impl IntoSeries for polars::prelude::Series {
288 fn into_series(self) -> Series {
291 (&self).into_series()
292 }
293 }
294
295 impl IntoCategories for &polars::prelude::Series {
296 fn into_categories(self) -> Categories {
299 let ca = self.str().unwrap_or_else(|_| {
300 panic!(
301 "plotkit-polars: series {:?} (dtype {:?}) is not a string type",
302 self.name(),
303 self.dtype()
304 )
305 });
306 let labels: Vec<String> = ca
307 .into_iter()
308 .map(|opt| opt.unwrap_or("null").to_owned())
309 .collect();
310 Categories::new(labels)
311 }
312 }
313}
314
315impl IntoSeries for Vec<f32> {
318 fn into_series(self) -> Series {
320 Series::new(self.into_iter().map(|v| v as f64).collect())
321 }
322}
323
324impl IntoSeries for &[f32] {
325 fn into_series(self) -> Series {
327 Series::new(self.iter().map(|&v| v as f64).collect())
328 }
329}
330
331#[derive(Debug, Clone)]
341pub struct Categories {
342 pub labels: Vec<String>,
344}
345
346impl Categories {
347 pub fn new(labels: Vec<String>) -> Self {
349 Self { labels }
350 }
351
352 pub fn len(&self) -> usize {
354 self.labels.len()
355 }
356
357 pub fn is_empty(&self) -> bool {
359 self.labels.is_empty()
360 }
361}
362
363pub trait IntoCategories {
369 fn into_categories(self) -> Categories;
371}
372
373impl IntoCategories for &[&str] {
374 fn into_categories(self) -> Categories {
376 Categories::new(self.iter().map(|s| (*s).to_owned()).collect())
377 }
378}
379
380impl IntoCategories for Vec<String> {
381 fn into_categories(self) -> Categories {
383 Categories::new(self)
384 }
385}
386
387impl IntoCategories for &[String] {
388 fn into_categories(self) -> Categories {
390 Categories::new(self.to_vec())
391 }
392}
393
394impl IntoCategories for Vec<&str> {
395 fn into_categories(self) -> Categories {
397 Categories::new(self.into_iter().map(|s| s.to_owned()).collect())
398 }
399}
400
401#[cfg(test)]
406mod tests {
407 use super::*;
408
409 #[test]
410 fn series_from_vec_f64() {
411 let s = vec![1.0, 2.0, 3.0].into_series();
412 assert_eq!(s.len(), 3);
413 assert_eq!(s.data, vec![1.0, 2.0, 3.0]);
414 }
415
416 #[test]
417 fn series_from_slice_f64() {
418 let data: &[f64] = &[4.0, 5.0];
419 let s = data.into_series();
420 assert_eq!(s.data, vec![4.0, 5.0]);
421 }
422
423 #[test]
424 fn series_from_vec_ref() {
425 let v = vec![1.0, 2.0];
426 let s = (&v).into_series();
427 assert_eq!(s.data, vec![1.0, 2.0]);
428 }
429
430 #[test]
431 fn series_from_array() {
432 let s = [10.0, 20.0, 30.0].into_series();
433 assert_eq!(s.data, vec![10.0, 20.0, 30.0]);
434 }
435
436 #[test]
437 fn series_from_array_ref() {
438 let arr = [7.0, 8.0];
439 let s = (&arr).into_series();
440 assert_eq!(s.data, vec![7.0, 8.0]);
441 }
442
443 #[test]
444 fn series_identity() {
445 let original = Series::new(vec![1.0]);
446 let s = original.into_series();
447 assert_eq!(s.data, vec![1.0]);
448 }
449
450 #[test]
451 fn series_from_range() {
452 let s = (0..4).into_series();
453 assert_eq!(s.data, vec![0.0, 1.0, 2.0, 3.0]);
454 }
455
456 #[test]
457 fn series_from_vec_i32() {
458 let s = vec![1i32, 2, 3].into_series();
459 assert_eq!(s.data, vec![1.0, 2.0, 3.0]);
460 }
461
462 #[test]
463 fn series_from_slice_i32() {
464 let data: &[i32] = &[10, 20];
465 let s = data.into_series();
466 assert_eq!(s.data, vec![10.0, 20.0]);
467 }
468
469 #[test]
470 fn series_from_vec_f32() {
471 let s = vec![1.5f32, 2.5].into_series();
472 assert_eq!(s.data, vec![1.5f64, 2.5]);
473 }
474
475 #[test]
476 fn series_from_slice_f32() {
477 let data: &[f32] = &[0.1, 0.2];
478 let s = data.into_series();
479 assert_eq!(s.len(), 2);
480 }
481
482 #[test]
483 fn series_empty() {
484 let s = Series::new(vec![]);
485 assert!(s.is_empty());
486 assert_eq!(s.min(), None);
487 assert_eq!(s.max(), None);
488 assert_eq!(s.bounds(), None);
489 }
490
491 #[test]
492 fn series_min_max_bounds() {
493 let s = vec![3.0, 1.0, 4.0, 1.5, 9.0].into_series();
494 assert_eq!(s.min(), Some(1.0));
495 assert_eq!(s.max(), Some(9.0));
496 assert_eq!(s.bounds(), Some((1.0, 9.0)));
497 }
498
499 #[test]
500 fn series_min_max_ignores_nan() {
501 let s = vec![f64::NAN, 2.0, f64::INFINITY, 1.0, f64::NEG_INFINITY].into_series();
502 assert_eq!(s.min(), Some(1.0));
503 assert_eq!(s.max(), Some(2.0));
504 }
505
506 #[test]
507 fn categories_from_str_slice() {
508 let cats: &[&str] = &["a", "b", "c"];
509 let c = cats.into_categories();
510 assert_eq!(c.labels, vec!["a", "b", "c"]);
511 }
512
513 #[test]
514 fn categories_from_vec_string() {
515 let c = vec!["x".to_string(), "y".to_string()].into_categories();
516 assert_eq!(c.labels, vec!["x", "y"]);
517 }
518
519 #[test]
520 fn categories_from_string_slice() {
521 let v = vec!["p".to_string(), "q".to_string()];
522 let c = v.as_slice().into_categories();
523 assert_eq!(c.labels, vec!["p", "q"]);
524 }
525
526 #[test]
527 fn categories_from_vec_str_ref() {
528 let c = vec!["foo", "bar"].into_categories();
529 assert_eq!(c.labels, vec!["foo", "bar"]);
530 assert_eq!(c.len(), 2);
531 assert!(!c.is_empty());
532 }
533}