Skip to main content

polars_core/series/ops/
reshape.rs

1use std::borrow::Cow;
2
3use arrow::array::*;
4use arrow::bitmap::Bitmap;
5use arrow::offset::{Offsets, OffsetsBuffer};
6use polars_compute::gather::sublist::list::array_to_unit_list;
7use polars_error::{PolarsResult, polars_bail, polars_ensure};
8use polars_utils::format_tuple;
9
10use crate::chunked_array::builder::get_list_builder;
11use crate::datatypes::{DataType, ListChunked};
12use crate::prelude::{IntoSeries, Series, *};
13
14impl Series {
15    /// Recurse nested types until we are at the leaf array.
16    pub fn get_leaf_array(&self) -> Series {
17        let s = self;
18        match s.dtype() {
19            #[cfg(feature = "dtype-array")]
20            DataType::Array(dtype, _) => {
21                let ca = s.array().unwrap();
22                let chunks = ca
23                    .downcast_iter()
24                    .map(|arr| arr.values().clone())
25                    .collect::<Vec<_>>();
26                // Safety: guarded by the type system
27                unsafe { Series::from_chunks_and_dtype_unchecked(s.name().clone(), chunks, dtype) }
28                    .get_leaf_array()
29            },
30            DataType::List(dtype) => {
31                let ca = s.list().unwrap();
32                let chunks = ca
33                    .downcast_iter()
34                    .map(|arr| arr.values().clone())
35                    .collect::<Vec<_>>();
36                // Safety: guarded by the type system
37                unsafe { Series::from_chunks_and_dtype_unchecked(s.name().clone(), chunks, dtype) }
38                    .get_leaf_array()
39            },
40            _ => s.clone(),
41        }
42    }
43
44    /// TODO: Move this somewhere else?
45    pub fn list_offsets_and_validities_recursive(
46        &self,
47    ) -> (Vec<OffsetsBuffer<i64>>, Vec<Option<Bitmap>>) {
48        let mut offsets = vec![];
49        let mut validities = vec![];
50
51        let mut s = self.rechunk();
52
53        while let DataType::List(_) = s.dtype() {
54            let ca = s.list().unwrap();
55            offsets.push(ca.offsets().unwrap());
56            validities.push(ca.rechunk_validity());
57            s = ca.get_inner();
58        }
59
60        (offsets, validities)
61    }
62
63    /// Wrap each element of this Series in a single-element list.
64    /// A Series `[1, 2, 3]` becomes `[[1], [2], [3]]`.
65    pub fn to_unit_list(&self) -> ListChunked {
66        let mut ca = ListChunked::from_chunk_iter(
67            self.name().clone(),
68            self.chunks()
69                .iter()
70                .map(|arr| array_to_unit_list(arr.clone())),
71        );
72        ca.set_inner_dtype(self.dtype().clone());
73        ca.set_fast_explode();
74        ca
75    }
76
77    /// Convert the values of this Series to a ListChunked with a length of 1,
78    /// so a Series of `[1, 2, 3]` becomes `[[1, 2, 3]]`.
79    pub fn implode(&self) -> PolarsResult<ListChunked> {
80        let s = self;
81        let s = s.rechunk();
82        let values = s.array_ref(0);
83
84        let offsets = vec![0i64, values.len() as i64];
85        let inner_type = s.dtype();
86
87        let dtype = ListArray::<i64>::default_datatype(values.dtype().clone());
88
89        // SAFETY: offsets are correct.
90        let arr = unsafe {
91            ListArray::new(
92                dtype,
93                Offsets::new_unchecked(offsets).into(),
94                values.clone(),
95                None,
96            )
97        };
98
99        let mut ca = ListChunked::with_chunk(s.name().clone(), arr);
100        unsafe { ca.to_logical(inner_type.clone()) };
101        ca.set_fast_explode();
102        Ok(ca)
103    }
104
105    #[cfg(feature = "dtype-array")]
106    pub fn reshape_array(&self, dimensions: &[ReshapeDimension]) -> PolarsResult<Series> {
107        polars_ensure!(
108            !dimensions.is_empty(),
109            InvalidOperation: "at least one dimension must be specified"
110        );
111
112        let leaf_array = self
113            .trim_lists_to_normalized_offsets()
114            .as_ref()
115            .unwrap_or(self)
116            .get_leaf_array()
117            .rechunk();
118        let size = leaf_array.len();
119
120        let mut total_dim_size = 1;
121        let mut num_infers = 0;
122        for &dim in dimensions {
123            match dim {
124                ReshapeDimension::Infer => num_infers += 1,
125                ReshapeDimension::Specified(dim) => total_dim_size *= dim.get() as usize,
126            }
127        }
128
129        polars_ensure!(num_infers <= 1, InvalidOperation: "can only specify one inferred dimension");
130
131        if size == 0 {
132            polars_ensure!(
133                num_infers > 0 || total_dim_size == 0,
134                InvalidOperation: "cannot reshape empty array into shape without zero dimension: {}",
135                format_tuple!(dimensions),
136            );
137
138            let mut prev_arrow_dtype = leaf_array
139                .dtype()
140                .to_physical()
141                .to_arrow(CompatLevel::newest());
142            let mut prev_dtype = leaf_array.dtype().clone();
143            let mut prev_array = leaf_array.chunks()[0].clone();
144
145            // @NOTE: We need to collect the iterator here because it is lazily processed.
146            let mut current_length = dimensions[0].get_or_infer(0);
147            let len_iter = dimensions[1..]
148                .iter()
149                .map(|d| {
150                    let length = current_length as usize;
151                    current_length *= d.get_or_infer(0);
152                    length
153                })
154                .collect::<Vec<_>>();
155
156            // We pop the outer dimension as that is the height of the series.
157            for (dim, length) in dimensions[1..].iter().zip(len_iter).rev() {
158                // Infer dimension if needed
159                let dim = dim.get_or_infer(0);
160                prev_arrow_dtype = prev_arrow_dtype.to_fixed_size_list(dim as usize, true);
161                prev_dtype = DataType::Array(Box::new(prev_dtype), dim as usize);
162
163                prev_array =
164                    FixedSizeListArray::new(prev_arrow_dtype.clone(), length, prev_array, None)
165                        .boxed();
166            }
167
168            return Ok(unsafe {
169                Series::from_chunks_and_dtype_unchecked(
170                    leaf_array.name().clone(),
171                    vec![prev_array],
172                    &prev_dtype,
173                )
174            });
175        }
176
177        polars_ensure!(
178            total_dim_size > 0,
179            InvalidOperation: "cannot reshape non-empty array into shape containing a zero dimension: {}",
180            format_tuple!(dimensions)
181        );
182
183        polars_ensure!(
184            size.is_multiple_of(total_dim_size),
185            InvalidOperation: "cannot reshape array of size {} into shape {}", size, format_tuple!(dimensions)
186        );
187
188        let leaf_array = leaf_array.rechunk();
189        let mut prev_arrow_dtype = leaf_array
190            .dtype()
191            .to_physical()
192            .to_arrow(CompatLevel::newest());
193        let mut prev_dtype = leaf_array.dtype().clone();
194        let mut prev_array = leaf_array.chunks()[0].clone();
195        let inferred_size = (size / total_dim_size) as u64;
196        let outer_dimension = dimensions[0].get_or_infer(inferred_size);
197
198        // We pop the outer dimension as that is the height of the series.
199        for dim in dimensions[1..].iter().rev() {
200            // Infer dimension if needed
201            let dim = dim.get_or_infer(inferred_size);
202            prev_arrow_dtype = prev_arrow_dtype.to_fixed_size_list(dim as usize, true);
203            prev_dtype = DataType::Array(Box::new(prev_dtype), dim as usize);
204
205            prev_array = FixedSizeListArray::new(
206                prev_arrow_dtype.clone(),
207                prev_array.len() / dim as usize,
208                prev_array,
209                None,
210            )
211            .boxed();
212        }
213
214        polars_ensure!(
215            prev_array.len() as u64 == outer_dimension,
216            InvalidOperation: "cannot reshape array of size {} into shape {}", size, format_tuple!(dimensions)
217        );
218
219        Ok(unsafe {
220            Series::from_chunks_and_dtype_unchecked(
221                leaf_array.name().clone(),
222                vec![prev_array],
223                &prev_dtype,
224            )
225        })
226    }
227
228    pub fn reshape_list(&self, dimensions: &[ReshapeDimension]) -> PolarsResult<Series> {
229        polars_ensure!(
230            !dimensions.is_empty(),
231            InvalidOperation: "at least one dimension must be specified"
232        );
233
234        let s = self;
235        let s = if let DataType::List(_) = s.dtype() {
236            Cow::Owned(s.explode(ExplodeOptions {
237                empty_as_null: false,
238                keep_nulls: true,
239            })?)
240        } else {
241            Cow::Borrowed(s)
242        };
243
244        let s_ref = s.as_ref();
245
246        // let dimensions = dimensions.to_vec();
247
248        match dimensions.len() {
249            1 => {
250                polars_ensure!(
251                    dimensions[0].get().is_none_or( |dim| dim as usize == s_ref.len()),
252                    InvalidOperation: "cannot reshape len {} into shape {:?}", s_ref.len(), dimensions,
253                );
254                Ok(s_ref.clone())
255            },
256            2 => {
257                let rows = dimensions[0];
258                let cols = dimensions[1];
259
260                if s_ref.is_empty() {
261                    if rows.get_or_infer(0) == 0 && cols.get_or_infer(0) <= 1 {
262                        return Ok(s_ref.to_unit_list().into_series());
263                    } else {
264                        polars_bail!(InvalidOperation: "cannot reshape len 0 into shape {}", format_tuple!(dimensions))
265                    }
266                }
267
268                use ReshapeDimension as RD;
269                // Infer dimension.
270
271                let (rows, cols) = match (rows, cols) {
272                    (RD::Infer, RD::Specified(cols)) if cols.get() >= 1 => {
273                        (s_ref.len() as u64 / cols.get(), cols.get())
274                    },
275                    (RD::Specified(rows), RD::Infer) if rows.get() >= 1 => {
276                        (rows.get(), s_ref.len() as u64 / rows.get())
277                    },
278                    (RD::Infer, RD::Infer) => (s_ref.len() as u64, 1u64),
279                    (RD::Specified(rows), RD::Specified(cols)) => (rows.get(), cols.get()),
280                    _ => polars_bail!(InvalidOperation: "reshape of non-zero list into zero list"),
281                };
282
283                // Fast path, we can create a unit list so we only allocate offsets.
284                if rows as usize == s_ref.len() && cols == 1 {
285                    return Ok(s_ref.to_unit_list().into_series());
286                }
287
288                polars_ensure!(
289                    (rows*cols) as usize == s_ref.len() && rows >= 1 && cols >= 1,
290                    InvalidOperation: "cannot reshape len {} into shape {:?}", s_ref.len(), dimensions,
291                );
292
293                let mut builder =
294                    get_list_builder(s_ref.dtype(), s_ref.len(), rows as usize, s.name().clone());
295
296                let mut offset = 0u64;
297                for _ in 0..rows {
298                    let row = s_ref.slice(offset as i64, cols as usize);
299                    builder.append_series(&row).unwrap();
300                    offset += cols;
301                }
302                Ok(builder.finish().into_series())
303            },
304            _ => {
305                polars_bail!(InvalidOperation: "more than two dimensions not supported in reshaping to List.\n\nConsider reshaping to Array type.");
306            },
307        }
308    }
309}
310
311#[cfg(test)]
312mod test {
313    use super::*;
314    use crate::prelude::*;
315
316    #[test]
317    fn test_to_list() -> PolarsResult<()> {
318        let s = Series::new("a".into(), &[1, 2, 3]);
319
320        let mut builder = get_list_builder(s.dtype(), s.len(), 1, s.name().clone());
321        builder.append_series(&s).unwrap();
322        let expected = builder.finish();
323
324        let out = s.implode()?;
325        assert!(expected.into_series().equals(&out.into_series()));
326
327        Ok(())
328    }
329
330    #[test]
331    fn test_reshape() -> PolarsResult<()> {
332        let s = Series::new("a".into(), &[1, 2, 3, 4]);
333
334        for (dims, list_len) in [
335            (&[-1, 1], 4),
336            (&[4, 1], 4),
337            (&[2, 2], 2),
338            (&[-1, 2], 2),
339            (&[2, -1], 2),
340        ] {
341            let dims = dims
342                .iter()
343                .map(|&v| ReshapeDimension::new(v))
344                .collect::<Vec<_>>();
345            let out = s.reshape_list(&dims)?;
346            assert_eq!(out.len(), list_len);
347            assert!(matches!(out.dtype(), DataType::List(_)));
348            assert_eq!(
349                out.explode(ExplodeOptions {
350                    empty_as_null: true,
351                    keep_nulls: true,
352                })?
353                .len(),
354                4
355            );
356        }
357
358        Ok(())
359    }
360}