use polars_error::{PolarsResult, polars_err};
use super::Column;
use crate::datatypes::AnyValue;
use crate::frame::DataFrame;
use crate::frame::validation::validate_columns_slice;
impl DataFrame {
pub unsafe fn hstack_mut_unchecked(&mut self, columns: &[Column]) -> &mut Self {
if self.shape() == (0, 0)
&& let Some(c) = columns.first()
{
unsafe { self.set_height(c.len()) };
}
unsafe { self.columns_mut() }.extend_from_slice(columns);
if cfg!(debug_assertions) {
if let err @ Err(_) = validate_columns_slice(self.height(), self.columns()) {
let initial_width = self.width() - columns.len();
unsafe { self.columns_mut() }.truncate(initial_width);
err.unwrap();
}
}
self
}
pub fn hstack_mut(&mut self, columns: &[Column]) -> PolarsResult<&mut Self> {
if self.shape() == (0, 0)
&& let Some(c) = columns.first()
{
unsafe { self.set_height(c.len()) };
}
unsafe { self.columns_mut() }.extend_from_slice(columns);
if let err @ Err(_) = validate_columns_slice(self.height(), self.columns()) {
let initial_width = self.width() - columns.len();
unsafe { self.columns_mut() }.truncate(initial_width);
err?;
}
Ok(self)
}
}
pub fn concat_df_horizontal(
dfs: &[DataFrame],
check_duplicates: bool,
strict: bool,
unit_length_as_scalar: bool,
) -> PolarsResult<DataFrame> {
if dfs.is_empty() {
return Err(polars_err!(ComputeError: "cannot concat empty dataframes"));
}
let heights = || dfs.iter().map(|df| df.height());
let output_height = if unit_length_as_scalar {
heights().filter(|h| *h != 1).max().unwrap_or(1)
} else {
heights().max().unwrap()
};
let owned_df;
let mut out_width = 0;
let all_equal_height = dfs.iter().filter(|df| df.shape() != (0, 0)).all(|df| {
out_width += df.width();
df.height() == output_height
});
let dfs = if !all_equal_height {
if strict {
return Err(
polars_err!(ShapeMismatch: "cannot concat dataframes with different heights in 'strict' mode"),
);
}
out_width = 0;
owned_df = dfs
.iter()
.filter(|df| df.shape() != (0, 0))
.cloned()
.map(|mut df| {
out_width += df.width();
let h = df.height();
if h != output_height {
if unit_length_as_scalar && h == 1 {
unsafe { df.columns_mut() }.iter_mut().for_each(|c| {
let Column::Scalar(s) = c else {
panic!("only supported for scalars");
};
*c = Column::Scalar(s.resize(output_height));
});
} else {
let diff = output_height - h;
unsafe { df.columns_mut() }.iter_mut().for_each(|c| {
*c = c.extend_constant(AnyValue::Null, diff).unwrap();
});
}
unsafe {
df.set_height(output_height);
}
}
df
})
.collect::<Vec<_>>();
owned_df.as_slice()
} else {
dfs
};
let mut acc_cols = Vec::with_capacity(out_width);
for df in dfs {
acc_cols.extend(df.columns().iter().cloned());
}
let df = if check_duplicates {
DataFrame::new(output_height, acc_cols)?
} else {
unsafe { DataFrame::new_unchecked(output_height, acc_cols) }
};
Ok(df)
}
#[cfg(test)]
mod tests {
use polars_error::PolarsError;
#[test]
fn test_hstack_mut_empty_frame_height_validation() {
use crate::frame::DataFrame;
use crate::prelude::{Column, DataType};
let mut df = DataFrame::empty();
let result = df.hstack_mut(&[
Column::full_null("a".into(), 1, &DataType::Null),
Column::full_null("b".into(), 3, &DataType::Null),
]);
assert!(
matches!(result, Err(PolarsError::ShapeMismatch(_))),
"expected shape mismatch error"
);
assert_eq!(df.width(), 0);
}
#[test]
fn test_scalar_broadcast_over_empty_frame() {
use crate::prelude::*;
let empty = df!["a" => Vec::<i64>::new()].unwrap();
let scalar = df!["b" => [1i64]].unwrap();
let out = super::concat_df_horizontal(&[empty, scalar], true, false, true).unwrap();
assert_eq!(out.height(), 0);
assert_eq!(out.width(), 2);
let non_empty = df!["a" => [10i64, 20, 30]].unwrap();
let scalar = df!["b" => [1i64]].unwrap();
let out = super::concat_df_horizontal(&[non_empty, scalar], true, false, true).unwrap();
assert_eq!(out.height(), 3);
assert_eq!(
out.column("b").unwrap().as_materialized_series(),
&Series::new("b".into(), [1i64, 1, 1])
);
}
}