1use std::borrow::Cow;
2
3use either::Either;
4
5use super::*;
6
7impl DataFrame {
8 pub(crate) fn transpose_from_dtype(
9 &self,
10 dtype: &DataType,
11 keep_names_as: Option<PlSmallStr>,
12 names_out: &[PlSmallStr],
13 ) -> PolarsResult<DataFrame> {
14 let new_width = self.height();
15 let new_height = self.width();
16 let mut cols_t = match keep_names_as {
18 None => Vec::<Column>::with_capacity(new_width),
19 Some(name) => {
20 let mut tmp = Vec::<Column>::with_capacity(new_width + 1);
21 tmp.push(
22 StringChunked::from_iter_values(
23 name,
24 self.get_column_names_owned().into_iter(),
25 )
26 .into_column(),
27 );
28 tmp
29 },
30 };
31
32 let cols = self.columns();
33 match dtype {
34 #[cfg(feature = "dtype-i8")]
35 DataType::Int8 => numeric_transpose::<Int8Type>(cols, names_out, &mut cols_t),
36 #[cfg(feature = "dtype-i16")]
37 DataType::Int16 => numeric_transpose::<Int16Type>(cols, names_out, &mut cols_t),
38 DataType::Int32 => numeric_transpose::<Int32Type>(cols, names_out, &mut cols_t),
39 DataType::Int64 => numeric_transpose::<Int64Type>(cols, names_out, &mut cols_t),
40 #[cfg(feature = "dtype-u8")]
41 DataType::UInt8 => numeric_transpose::<UInt8Type>(cols, names_out, &mut cols_t),
42 #[cfg(feature = "dtype-u16")]
43 DataType::UInt16 => numeric_transpose::<UInt16Type>(cols, names_out, &mut cols_t),
44 DataType::UInt32 => numeric_transpose::<UInt32Type>(cols, names_out, &mut cols_t),
45 DataType::UInt64 => numeric_transpose::<UInt64Type>(cols, names_out, &mut cols_t),
46 DataType::Float32 => numeric_transpose::<Float32Type>(cols, names_out, &mut cols_t),
47 DataType::Float64 => numeric_transpose::<Float64Type>(cols, names_out, &mut cols_t),
48 #[cfg(feature = "object")]
49 DataType::Object(_) => {
50 polars_bail!(InvalidOperation: "Object dtype not supported in 'transpose'")
52 },
53 _ => {
54 let phys_dtype = dtype.to_physical();
55 let mut buffers = (0..new_width)
56 .map(|_| {
57 let buf: AnyValueBufferTrusted = (&phys_dtype, new_height).into();
58 buf
59 })
60 .collect::<Vec<_>>();
61
62 let columns = self
63 .materialized_column_iter()
64 .map(|s| s.cast(dtype)?.cast(&phys_dtype))
66 .collect::<PolarsResult<Vec<_>>>()?;
67
68 for series in &columns {
71 polars_ensure!(
72 series.dtype() == &phys_dtype,
73 ComputeError: "cannot transpose with supertype: {}", dtype
74 );
75 for (av, buf) in series.iter().zip(buffers.iter_mut()) {
76 unsafe {
78 buf.add_unchecked_borrowed_physical(&av);
79 }
80 }
81 }
82 cols_t.extend(buffers.into_iter().zip(names_out).map(|(buf, name)| {
83 let mut s = unsafe { buf.into_series().cast_unchecked(dtype).unwrap() };
85 s.rename(name.clone());
86 s.into()
87 }));
88 },
89 };
90
91 DataFrame::new(new_height, cols_t)
92 }
93
94 pub fn transpose(
95 &mut self,
96 keep_names_as: Option<&str>,
97 new_col_names: Option<Either<String, Vec<String>>>,
98 ) -> PolarsResult<DataFrame> {
99 let new_col_names = match new_col_names {
100 None => None,
101 Some(Either::Left(v)) => Some(Either::Left(v.into())),
102 Some(Either::Right(v)) => Some(Either::Right(
103 v.into_iter().map(Into::into).collect::<Vec<_>>(),
104 )),
105 };
106
107 self.transpose_impl(keep_names_as, new_col_names)
108 }
109 pub fn transpose_impl(
111 &mut self,
112 keep_names_as: Option<&str>,
113 new_col_names: Option<Either<PlSmallStr, Vec<PlSmallStr>>>,
114 ) -> PolarsResult<DataFrame> {
115 self.rechunk_mut_par();
117
118 let mut df = Cow::Borrowed(self); let names_out = match new_col_names {
120 None => (0..self.height())
121 .map(|i| format_pl_smallstr!("column_{i}"))
122 .collect(),
123 Some(cn) => match cn {
124 Either::Left(name) => {
125 let new_names = self.column(name.as_str()).and_then(|x| x.str())?;
126 polars_ensure!(new_names.null_count() == 0, ComputeError: "Column with new names can't have null values");
127 df = Cow::Owned(self.drop(name.as_str())?);
128 new_names.no_null_iter().map(PlSmallStr::from_str).collect()
129 },
130 Either::Right(names) => {
131 polars_ensure!(names.len() == self.height(), ShapeMismatch: "Length of new column names must be the same as the row count");
132 names
133 },
134 },
135 };
136 if let Some(cn) = keep_names_as {
137 polars_ensure!(names_out.iter().all(|a| a.as_str() != cn), Duplicate: "{} is already in output column names", cn)
140 }
141 polars_ensure!(
142 df.height() != 0 && df.width() != 0,
143 NoData: "unable to transpose an empty DataFrame"
144 );
145 let dtype = df.get_supertype().unwrap()?;
146 df.transpose_from_dtype(&dtype, keep_names_as.map(PlSmallStr::from_str), &names_out)
147 }
148}
149
150#[inline]
151unsafe fn add_value<T: NumericNative>(
152 values_buf_ptr: usize,
153 col_idx: usize,
154 row_idx: usize,
155 value: T,
156) {
157 let vec_ref: &mut Vec<Vec<T>> = &mut *(values_buf_ptr as *mut Vec<Vec<T>>);
158 let column = vec_ref.get_unchecked_mut(col_idx);
159 let el_ptr = column.as_mut_ptr();
160 *el_ptr.add(row_idx) = value;
161}
162
163pub(super) fn numeric_transpose<T: PolarsNumericType>(
166 cols: &[Column],
167 names_out: &[PlSmallStr],
168 cols_t: &mut Vec<Column>,
169) {
170 let new_width = cols[0].len();
171 let new_height = cols.len();
172
173 let has_nulls = cols.iter().any(|s| s.null_count() > 0);
174
175 let mut values_buf: Vec<Vec<T::Native>> = (0..new_width)
176 .map(|_| Vec::with_capacity(new_height))
177 .collect();
178 let mut validity_buf: Vec<_> = if has_nulls {
179 (0..new_width).map(|_| vec![true; new_height]).collect()
181 } else {
182 (0..new_width).map(|_| vec![]).collect()
183 };
184
185 let values_buf_ptr = &mut values_buf as *mut Vec<Vec<T::Native>> as usize;
187 let validity_buf_ptr = &mut validity_buf as *mut Vec<Vec<bool>> as usize;
188
189 RAYON.install(|| {
190 cols.iter()
191 .map(Column::as_materialized_series)
192 .enumerate()
193 .for_each(|(row_idx, s)| {
194 let s = s.cast(&T::get_static_dtype()).unwrap();
195 let ca = s.unpack::<T>().unwrap();
196
197 if has_nulls {
202 for (col_idx, opt_v) in ca.iter().enumerate() {
203 match opt_v {
204 None => unsafe {
205 let validity_vec: &mut Vec<Vec<bool>> =
206 &mut *(validity_buf_ptr as *mut Vec<Vec<bool>>);
207 let column = validity_vec.get_unchecked_mut(col_idx);
208 let el_ptr = column.as_mut_ptr();
209 *el_ptr.add(row_idx) = false;
210 add_value(values_buf_ptr, col_idx, row_idx, T::Native::default());
214 },
215 Some(v) => unsafe {
216 add_value(values_buf_ptr, col_idx, row_idx, v);
217 },
218 }
219 }
220 } else {
221 for (col_idx, v) in ca.into_no_null_iter().enumerate() {
222 unsafe {
223 let column: &mut Vec<Vec<T::Native>> =
224 &mut *(values_buf_ptr as *mut Vec<Vec<T::Native>>);
225 let el_ptr = column.get_unchecked_mut(col_idx).as_mut_ptr();
226 *el_ptr.add(row_idx) = v;
227 }
228 }
229 }
230 })
231 });
232
233 let par_iter = values_buf
234 .into_par_iter()
235 .zip(validity_buf)
236 .zip(names_out)
237 .map(|((mut values, validity), name)| {
238 unsafe {
241 values.set_len(new_height);
242 }
243
244 let validity = if has_nulls {
245 let validity = Bitmap::from_trusted_len_iter(validity.iter().copied());
246 if validity.unset_bits() > 0 {
247 Some(validity)
248 } else {
249 None
250 }
251 } else {
252 None
253 };
254
255 let arr = PrimitiveArray::<T::Native>::new(
256 T::get_static_dtype().to_arrow(CompatLevel::newest()),
257 values.into(),
258 validity,
259 );
260 ChunkedArray::<T>::with_chunk(name.clone(), arr).into_column()
261 });
262 RAYON.install(|| cols_t.par_extend(par_iter));
263}
264
265#[cfg(test)]
266mod test {
267 use super::*;
268
269 #[test]
270 fn test_transpose() -> PolarsResult<()> {
271 let mut df = df![
272 "a" => [1, 2, 3],
273 "b" => [10, 20, 30],
274 ]?;
275
276 let out = df.transpose(None, None)?;
277 let expected = df![
278 "column_0" => [1, 10],
279 "column_1" => [2, 20],
280 "column_2" => [3, 30],
281
282 ]?;
283 assert!(out.equals_missing(&expected));
284
285 let mut df = df![
286 "a" => [Some(1), None, Some(3)],
287 "b" => [Some(10), Some(20), None],
288 ]?;
289 let out = df.transpose(None, None)?;
290 let expected = df![
291 "column_0" => [1, 10],
292 "column_1" => [None, Some(20)],
293 "column_2" => [Some(3), None],
294
295 ]?;
296 assert!(out.equals_missing(&expected));
297
298 let mut df = df![
299 "a" => ["a", "b", "c"],
300 "b" => [Some(10), Some(20), None],
301 ]?;
302 let out = df.transpose(None, None)?;
303 let expected = df![
304 "column_0" => ["a", "10"],
305 "column_1" => ["b", "20"],
306 "column_2" => [Some("c"), None],
307
308 ]?;
309 assert!(out.equals_missing(&expected));
310 Ok(())
311 }
312}