polars_python/series/
general.rs

1use polars_core::chunked_array::cast::CastOptions;
2use polars_core::series::IsSorted;
3use polars_core::utils::flatten::flatten_series;
4use polars_utils::python_function::PythonObject;
5use pyo3::exceptions::{PyIndexError, PyRuntimeError, PyValueError};
6use pyo3::prelude::*;
7use pyo3::types::PyBytes;
8use pyo3::{IntoPyObjectExt, Python};
9
10use super::PySeries;
11use crate::dataframe::PyDataFrame;
12use crate::error::PyPolarsErr;
13use crate::prelude::*;
14use crate::py_modules::polars;
15use crate::utils::EnterPolarsExt;
16
17#[pymethods]
18impl PySeries {
19    fn struct_unnest(&self, py: Python) -> PyResult<PyDataFrame> {
20        py.enter_polars_df(|| Ok(self.series.read().struct_()?.clone().unnest()))
21    }
22
23    fn struct_fields(&self) -> PyResult<Vec<String>> {
24        let s = self.series.read();
25        let ca = s.struct_().map_err(PyPolarsErr::from)?;
26        Ok(ca
27            .struct_fields()
28            .iter()
29            .map(|s| s.name().to_string())
30            .collect())
31    }
32
33    fn is_sorted_ascending_flag(&self) -> bool {
34        matches!(self.series.read().is_sorted_flag(), IsSorted::Ascending)
35    }
36
37    fn is_sorted_descending_flag(&self) -> bool {
38        matches!(self.series.read().is_sorted_flag(), IsSorted::Descending)
39    }
40
41    fn can_fast_explode_flag(&self) -> bool {
42        match self.series.read().list() {
43            Err(_) => false,
44            Ok(list) => list._can_fast_explode(),
45        }
46    }
47
48    pub fn cat_uses_lexical_ordering(&self) -> PyResult<bool> {
49        Ok(true)
50    }
51
52    pub fn cat_is_local(&self) -> PyResult<bool> {
53        Ok(false)
54    }
55
56    pub fn cat_to_local(&self, _py: Python) -> PyResult<Self> {
57        Ok(self.clone())
58    }
59
60    fn estimated_size(&self) -> usize {
61        self.series.read().estimated_size()
62    }
63
64    #[cfg(feature = "object")]
65    fn get_object<'py>(&self, py: Python<'py>, index: usize) -> PyResult<Bound<'py, PyAny>> {
66        let s = self.series.read();
67        if matches!(s.dtype(), DataType::Object(_)) {
68            let obj: Option<&ObjectValue> = s.get_object(index).map(|any| any.into());
69            Ok(obj.into_pyobject(py)?)
70        } else {
71            Ok(py.None().into_bound(py))
72        }
73    }
74
75    #[cfg(feature = "dtype-array")]
76    fn reshape(&self, py: Python<'_>, dims: Vec<i64>) -> PyResult<Self> {
77        let dims = dims
78            .into_iter()
79            .map(ReshapeDimension::new)
80            .collect::<Vec<_>>();
81
82        py.enter_polars_series(|| self.series.read().reshape_array(&dims))
83    }
84
85    /// Returns the string format of a single element of the Series.
86    fn get_fmt(&self, index: usize, str_len_limit: usize) -> String {
87        let s = self.series.read();
88        let v = format!("{}", s.get(index).unwrap());
89        if let DataType::String | DataType::Categorical(_, _) | DataType::Enum(_, _) = s.dtype() {
90            let v_no_quotes = &v[1..v.len() - 1];
91            let v_trunc = &v_no_quotes[..v_no_quotes
92                .char_indices()
93                .take(str_len_limit)
94                .last()
95                .map(|(i, c)| i + c.len_utf8())
96                .unwrap_or(0)];
97            if v_no_quotes == v_trunc {
98                v
99            } else {
100                format!("\"{v_trunc}…")
101            }
102        } else {
103            v
104        }
105    }
106
107    pub fn rechunk(&self, py: Python<'_>, in_place: bool) -> PyResult<Option<Self>> {
108        let series = py.enter_polars_ok(|| self.series.read().rechunk())?;
109        if in_place {
110            *self.series.write() = series;
111            Ok(None)
112        } else {
113            Ok(Some(series.into()))
114        }
115    }
116
117    /// Get a value by index.
118    fn get_index(&self, py: Python<'_>, index: usize) -> PyResult<PyObject> {
119        let s = self.series.read();
120        let av = match s.get(index) {
121            Ok(v) => v,
122            Err(PolarsError::OutOfBounds(err)) => {
123                return Err(PyIndexError::new_err(err.to_string()));
124            },
125            Err(e) => return Err(PyPolarsErr::from(e).into()),
126        };
127
128        match av {
129            AnyValue::List(s) | AnyValue::Array(s, _) => {
130                let pyseries = PySeries::new(s);
131                polars(py).getattr(py, "wrap_s")?.call1(py, (pyseries,))
132            },
133            _ => Wrap(av).into_py_any(py),
134        }
135    }
136
137    /// Get a value by index, allowing negative indices.
138    fn get_index_signed(&self, py: Python<'_>, index: isize) -> PyResult<PyObject> {
139        let index = if index < 0 {
140            match self.len().checked_sub(index.unsigned_abs()) {
141                Some(v) => v,
142                None => {
143                    return Err(PyIndexError::new_err(
144                        polars_err!(oob = index, self.len()).to_string(),
145                    ));
146                },
147            }
148        } else {
149            usize::try_from(index).unwrap()
150        };
151        self.get_index(py, index)
152    }
153
154    fn bitand(&self, py: Python<'_>, other: &PySeries) -> PyResult<Self> {
155        py.enter_polars_series(|| &*self.series.read() & &*other.series.read())
156    }
157
158    fn bitor(&self, py: Python<'_>, other: &PySeries) -> PyResult<Self> {
159        py.enter_polars_series(|| &*self.series.read() | &*other.series.read())
160    }
161
162    fn bitxor(&self, py: Python<'_>, other: &PySeries) -> PyResult<Self> {
163        py.enter_polars_series(|| &*self.series.read() ^ &*other.series.read())
164    }
165
166    fn chunk_lengths(&self) -> Vec<usize> {
167        self.series.read().chunk_lengths().collect()
168    }
169
170    pub fn name(&self) -> String {
171        self.series.read().name().to_string()
172    }
173
174    fn rename(&self, name: &str) {
175        self.series.write().rename(name.into());
176    }
177
178    fn dtype<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
179        Wrap(self.series.read().dtype().clone()).into_pyobject(py)
180    }
181
182    fn set_sorted_flag(&self, descending: bool) -> Self {
183        let mut out = self.series.read().clone();
184        if descending {
185            out.set_sorted_flag(IsSorted::Descending);
186        } else {
187            out.set_sorted_flag(IsSorted::Ascending)
188        }
189        out.into()
190    }
191
192    fn n_chunks(&self) -> usize {
193        self.series.read().n_chunks()
194    }
195
196    fn append(&self, py: Python<'_>, other: &PySeries) -> PyResult<()> {
197        py.enter_polars(|| {
198            // Prevent self-append deadlocks.
199            let other = other.series.read().clone();
200            let mut s = self.series.write();
201            s.append(&other)?;
202            PolarsResult::Ok(())
203        })
204    }
205
206    fn extend(&self, py: Python<'_>, other: &PySeries) -> PyResult<()> {
207        py.enter_polars(|| {
208            // Prevent self-extend deadlocks.
209            let other = other.series.read().clone();
210            let mut s = self.series.write();
211            s.extend(&other)?;
212            PolarsResult::Ok(())
213        })
214    }
215
216    fn new_from_index(&self, py: Python<'_>, index: usize, length: usize) -> PyResult<Self> {
217        let s = self.series.read();
218        if index >= s.len() {
219            Err(PyValueError::new_err("index is out of bounds"))
220        } else {
221            py.enter_polars_series(|| Ok(s.new_from_index(index, length)))
222        }
223    }
224
225    fn filter(&self, py: Python<'_>, filter: &PySeries) -> PyResult<Self> {
226        let filter_series = &filter.series.read();
227        if let Ok(ca) = filter_series.bool() {
228            py.enter_polars_series(|| self.series.read().filter(ca))
229        } else {
230            Err(PyRuntimeError::new_err("Expected a boolean mask"))
231        }
232    }
233
234    fn sort(
235        &self,
236        py: Python<'_>,
237        descending: bool,
238        nulls_last: bool,
239        multithreaded: bool,
240    ) -> PyResult<Self> {
241        py.enter_polars_series(|| {
242            self.series.read().sort(
243                SortOptions::default()
244                    .with_order_descending(descending)
245                    .with_nulls_last(nulls_last)
246                    .with_multithreaded(multithreaded),
247            )
248        })
249    }
250
251    fn gather_with_series(&self, py: Python<'_>, indices: &PySeries) -> PyResult<Self> {
252        py.enter_polars_series(|| self.series.read().take(indices.series.read().idx()?))
253    }
254
255    fn null_count(&self) -> PyResult<usize> {
256        Ok(self.series.read().null_count())
257    }
258
259    fn has_nulls(&self) -> bool {
260        self.series.read().has_nulls()
261    }
262
263    fn equals(
264        &self,
265        py: Python<'_>,
266        other: &PySeries,
267        check_dtypes: bool,
268        check_names: bool,
269        null_equal: bool,
270    ) -> PyResult<bool> {
271        let s = self.series.read();
272        let o = other.series.read();
273        if check_dtypes && (s.dtype() != o.dtype()) {
274            return Ok(false);
275        }
276        if check_names && (s.name() != o.name()) {
277            return Ok(false);
278        }
279        if null_equal {
280            py.enter_polars_ok(|| s.equals_missing(&o))
281        } else {
282            py.enter_polars_ok(|| s.equals(&o))
283        }
284    }
285
286    fn as_str(&self) -> PyResult<String> {
287        Ok(format!("{:?}", self.series.read()))
288    }
289
290    #[allow(clippy::len_without_is_empty)]
291    pub fn len(&self) -> usize {
292        self.series.read().len()
293    }
294
295    /// Rechunk and return a pointer to the start of the Series.
296    /// Only implemented for numeric types
297    fn as_single_ptr(&self, py: Python) -> PyResult<usize> {
298        py.enter_polars(|| self.series.write().as_single_ptr())
299    }
300
301    fn clone(&self) -> Self {
302        Clone::clone(self)
303    }
304
305    fn zip_with(&self, py: Python<'_>, mask: &PySeries, other: &PySeries) -> PyResult<Self> {
306        let ms = mask.series.read();
307        let mask = ms.bool().map_err(PyPolarsErr::from)?;
308        py.enter_polars_series(|| self.series.read().zip_with(mask, &other.series.read()))
309    }
310
311    #[pyo3(signature = (separator, drop_first, drop_nulls))]
312    fn to_dummies(
313        &self,
314        py: Python<'_>,
315        separator: Option<&str>,
316        drop_first: bool,
317        drop_nulls: bool,
318    ) -> PyResult<PyDataFrame> {
319        py.enter_polars_df(|| {
320            self.series
321                .read()
322                .to_dummies(separator, drop_first, drop_nulls)
323        })
324    }
325
326    fn get_list(&self, index: usize) -> Option<Self> {
327        let s = self.series.read();
328        let ca = s.list().ok()?;
329        Some(ca.get_as_series(index)?.into())
330    }
331
332    fn n_unique(&self, py: Python) -> PyResult<usize> {
333        py.enter_polars(|| self.series.read().n_unique())
334    }
335
336    fn floor(&self, py: Python) -> PyResult<Self> {
337        py.enter_polars_series(|| self.series.read().floor())
338    }
339
340    fn shrink_to_fit(&self, py: Python) -> PyResult<()> {
341        py.enter_polars_ok(|| self.series.write().shrink_to_fit())
342    }
343
344    fn dot<'py>(&self, other: &PySeries, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
345        let s = &*self.series.read();
346        let o = &*other.series.read();
347        let lhs_dtype = s.dtype();
348        let rhs_dtype = o.dtype();
349
350        if !lhs_dtype.is_primitive_numeric() {
351            return Err(PyPolarsErr::from(polars_err!(opq = dot, lhs_dtype)).into());
352        };
353        if !rhs_dtype.is_primitive_numeric() {
354            return Err(PyPolarsErr::from(polars_err!(opq = dot, rhs_dtype)).into());
355        }
356
357        let result: AnyValue = if lhs_dtype.is_float() || rhs_dtype.is_float() {
358            py.enter_polars(|| (s * o)?.sum::<f64>())?.into()
359        } else {
360            py.enter_polars(|| (s * o)?.sum::<i64>())?.into()
361        };
362
363        Wrap(result).into_pyobject(py)
364    }
365
366    fn __getstate__<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
367        // Used in pickle/pickling
368        Ok(PyBytes::new(
369            py,
370            &py.enter_polars(|| self.series.read().serialize_to_bytes())?,
371        ))
372    }
373
374    fn __setstate__(&self, py: Python<'_>, state: PyObject) -> PyResult<()> {
375        // Used in pickle/pickling
376        use pyo3::pybacked::PyBackedBytes;
377        match state.extract::<PyBackedBytes>(py) {
378            Ok(bytes) => py.enter_polars(|| {
379                *self.series.write() = Series::deserialize_from_reader(&mut &*bytes)?;
380                PolarsResult::Ok(())
381            }),
382            Err(e) => Err(e),
383        }
384    }
385
386    fn skew(&self, py: Python<'_>, bias: bool) -> PyResult<Option<f64>> {
387        py.enter_polars(|| self.series.read().skew(bias))
388    }
389
390    fn kurtosis(&self, py: Python<'_>, fisher: bool, bias: bool) -> PyResult<Option<f64>> {
391        py.enter_polars(|| self.series.read().kurtosis(fisher, bias))
392    }
393
394    fn cast(
395        &self,
396        py: Python<'_>,
397        dtype: Wrap<DataType>,
398        strict: bool,
399        wrap_numerical: bool,
400    ) -> PyResult<Self> {
401        let options = if wrap_numerical {
402            CastOptions::Overflowing
403        } else if strict {
404            CastOptions::Strict
405        } else {
406            CastOptions::NonStrict
407        };
408        py.enter_polars_series(|| self.series.read().cast_with_options(&dtype.0, options))
409    }
410
411    fn get_chunks(&self) -> PyResult<Vec<PyObject>> {
412        Python::with_gil(|py| {
413            let wrap_s = py_modules::polars(py).getattr(py, "wrap_s").unwrap();
414            flatten_series(&self.series.read())
415                .into_iter()
416                .map(|s| wrap_s.call1(py, (Self::new(s),)))
417                .collect()
418        })
419    }
420
421    fn is_sorted(&self, py: Python<'_>, descending: bool, nulls_last: bool) -> PyResult<bool> {
422        let options = SortOptions {
423            descending,
424            nulls_last,
425            multithreaded: true,
426            maintain_order: false,
427            limit: None,
428        };
429        py.enter_polars(|| self.series.read().is_sorted(options))
430    }
431
432    fn clear(&self) -> Self {
433        self.series.read().clear().into()
434    }
435
436    fn head(&self, py: Python<'_>, n: usize) -> PyResult<Self> {
437        py.enter_polars_series(|| Ok(self.series.read().head(Some(n))))
438    }
439
440    fn tail(&self, py: Python<'_>, n: usize) -> PyResult<Self> {
441        py.enter_polars_series(|| Ok(self.series.read().tail(Some(n))))
442    }
443
444    fn value_counts(
445        &self,
446        py: Python<'_>,
447        sort: bool,
448        parallel: bool,
449        name: String,
450        normalize: bool,
451    ) -> PyResult<PyDataFrame> {
452        py.enter_polars_df(|| {
453            self.series
454                .read()
455                .value_counts(sort, parallel, name.into(), normalize)
456        })
457    }
458
459    #[pyo3(signature = (offset, length))]
460    fn slice(&self, offset: i64, length: Option<usize>) -> Self {
461        let s = self.series.read();
462        let length = length.unwrap_or_else(|| s.len());
463        s.slice(offset, length).into()
464    }
465
466    pub fn not_(&self, py: Python) -> PyResult<Self> {
467        py.enter_polars_series(|| polars_ops::series::negate_bitwise(&self.series.read()))
468    }
469
470    pub fn shrink_dtype(&self, py: Python<'_>) -> PyResult<Self> {
471        py.enter_polars(|| {
472            self.series
473                .read()
474                .shrink_type()
475                .map(Into::into)
476                .map_err(PyPolarsErr::from)
477                .map_err(PyErr::from)
478        })
479    }
480
481    fn str_to_datetime_infer(
482        &self,
483        py: Python,
484        time_unit: Option<Wrap<TimeUnit>>,
485        strict: bool,
486        exact: bool,
487        ambiguous: PySeries,
488    ) -> PyResult<Self> {
489        Ok(py
490            .enter_polars(|| {
491                let s = self.series.read();
492                let datetime_strings = s.str()?;
493                let ambiguous = ambiguous.series.into_inner();
494                let ambiguous = ambiguous.str()?;
495
496                polars_time::prelude::string::infer::to_datetime_with_inferred_tz(
497                    datetime_strings,
498                    time_unit.map_or(TimeUnit::Microseconds, |v| v.0),
499                    strict,
500                    exact,
501                    ambiguous,
502                )
503            })?
504            .into_series()
505            .into())
506    }
507
508    pub fn str_to_decimal_infer(&self, py: Python, inference_length: usize) -> PyResult<Self> {
509        py.enter_polars_series(|| {
510            let s = self.series.read();
511            let ca = s.str()?;
512            ca.to_decimal_infer(inference_length).map(Series::from)
513        })
514    }
515
516    pub fn list_to_struct(
517        &self,
518        py: Python<'_>,
519        width_strat: Wrap<ListToStructWidthStrategy>,
520        name_gen: Option<PyObject>,
521    ) -> PyResult<Self> {
522        py.enter_polars(|| {
523            let get_index_name =
524                name_gen.map(|f| PlanCallback::<usize, String>::new_python(PythonObject(f)));
525            let get_index_name = get_index_name.map(|f| {
526                NameGenerator(Arc::new(move |i| f.call(i).map(PlSmallStr::from)) as Arc<_>)
527            });
528            self.series
529                .read()
530                .list()?
531                .to_struct(&ListToStructArgs::InferWidth {
532                    infer_field_strategy: width_strat.0,
533                    get_index_name,
534                    max_fields: None,
535                })
536                .map(IntoSeries::into_series)
537        })
538        .map(Into::into)
539        .map_err(PyPolarsErr::from)
540        .map_err(PyErr::from)
541    }
542
543    #[cfg(feature = "extract_jsonpath")]
544    fn str_json_decode(
545        &self,
546        py: Python<'_>,
547        infer_schema_length: Option<usize>,
548    ) -> PyResult<Self> {
549        py.enter_polars(|| {
550            let lock = self.series.read();
551            lock.str()?
552                .json_decode(None, infer_schema_length)
553                .map(|s| s.with_name(lock.name().clone()))
554        })
555        .map(Into::into)
556        .map_err(PyPolarsErr::from)
557        .map_err(PyErr::from)
558    }
559}
560
561macro_rules! impl_set_with_mask {
562    ($name:ident, $native:ty, $cast:ident, $variant:ident) => {
563        fn $name(
564            series: &Series,
565            filter: &PySeries,
566            value: Option<$native>,
567        ) -> PolarsResult<Series> {
568            let fs = filter.series.read();
569            let mask = fs.bool()?;
570            let ca = series.$cast()?;
571            let new = ca.set(mask, value)?;
572            Ok(new.into_series())
573        }
574
575        #[pymethods]
576        impl PySeries {
577            #[pyo3(signature = (filter, value))]
578            fn $name(
579                &self,
580                py: Python<'_>,
581                filter: &PySeries,
582                value: Option<$native>,
583            ) -> PyResult<Self> {
584                py.enter_polars_series(|| $name(&self.series.read(), filter, value))
585            }
586        }
587    };
588}
589
590impl_set_with_mask!(set_with_mask_str, &str, str, String);
591impl_set_with_mask!(set_with_mask_f64, f64, f64, Float64);
592impl_set_with_mask!(set_with_mask_f32, f32, f32, Float32);
593impl_set_with_mask!(set_with_mask_u8, u8, u8, UInt8);
594impl_set_with_mask!(set_with_mask_u16, u16, u16, UInt16);
595impl_set_with_mask!(set_with_mask_u32, u32, u32, UInt32);
596impl_set_with_mask!(set_with_mask_u64, u64, u64, UInt64);
597impl_set_with_mask!(set_with_mask_i8, i8, i8, Int8);
598impl_set_with_mask!(set_with_mask_i16, i16, i16, Int16);
599impl_set_with_mask!(set_with_mask_i32, i32, i32, Int32);
600impl_set_with_mask!(set_with_mask_i64, i64, i64, Int64);
601impl_set_with_mask!(set_with_mask_bool, bool, bool, Boolean);
602
603macro_rules! impl_get {
604    ($name:ident, $series_variant:ident, $type:ty) => {
605        #[pymethods]
606        impl PySeries {
607            fn $name(&self, index: i64) -> Option<$type> {
608                let s = self.series.read();
609                if let Ok(ca) = s.$series_variant() {
610                    let index = if index < 0 {
611                        (ca.len() as i64 + index) as usize
612                    } else {
613                        index as usize
614                    };
615                    ca.get(index).map(|r| r.to_owned())
616                } else {
617                    None
618                }
619            }
620        }
621    };
622}
623
624impl_get!(get_f32, f32, f32);
625impl_get!(get_f64, f64, f64);
626impl_get!(get_u8, u8, u8);
627impl_get!(get_u16, u16, u16);
628impl_get!(get_u32, u32, u32);
629impl_get!(get_u64, u64, u64);
630impl_get!(get_i8, i8, i8);
631impl_get!(get_i16, i16, i16);
632impl_get!(get_i32, i32, i32);
633impl_get!(get_i64, i64, i64);
634impl_get!(get_str, str, String);
635
636macro_rules! impl_get_phys {
637    ($name:ident, $series_variant:ident, $type:ty) => {
638        #[pymethods]
639        impl PySeries {
640            fn $name(&self, index: i64) -> Option<$type> {
641                let s = self.series.read();
642                if let Ok(ca) = s.$series_variant() {
643                    let index = if index < 0 {
644                        (ca.len() as i64 + index) as usize
645                    } else {
646                        index as usize
647                    };
648                    ca.physical().get(index)
649                } else {
650                    None
651                }
652            }
653        }
654    };
655}
656
657impl_get_phys!(get_date, date, i32);
658impl_get_phys!(get_datetime, datetime, i64);
659impl_get_phys!(get_duration, duration, i64);