Path: blob/main/crates/polars-core/src/frame/horizontal.rs
6940 views
use polars_error::{PolarsResult, polars_err};12use super::Column;3use crate::datatypes::AnyValue;4use crate::frame::DataFrame;56impl DataFrame {7/// Add columns horizontally.8///9/// # Safety10/// The caller must ensure:11/// - the length of all [`Column`] is equal to the height of this [`DataFrame`]12/// - the columns names are unique13///14/// Note: If `self` is empty, `self.height` will always be overridden by the height of the first15/// column in `columns`.16///17/// Note that on a debug build this will panic on duplicates / height mismatch.18pub unsafe fn hstack_mut_unchecked(&mut self, columns: &[Column]) -> &mut Self {19self.clear_schema();20self.columns.extend_from_slice(columns);2122if cfg!(debug_assertions) {23if let err @ Err(_) = DataFrame::validate_columns_slice(&self.columns) {24// Reset DataFrame state to before extend.25self.columns.truncate(self.columns.len() - columns.len());26err.unwrap();27}28}2930if let Some(c) = self.columns.first() {31unsafe { self.set_height(c.len()) };32}3334self35}3637/// Add multiple [`Column`] to a [`DataFrame`].38/// Errors if the resulting DataFrame columns have duplicate names or unequal heights.39///40/// Note: If `self` is empty, `self.height` will always be overridden by the height of the first41/// column in `columns`.42///43/// # Example44///45/// ```rust46/// # use polars_core::prelude::*;47/// fn stack(df: &mut DataFrame, columns: &[Column]) {48/// df.hstack_mut(columns);49/// }50/// ```51pub fn hstack_mut(&mut self, columns: &[Column]) -> PolarsResult<&mut Self> {52self.clear_schema();53self.columns.extend_from_slice(columns);5455if let err @ Err(_) = DataFrame::validate_columns_slice(&self.columns) {56// Reset DataFrame state to before extend.57self.columns.truncate(self.columns.len() - columns.len());58err?;59}6061if let Some(c) = self.columns.first() {62unsafe { self.set_height(c.len()) };63}6465Ok(self)66}67}6869/// Concat [`DataFrame`]s horizontally.70/// Concat horizontally and extend with null values if lengths don't match71pub fn concat_df_horizontal(dfs: &[DataFrame], check_duplicates: bool) -> PolarsResult<DataFrame> {72let output_height = dfs73.iter()74.map(|df| df.height())75.max()76.ok_or_else(|| polars_err!(ComputeError: "cannot concat empty dataframes"))?;7778let owned_df;7980let mut out_width = 0;8182let all_equal_height = dfs.iter().all(|df| {83out_width += df.width();84df.height() == output_height85});8687// if not all equal length, extend the DataFrame with nulls88let dfs = if !all_equal_height {89out_width = 0;9091owned_df = dfs92.iter()93.cloned()94.map(|mut df| {95out_width += df.width();9697if df.height() != output_height {98let diff = output_height - df.height();99100// SAFETY: We extend each column with nulls to the point of being of length101// `output_height`. Then, we set the height of the resulting dataframe.102unsafe { df.get_columns_mut() }.iter_mut().for_each(|c| {103*c = c.extend_constant(AnyValue::Null, diff).unwrap();104});105df.clear_schema();106unsafe {107df.set_height(output_height);108}109}110df111})112.collect::<Vec<_>>();113owned_df.as_slice()114} else {115dfs116};117118let mut acc_cols = Vec::with_capacity(out_width);119120for df in dfs {121acc_cols.extend(df.get_columns().iter().cloned());122}123124if check_duplicates {125DataFrame::validate_columns_slice(&acc_cols)?;126}127128let df = unsafe { DataFrame::new_no_checks_height_from_first(acc_cols) };129130Ok(df)131}132133#[cfg(test)]134mod tests {135use polars_error::PolarsError;136137#[test]138fn test_hstack_mut_empty_frame_height_validation() {139use crate::frame::DataFrame;140use crate::prelude::{Column, DataType};141let mut df = DataFrame::empty();142let result = df.hstack_mut(&[143Column::full_null("a".into(), 1, &DataType::Null),144Column::full_null("b".into(), 3, &DataType::Null),145]);146147assert!(148matches!(result, Err(PolarsError::ShapeMismatch(_))),149"expected shape mismatch error"150);151152// Ensure the DataFrame is not mutated in the error case.153assert_eq!(df.width(), 0);154}155}156157158