Skip to main content

polars_core/utils/
series.rs

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