Path: blob/main/crates/polars-core/src/frame/horizontal.rs
8458 views
use polars_error::{PolarsResult, polars_err};12use super::Column;3use crate::datatypes::AnyValue;4use crate::frame::DataFrame;5use crate::frame::validation::validate_columns_slice;67impl DataFrame {8/// Add columns horizontally.9///10/// # Safety11/// The caller must ensure:12/// - the length of all [`Column`] is equal to the height of this [`DataFrame`]13/// - the columns names are unique14///15/// Note: If `self` is empty, `self.height` will always be overridden by the height of the first16/// column in `columns`.17///18/// Note that on a debug build this will panic on duplicates / height mismatch.19pub unsafe fn hstack_mut_unchecked(&mut self, columns: &[Column]) -> &mut Self {20if self.shape() == (0, 0)21&& let Some(c) = columns.first()22{23unsafe { self.set_height(c.len()) };24}2526unsafe { self.columns_mut() }.extend_from_slice(columns);2728if cfg!(debug_assertions) {29if let err @ Err(_) = validate_columns_slice(self.height(), self.columns()) {30let initial_width = self.width() - columns.len();31unsafe { self.columns_mut() }.truncate(initial_width);32err.unwrap();33}34}3536self37}3839/// Add multiple [`Column`] to a [`DataFrame`].40/// Errors if the resulting DataFrame columns have duplicate names or unequal heights.41///42/// Note: If `self` is empty, `self.height` will always be overridden by the height of the first43/// column in `columns`.44///45/// # Example46///47/// ```rust48/// # use polars_core::prelude::*;49/// fn stack(df: &mut DataFrame, columns: &[Column]) {50/// df.hstack_mut(columns);51/// }52/// ```53pub fn hstack_mut(&mut self, columns: &[Column]) -> PolarsResult<&mut Self> {54if self.shape() == (0, 0)55&& let Some(c) = columns.first()56{57unsafe { self.set_height(c.len()) };58}5960unsafe { self.columns_mut() }.extend_from_slice(columns);6162if let err @ Err(_) = validate_columns_slice(self.height(), self.columns()) {63let initial_width = self.width() - columns.len();64unsafe { self.columns_mut() }.truncate(initial_width);65err?;66}6768Ok(self)69}70}7172/// Concat [`DataFrame`]s horizontally.73///74/// If the lengths don't match and strict is false we pad with nulls, or return a `ShapeError` if strict is true.75pub fn concat_df_horizontal(76dfs: &[DataFrame],77check_duplicates: bool,78strict: bool,79unit_length_as_scalar: bool,80) -> PolarsResult<DataFrame> {81let output_height = dfs82.iter()83.map(|df| df.height())84.max()85.ok_or_else(|| polars_err!(ComputeError: "cannot concat empty dataframes"))?;8687let owned_df;8889let mut out_width = 0;9091let all_equal_height = dfs.iter().all(|df| {92out_width += df.width();93df.height() == output_height94});9596// if not all equal length, extend the DataFrame with nulls97let dfs = if !all_equal_height {98if strict {99return Err(100polars_err!(ShapeMismatch: "cannot concat dataframes with different heights in 'strict' mode"),101);102}103out_width = 0;104105owned_df = dfs106.iter()107.cloned()108.map(|mut df| {109out_width += df.width();110let h = df.height();111112if h != output_height {113if unit_length_as_scalar && h == 1 {114// SAFETY: We extend each scalar column length to115// `output_height`. Then, we set the height of the resulting dataframe.116unsafe { df.columns_mut() }.iter_mut().for_each(|c| {117let Column::Scalar(s) = c else {118panic!("only supported for scalars");119};120121*c = Column::Scalar(s.resize(output_height));122});123} else {124let diff = output_height - h;125126// SAFETY: We extend each column with nulls to the point of being of length127// `output_height`. Then, we set the height of the resulting dataframe.128unsafe { df.columns_mut() }.iter_mut().for_each(|c| {129*c = c.extend_constant(AnyValue::Null, diff).unwrap();130});131}132unsafe {133df.set_height(output_height);134}135}136137df138})139.collect::<Vec<_>>();140owned_df.as_slice()141} else {142dfs143};144145let mut acc_cols = Vec::with_capacity(out_width);146147for df in dfs {148acc_cols.extend(df.columns().iter().cloned());149}150151let df = if check_duplicates {152DataFrame::new(output_height, acc_cols)?153} else {154unsafe { DataFrame::new_unchecked(output_height, acc_cols) }155};156157Ok(df)158}159160#[cfg(test)]161mod tests {162use polars_error::PolarsError;163164#[test]165fn test_hstack_mut_empty_frame_height_validation() {166use crate::frame::DataFrame;167use crate::prelude::{Column, DataType};168let mut df = DataFrame::empty();169let result = df.hstack_mut(&[170Column::full_null("a".into(), 1, &DataType::Null),171Column::full_null("b".into(), 3, &DataType::Null),172]);173174assert!(175matches!(result, Err(PolarsError::ShapeMismatch(_))),176"expected shape mismatch error"177);178179// Ensure the DataFrame is not mutated in the error case.180assert_eq!(df.width(), 0);181}182}183184185