polars_core/frame/
horizontal.rs1use polars_error::{PolarsResult, polars_err};
2
3use super::Column;
4use crate::datatypes::AnyValue;
5use crate::frame::DataFrame;
6use crate::frame::validation::validate_columns_slice;
7
8impl DataFrame {
9 pub unsafe fn hstack_mut_unchecked(&mut self, columns: &[Column]) -> &mut Self {
21 if self.shape() == (0, 0)
22 && let Some(c) = columns.first()
23 {
24 unsafe { self.set_height(c.len()) };
25 }
26
27 unsafe { self.columns_mut() }.extend_from_slice(columns);
28
29 if cfg!(debug_assertions) {
30 if let err @ Err(_) = validate_columns_slice(self.height(), self.columns()) {
31 let initial_width = self.width() - columns.len();
32 unsafe { self.columns_mut() }.truncate(initial_width);
33 err.unwrap();
34 }
35 }
36
37 self
38 }
39
40 pub fn hstack_mut(&mut self, columns: &[Column]) -> PolarsResult<&mut Self> {
55 if self.shape() == (0, 0)
56 && let Some(c) = columns.first()
57 {
58 unsafe { self.set_height(c.len()) };
59 }
60
61 unsafe { self.columns_mut() }.extend_from_slice(columns);
62
63 if let err @ Err(_) = validate_columns_slice(self.height(), self.columns()) {
64 let initial_width = self.width() - columns.len();
65 unsafe { self.columns_mut() }.truncate(initial_width);
66 err?;
67 }
68
69 Ok(self)
70 }
71}
72
73pub fn concat_df_horizontal(
77 dfs: &[DataFrame],
78 check_duplicates: bool,
79 strict: bool,
80 unit_length_as_scalar: bool,
81) -> PolarsResult<DataFrame> {
82 if dfs.is_empty() {
83 return Err(polars_err!(ComputeError: "cannot concat empty dataframes"));
84 }
85 let heights = || dfs.iter().map(|df| df.height());
86 let output_height = if unit_length_as_scalar {
87 heights().filter(|h| *h != 1).max().unwrap_or(1)
88 } else {
89 heights().max().unwrap()
90 };
91
92 let owned_df;
93
94 let mut out_width = 0;
95
96 let all_equal_height = dfs.iter().filter(|df| df.shape() != (0, 0)).all(|df| {
97 out_width += df.width();
98 df.height() == output_height
99 });
100
101 let dfs = if !all_equal_height {
103 if strict {
104 return Err(
105 polars_err!(ShapeMismatch: "cannot concat dataframes with different heights in 'strict' mode"),
106 );
107 }
108 out_width = 0;
109
110 owned_df = dfs
111 .iter()
112 .filter(|df| df.shape() != (0, 0))
113 .cloned()
114 .map(|mut df| {
115 out_width += df.width();
116 let h = df.height();
117
118 if h != output_height {
119 if unit_length_as_scalar && h == 1 {
120 unsafe { df.columns_mut() }.iter_mut().for_each(|c| {
123 let Column::Scalar(s) = c else {
124 panic!("only supported for scalars");
125 };
126
127 *c = Column::Scalar(s.resize(output_height));
128 });
129 } else {
130 let diff = output_height - h;
131
132 unsafe { df.columns_mut() }.iter_mut().for_each(|c| {
135 *c = c.extend_constant(AnyValue::Null, diff).unwrap();
136 });
137 }
138 unsafe {
139 df.set_height(output_height);
140 }
141 }
142
143 df
144 })
145 .collect::<Vec<_>>();
146 owned_df.as_slice()
147 } else {
148 dfs
149 };
150
151 let mut acc_cols = Vec::with_capacity(out_width);
152
153 for df in dfs {
154 acc_cols.extend(df.columns().iter().cloned());
155 }
156
157 let df = if check_duplicates {
158 DataFrame::new(output_height, acc_cols)?
159 } else {
160 unsafe { DataFrame::new_unchecked(output_height, acc_cols) }
161 };
162
163 Ok(df)
164}
165
166#[cfg(test)]
167mod tests {
168 use polars_error::PolarsError;
169
170 #[test]
171 fn test_hstack_mut_empty_frame_height_validation() {
172 use crate::frame::DataFrame;
173 use crate::prelude::{Column, DataType};
174 let mut df = DataFrame::empty();
175 let result = df.hstack_mut(&[
176 Column::full_null("a".into(), 1, &DataType::Null),
177 Column::full_null("b".into(), 3, &DataType::Null),
178 ]);
179
180 assert!(
181 matches!(result, Err(PolarsError::ShapeMismatch(_))),
182 "expected shape mismatch error"
183 );
184 assert_eq!(df.width(), 0);
186 }
187
188 #[test]
189 fn test_scalar_broadcast_over_empty_frame() {
190 use crate::prelude::*;
191
192 let empty = df!["a" => Vec::<i64>::new()].unwrap();
194 let scalar = df!["b" => [1i64]].unwrap();
195 let out = super::concat_df_horizontal(&[empty, scalar], true, false, true).unwrap();
196 assert_eq!(out.height(), 0);
197 assert_eq!(out.width(), 2);
198
199 let non_empty = df!["a" => [10i64, 20, 30]].unwrap();
201 let scalar = df!["b" => [1i64]].unwrap();
202 let out = super::concat_df_horizontal(&[non_empty, scalar], true, false, true).unwrap();
203 assert_eq!(out.height(), 3);
204 assert_eq!(
205 out.column("b").unwrap().as_materialized_series(),
206 &Series::new("b".into(), [1i64, 1, 1])
207 );
208 }
209}