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 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 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 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 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 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 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 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 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);