polars_core/utils/
series.rs1use 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 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}