Skip to main content

polars_core/utils/
series.rs

1use std::rc::Rc;
2
3use polars_compute::find_validity_mismatch::find_validity_mismatch;
4use polars_compute::gather::take_unchecked;
5
6use crate::prelude::*;
7use crate::series::amortized_iter::AmortSeries;
8
9/// A utility that allocates an [`AmortSeries`]. The applied function can then use that
10/// series container to save heap allocations and swap arrow arrays.
11pub fn with_unstable_series<F, T>(dtype: &DataType, f: F) -> T
12where
13    F: Fn(&mut AmortSeries) -> T,
14{
15    let container = Series::full_null(PlSmallStr::EMPTY, 0, dtype);
16    let mut us = AmortSeries::new(Rc::new(container));
17
18    f(&mut us)
19}
20
21pub fn check_is_valid_struct_cast(
22    input_dtype: &DataType,
23    output_dtype: &DataType,
24    output_name: &PlSmallStr,
25) -> PolarsResult<()> {
26    use DataType as D;
27
28    let err = |msg: &str| -> PolarsError {
29        polars_err!(
30            InvalidOperation:
31            "cast from `{}` to `{}` failed in column '{}': {}\n\n\
32            Ensure that any output struct has the same number of fields as the input, and that all struct field names in the output are present in the input.\n\
33            Use `strict=False` to force the cast, and Polars will select the first n fields from the struct.",
34            input_dtype,
35            output_dtype,
36            output_name,
37            msg,
38        )
39    };
40
41    #[allow(clippy::single_match)]
42    match (input_dtype, output_dtype) {
43        #[cfg(feature = "dtype-struct")]
44        (D::Struct(l_fields), D::Struct(r_fields)) => {
45            if l_fields.len() != r_fields.len() {
46                return Err(err(&format!(
47                    "structs do not have the same number of fields: {} vs {}",
48                    l_fields.len(),
49                    r_fields.len(),
50                )));
51            }
52            for (l, r) in Iterator::zip(l_fields.iter(), r_fields.iter()) {
53                if l.name() != r.name() {
54                    return Err(err(&format!(
55                        "structs field name mismatch: {} vs {}",
56                        l.name(),
57                        r.name()
58                    )));
59                }
60                check_is_valid_struct_cast(l.dtype(), r.dtype(), output_name)?;
61            }
62            Ok(())
63        },
64        (D::List(input_dtype), D::List(output_dtype)) => {
65            check_is_valid_struct_cast(input_dtype, output_dtype, output_name)
66        },
67        #[cfg(feature = "dtype-array")]
68        (D::Array(input_dtype, _), D::Array(output_dtype, _)) => {
69            check_is_valid_struct_cast(input_dtype, output_dtype, output_name)
70        },
71        #[cfg(feature = "dtype-array")]
72        (D::List(input_dtype), D::Array(output_dtype, _))
73        | (D::Array(input_dtype, _), D::List(output_dtype)) => {
74            check_is_valid_struct_cast(input_dtype, output_dtype, output_name)
75        },
76        _ => Ok(()),
77    }
78}
79
80pub fn handle_casting_failures(input: &Series, output: &Series) -> PolarsResult<()> {
81    check_is_valid_struct_cast(input.dtype(), output.dtype(), output.name())?;
82
83    // Casting to a Map merges duplicate keys, so its entries are not positionally
84    // comparable with the input's -- which `find_validity_mismatch` requires. Strictness
85    // still holds, since the key and value child casts run with the same options.
86    #[cfg(feature = "dtype-map")]
87    if output.dtype().contains_map() {
88        return Ok(());
89    }
90
91    let mut idxs = Vec::new();
92    input.find_validity_mismatch(output, &mut idxs);
93
94    if idxs.is_empty() {
95        return Ok(());
96    }
97
98    let num_failures = idxs.len();
99    let failures = input.take_slice(&idxs[..num_failures.min(10)])?;
100
101    let additional_info = match (input.dtype(), output.dtype()) {
102        (DataType::String, DataType::Date | DataType::Datetime(_, _)) => {
103            "\n\nYou might want to try:\n\
104            - setting `strict=False` to set values that cannot be converted to `null`\n\
105            - using `str.strptime`, `str.to_date`, or `str.to_datetime` and providing a format string"
106        },
107        #[cfg(feature = "dtype-categorical")]
108        (DataType::String, DataType::Enum(_, _)) => {
109            "\n\nEnsure that all values in the input column are present in the categories of the enum datatype."
110        },
111        _ if failures.len() < num_failures => {
112            "\n\nDid not show all failed cases as there were too many."
113        },
114        _ => "",
115    };
116
117    polars_bail!(
118        InvalidOperation:
119        "conversion from `{}` to `{}` failed in column '{}' for {} out of {} values: {}{}",
120        input.dtype(),
121        output.dtype(),
122        output.name(),
123        num_failures,
124        input.len(),
125        failures.fmt_list(),
126        additional_info,
127    )
128}
129
130pub fn handle_array_casting_failures(input: &dyn Array, output: &dyn Array) -> PolarsResult<()> {
131    let mut idxs = Vec::new();
132    find_validity_mismatch(input, output, &mut idxs);
133    if idxs.is_empty() {
134        return Ok(());
135    }
136
137    let num_failures = idxs.len();
138    let failures = PrimitiveArray::with_slice(&idxs[..num_failures.min(10)], |idxs| unsafe {
139        take_unchecked(input, &idxs)
140    });
141
142    polars_bail!(
143        InvalidOperation:
144        "conversion from `{}` to `{}` failed for {} out of {} values: {}",
145        DataType::from_arrow(input.dtype(), None),
146        DataType::from_arrow(output.dtype(), None),
147        num_failures,
148        input.len(),
149        Series::try_from((PlSmallStr::EMPTY, failures))?,
150    )
151}