Path: blob/main/crates/polars-testing/src/asserts/utils.rs
8395 views
use std::ops::Not;12use polars_core::datatypes::unpack_dtypes;3use polars_core::prelude::*;4use polars_ops::series::is_close;56/// Configuration options for comparing Series equality.7///8/// Controls the behavior of Series equality comparisons by specifying9/// which aspects to check and the tolerance for floating point comparisons.10pub struct SeriesEqualOptions {11/// Whether to check that the data types match.12pub check_dtypes: bool,13/// Whether to check that the Series names match.14pub check_names: bool,15/// Whether to check that elements appear in the same order.16pub check_order: bool,17/// Whether to check for exact equality (true) or approximate equality (false) for floating point values.18pub check_exact: bool,19/// Relative tolerance for approximate equality of floating point values.20pub rel_tol: f64,21/// Absolute tolerance for approximate equality of floating point values.22pub abs_tol: f64,23/// Whether to compare categorical values as strings.24pub categorical_as_str: bool,25}2627impl Default for SeriesEqualOptions {28/// Creates a new `SeriesEqualOptions` with default settings.29///30/// Default configuration:31/// - Checks data types, names, and order32/// - Uses exact equality comparisons33/// - Sets relative tolerance to 1e-5 and absolute tolerance to 1e-8 for floating point comparisons34/// - Does not convert categorical values to strings for comparison35fn default() -> Self {36Self {37check_dtypes: true,38check_names: true,39check_order: true,40check_exact: true,41rel_tol: 1e-5,42abs_tol: 1e-8,43categorical_as_str: false,44}45}46}4748impl SeriesEqualOptions {49/// Creates a new `SeriesEqualOptions` with default settings.50pub fn new() -> Self {51Self::default()52}5354/// Sets whether to check that data types match.55pub fn with_check_dtypes(mut self, value: bool) -> Self {56self.check_dtypes = value;57self58}5960/// Sets whether to check that Series names match.61pub fn with_check_names(mut self, value: bool) -> Self {62self.check_names = value;63self64}6566/// Sets whether to check that elements appear in the same order.67pub fn with_check_order(mut self, value: bool) -> Self {68self.check_order = value;69self70}7172/// Sets whether to check for exact equality (true) or approximate equality (false) for floating point values.73pub fn with_check_exact(mut self, value: bool) -> Self {74self.check_exact = value;75self76}7778/// Sets the relative tolerance for approximate equality of floating point values.79pub fn with_rel_tol(mut self, value: f64) -> Self {80self.rel_tol = value;81self82}8384/// Sets the absolute tolerance for approximate equality of floating point values.85pub fn with_abs_tol(mut self, value: f64) -> Self {86self.abs_tol = value;87self88}8990/// Sets whether to compare categorical values as strings.91pub fn with_categorical_as_str(mut self, value: bool) -> Self {92self.categorical_as_str = value;93self94}95}9697/// Change a (possibly nested) Categorical data type to a String data type.98fn categorical_dtype_to_string_dtype(dtype: &DataType) -> DataType {99match dtype {100DataType::Categorical(..) => DataType::String,101DataType::List(inner) => {102let inner_cast = categorical_dtype_to_string_dtype(inner);103DataType::List(Box::new(inner_cast))104},105DataType::Array(inner, size) => {106let inner_cast = categorical_dtype_to_string_dtype(inner);107DataType::Array(Box::new(inner_cast), *size)108},109DataType::Struct(fields) => {110let transformed_fields = fields111.iter()112.map(|field| {113Field::new(114field.name().clone(),115categorical_dtype_to_string_dtype(field.dtype()),116)117})118.collect::<Vec<Field>>();119120DataType::Struct(transformed_fields)121},122_ => dtype.clone(),123}124}125126/// Cast a (possibly nested) Categorical Series to a String Series.127fn categorical_series_to_string(s: &Series) -> PolarsResult<Series> {128let dtype = s.dtype();129let noncat_dtype = categorical_dtype_to_string_dtype(dtype);130131if *dtype != noncat_dtype {132Ok(s.cast(&noncat_dtype)?)133} else {134Ok(s.clone())135}136}137138/// Returns true if both DataTypes are floating point types.139fn are_both_floats(left: &DataType, right: &DataType) -> bool {140left.is_float() && right.is_float()141}142143/// Returns true if both DataTypes are list-like (either List or Array types).144fn are_both_lists(left: &DataType, right: &DataType) -> bool {145matches!(left, DataType::List(_) | DataType::Array(_, _))146&& matches!(right, DataType::List(_) | DataType::Array(_, _))147}148149/// Returns true if both DataTypes are struct types.150fn are_both_structs(left: &DataType, right: &DataType) -> bool {151left.is_struct() && right.is_struct()152}153154/// Returns true if both DataTypes are nested types (lists or structs) that contain floating point types within them.155/// First checks if both types are either lists or structs, then unpacks their nested DataTypes to determine if156/// at least one floating point type exists in each of the nested structures.157fn comparing_nested_floats(left: &DataType, right: &DataType) -> bool {158if !are_both_lists(left, right) && !are_both_structs(left, right) {159return false;160}161162let left_dtypes = unpack_dtypes(left, false);163let right_dtypes = unpack_dtypes(right, false);164165let left_has_floats = left_dtypes.iter().any(|dt| dt.is_float());166let right_has_floats = right_dtypes.iter().any(|dt| dt.is_float());167168left_has_floats && right_has_floats169}170171/// Ensures that null values in two Series match exactly and returns an error if any mismatches are found.172fn assert_series_null_values_match(left: &Series, right: &Series) -> PolarsResult<()> {173let null_value_mismatch = left.is_null().not_equal(&right.is_null());174175if null_value_mismatch.any() {176return Err(polars_err!(177assertion_error = "Series",178"null value mismatch",179left.null_count(),180right.null_count()181));182}183184Ok(())185}186187/// Validates that NaN patterns are identical between two float Series, returning error if any mismatches are found.188fn assert_series_nan_values_match(left: &Series, right: &Series) -> PolarsResult<()> {189if !are_both_floats(left.dtype(), right.dtype()) {190return Ok(());191}192let left_nan = left.is_nan()?;193let right_nan = right.is_nan()?;194195let nan_value_mismatch = left_nan.not_equal(&right_nan);196197let left_nan_count = left_nan.sum().unwrap_or(0);198let right_nan_count = right_nan.sum().unwrap_or(0);199200if nan_value_mismatch.any() {201return Err(polars_err!(202assertion_error = "Series",203"nan value mismatch",204left_nan_count,205right_nan_count206));207}208209Ok(())210}211212/// Verifies that two Series have values within a specified tolerance.213///214/// This function checks if the values in `left` and `right` Series that are marked as unequal215/// in the `unequal` boolean array are within the specified relative and absolute tolerances.216///217/// # Arguments218///219/// * `left` - The first Series to compare220/// * `right` - The second Series to compare221/// * `unequal` - Boolean ChunkedArray indicating which elements to check (true = check this element)222/// * `rel_tol` - Relative tolerance (relative to the maximum absolute value of the two Series)223/// * `abs_tol` - Absolute tolerance added to the relative tolerance224///225/// # Returns226///227/// * `Ok(())` if all values are within tolerance228/// * `Err` with details about problematic values if any values exceed the tolerance229///230/// # Formula231///232/// Values are considered within tolerance if:233/// `|left - right| <= max(rel_tol * max(abs(left), abs(right)), abs_tol)` OR values are exactly equal234///235fn assert_series_values_within_tolerance(236left: &Series,237right: &Series,238unequal: &ChunkedArray<BooleanType>,239rel_tol: f64,240abs_tol: f64,241) -> PolarsResult<()> {242let left_unequal = left.filter(unequal)?;243let right_unequal = right.filter(unequal)?;244245let within_tolerance = is_close(&left_unequal, &right_unequal, abs_tol, rel_tol, false)?;246if within_tolerance.all() {247Ok(())248} else {249let exceeded_indices = within_tolerance.not();250let problematic_left = left_unequal.filter(&exceeded_indices)?;251let problematic_right = right_unequal.filter(&exceeded_indices)?;252253Err(polars_err!(254assertion_error = "Series",255"values not within tolerance",256problematic_left,257problematic_right258))259}260}261262/// Compares two Series for equality with configurable options for ordering, exact matching, and tolerance.263///264/// This function verifies that the values in `left` and `right` Series are equal according to265/// the specified comparison criteria. It handles different types including floats and nested types266/// with appropriate equality checks.267///268/// # Arguments269///270/// * `left` - The first Series to compare271/// * `right` - The second Series to compare272/// * `check_order` - If true, elements must be in the same order; if false, Series will be sorted before comparison273/// * `check_exact` - If true, requires exact equality; if false, allows approximate equality for floats within tolerance274/// * `rel_tol` - Relative tolerance for float comparison (used when `check_exact` is false)275/// * `abs_tol` - Absolute tolerance for float comparison (used when `check_exact` is false)276/// * `categorical_as_str` - If true, converts categorical Series to strings before comparison277///278/// # Returns279///280/// * `Ok(())` if Series match according to specified criteria281/// * `Err` with details about mismatches if Series differ282///283/// # Behavior284///285/// 1. Handles categorical Series based on `categorical_as_str` flag286/// 2. Sorts Series if `check_order` is false287/// 3. For nested float types, delegates to `assert_series_nested_values_equal`288/// 4. For non-float types or when `check_exact` is true, requires exact match289/// 5. For float types with approximate matching:290/// - Verifies null values match using `assert_series_null_values_match`291/// - Verifies NaN values match using `assert_series_nan_values_match`292/// - Verifies float values are within tolerance using `assert_series_values_within_tolerance`293///294#[allow(clippy::too_many_arguments)]295fn assert_series_values_equal(296left: &Series,297right: &Series,298check_order: bool,299check_exact: bool,300check_dtypes: bool,301rel_tol: f64,302abs_tol: f64,303categorical_as_str: bool,304) -> PolarsResult<()> {305// When `check_dtypes` is `false` and both series are entirely null,306// consider them equal regardless of their underlying data types307if !check_dtypes && left.dtype() != right.dtype() {308if left.null_count() == left.len() && right.null_count() == right.len() {309return Ok(());310}311}312313let (left, right) = if categorical_as_str {314(315categorical_series_to_string(left)?,316categorical_series_to_string(right)?,317)318} else {319(left.clone(), right.clone())320};321322let (left, right) = if !check_order {323(324left.sort(SortOptions::default())?,325right.sort(SortOptions::default())?,326)327} else {328(left, right)329};330331let unequal = match left.not_equal_missing(&right) {332Ok(result) => result,333Err(_) => {334return Err(polars_err!(335assertion_error = "Series",336"incompatible data types",337left.dtype(),338right.dtype()339));340},341};342343if comparing_nested_floats(left.dtype(), right.dtype()) {344let filtered_left = left.filter(&unequal)?;345let filtered_right = right.filter(&unequal)?;346347match assert_series_nested_values_equal(348&filtered_left,349&filtered_right,350check_exact,351check_dtypes,352rel_tol,353abs_tol,354categorical_as_str,355) {356Ok(_) => return Ok(()),357Err(_) => {358return Err(polars_err!(359assertion_error = "Series",360"nested value mismatch",361left,362right363));364},365}366}367368if !unequal.any() {369return Ok(());370}371372if check_exact || !left.dtype().is_float() || !right.dtype().is_float() {373return Err(polars_err!(374assertion_error = "Series",375"exact value mismatch",376left,377right378));379}380381assert_series_null_values_match(&left, &right)?;382assert_series_nan_values_match(&left, &right)?;383assert_series_values_within_tolerance(&left, &right, &unequal, rel_tol, abs_tol)?;384385Ok(())386}387388/// Recursively compares nested Series structures (lists or structs) for equality.389///390/// This function handles the comparison of complex nested data structures by recursively391/// applying appropriate equality checks based on the nested data type.392///393/// # Arguments394///395/// * `left` - The first nested Series to compare396/// * `right` - The second nested Series to compare397/// * `check_exact` - If true, requires exact equality; if false, allows approximate equality for floats398/// * `rel_tol` - Relative tolerance for float comparison (used when `check_exact` is false)399/// * `abs_tol` - Absolute tolerance for float comparison (used when `check_exact` is false)400/// * `categorical_as_str` - If true, converts categorical Series to strings before comparison401///402/// # Returns403///404/// * `Ok(())` if nested Series match according to specified criteria405/// * `Err` with details about mismatches if Series differ406///407/// # Behavior408///409/// For List types:410/// 1. Iterates through corresponding elements in both Series411/// 2. Returns error if null values are encountered412/// 3. Creates single-element Series for each value and explodes them413/// 4. Recursively calls `assert_series_values_equal` on the exploded Series414///415/// For Struct types:416/// 1. Unnests both struct Series to access their columns417/// 2. Iterates through corresponding columns418/// 3. Recursively calls `assert_series_values_equal` on each column pair419///420fn assert_series_nested_values_equal(421left: &Series,422right: &Series,423check_exact: bool,424check_dtypes: bool,425rel_tol: f64,426abs_tol: f64,427categorical_as_str: bool,428) -> PolarsResult<()> {429if are_both_lists(left.dtype(), right.dtype()) {430let zipped = left.iter().zip(right.iter());431432for (s1, s2) in zipped {433if s1.is_null() || s2.is_null() {434return Err(polars_err!(435assertion_error = "Series",436"nested value mismatch",437s1,438s2439));440} else {441let s1_series = Series::new("".into(), std::slice::from_ref(&s1));442let s2_series = Series::new("".into(), std::slice::from_ref(&s2));443444assert_series_values_equal(445&s1_series.explode(ExplodeOptions {446empty_as_null: true,447keep_nulls: true,448})?,449&s2_series.explode(ExplodeOptions {450empty_as_null: true,451keep_nulls: true,452})?,453true,454check_exact,455check_dtypes,456rel_tol,457abs_tol,458categorical_as_str,459)?460}461}462} else {463let ls = left.struct_()?.clone().unnest();464let rs = right.struct_()?.clone().unnest();465466for col_name in ls.get_column_names() {467let s1_column = ls.column(col_name)?;468let s2_column = rs.column(col_name)?;469470let s1_series = s1_column.as_materialized_series();471let s2_series = s2_column.as_materialized_series();472473assert_series_values_equal(474s1_series,475s2_series,476true,477check_exact,478check_dtypes,479rel_tol,480abs_tol,481categorical_as_str,482)?483}484}485486Ok(())487}488489/// Verifies that two Series are equal according to a set of configurable criteria.490///491/// This function serves as the main entry point for comparing Series, checking various492/// metadata properties before comparing the actual values.493///494/// # Arguments495///496/// * `left` - The first Series to compare497/// * `right` - The second Series to compare498/// * `options` - A `SeriesEqualOptions` struct containing configuration parameters:499/// * `check_names` - If true, verifies Series names match500/// * `check_dtypes` - If true, verifies data types match501/// * `check_order` - If true, elements must be in the same order502/// * `check_exact` - If true, requires exact equality for float values503/// * `rel_tol` - Relative tolerance for float comparison504/// * `abs_tol` - Absolute tolerance for float comparison505/// * `categorical_as_str` - If true, converts categorical Series to strings before comparison506///507/// # Returns508///509/// * `Ok(())` if Series match according to all specified criteria510/// * `Err` with details about the first mismatch encountered:511/// * Length mismatch512/// * Name mismatch (if checking names)513/// * Data type mismatch (if checking dtypes)514/// * Value mismatches (via `assert_series_values_equal`)515///516/// # Order of Checks517///518/// 1. Series length519/// 2. Series names (if `check_names` is true)520/// 3. Data types (if `check_dtypes` is true)521/// 4. Series values (delegated to `assert_series_values_equal`)522///523pub fn assert_series_equal(524left: &Series,525right: &Series,526options: SeriesEqualOptions,527) -> PolarsResult<()> {528// Short-circuit if they're the same series object529if std::ptr::eq(left, right) {530return Ok(());531}532533if left.len() != right.len() {534return Err(polars_err!(535assertion_error = "Series",536"length mismatch",537left.len(),538right.len()539));540}541542if options.check_names && left.name() != right.name() {543return Err(polars_err!(544assertion_error = "Series",545"name mismatch",546left.name(),547right.name()548));549}550551if options.check_dtypes && left.dtype() != right.dtype() {552return Err(polars_err!(553assertion_error = "Series",554"dtype mismatch",555left.dtype(),556right.dtype()557));558}559560assert_series_values_equal(561left,562right,563options.check_order,564options.check_exact,565options.check_dtypes,566options.rel_tol,567options.abs_tol,568options.categorical_as_str,569)570}571572/// Configuration options for comparing DataFrame equality.573///574/// Controls the behavior of DataFrame equality comparisons by specifying575/// which aspects to check and the tolerance for floating point comparisons.576pub struct DataFrameEqualOptions {577/// Whether to check that rows appear in the same order.578pub check_row_order: bool,579/// Whether to check that columns appear in the same order.580pub check_column_order: bool,581/// Whether to check that the data types match for corresponding columns.582pub check_dtypes: bool,583/// Whether to check for exact equality (true) or approximate equality (false) for floating point values.584pub check_exact: bool,585/// Relative tolerance for approximate equality of floating point values.586pub rel_tol: f64,587/// Absolute tolerance for approximate equality of floating point values.588pub abs_tol: f64,589/// Whether to compare categorical values as strings.590pub categorical_as_str: bool,591}592593impl Default for DataFrameEqualOptions {594/// Creates a new `DataFrameEqualOptions` with default settings.595///596/// Default configuration:597/// - Checks row order, column order, and data types598/// - Uses approximate equality comparisons for floating point values599/// - Sets relative tolerance to 1e-5 and absolute tolerance to 1e-8 for floating point comparisons600/// - Does not convert categorical values to strings for comparison601fn default() -> Self {602Self {603check_row_order: true,604check_column_order: true,605check_dtypes: true,606check_exact: false,607rel_tol: 1e-5,608abs_tol: 1e-8,609categorical_as_str: false,610}611}612}613614impl DataFrameEqualOptions {615/// Creates a new `DataFrameEqualOptions` with default settings.616pub fn new() -> Self {617Self::default()618}619620/// Sets whether to check that rows appear in the same order.621pub fn with_check_row_order(mut self, value: bool) -> Self {622self.check_row_order = value;623self624}625626/// Sets whether to check that columns appear in the same order.627pub fn with_check_column_order(mut self, value: bool) -> Self {628self.check_column_order = value;629self630}631632/// Sets whether to check that data types match for corresponding columns.633pub fn with_check_dtypes(mut self, value: bool) -> Self {634self.check_dtypes = value;635self636}637638/// Sets whether to check for exact equality (true) or approximate equality (false) for floating point values.639pub fn with_check_exact(mut self, value: bool) -> Self {640self.check_exact = value;641self642}643644/// Sets the relative tolerance for approximate equality of floating point values.645pub fn with_rel_tol(mut self, value: f64) -> Self {646self.rel_tol = value;647self648}649650/// Sets the absolute tolerance for approximate equality of floating point values.651pub fn with_abs_tol(mut self, value: f64) -> Self {652self.abs_tol = value;653self654}655656/// Sets whether to compare categorical values as strings.657pub fn with_categorical_as_str(mut self, value: bool) -> Self {658self.categorical_as_str = value;659self660}661}662663/// Compares DataFrame schemas for equality based on specified criteria.664///665/// This function validates that two DataFrames have compatible schemas by checking666/// column names, their order, and optionally their data types according to the667/// provided configuration parameters.668///669/// # Arguments670///671/// * `left` - The first DataFrame to compare672/// * `right` - The second DataFrame to compare673/// * `check_dtypes` - If true, requires data types to match for corresponding columns674/// * `check_column_order` - If true, requires columns to appear in the same order675///676/// # Returns677///678/// * `Ok(())` if DataFrame schemas match according to specified criteria679/// * `Err` with details about schema mismatches if DataFrames differ680///681/// # Behavior682///683/// The function performs schema validation in the following order:684///685/// 1. **Fast path**: Returns immediately if schemas are identical686/// 2. **Column name validation**: Ensures both DataFrames have the same set of column names687/// - Reports columns present in left but missing in right688/// - Reports columns present in right but missing in left689/// 3. **Column order validation**: If `check_column_order` is true, verifies columns appear in the same sequence690/// 4. **Data type validation**: If `check_dtypes` is true, ensures corresponding columns have matching data types691/// - When `check_column_order` is false, compares data type sets for equality692/// - When `check_column_order` is true, performs more precise type checking693///694fn assert_dataframe_schema_equal(695left: &DataFrame,696right: &DataFrame,697check_dtypes: bool,698check_column_order: bool,699) -> PolarsResult<()> {700let left_schema = left.schema();701let right_schema = right.schema();702703let ordered_left_cols = left.get_column_names();704let ordered_right_cols = right.get_column_names();705706let left_set: PlHashSet<&PlSmallStr> = ordered_left_cols.iter().copied().collect();707let right_set: PlHashSet<&PlSmallStr> = ordered_right_cols.iter().copied().collect();708709// Fast path for equal DataFrames710if left_schema == right_schema {711return Ok(());712}713714if left_set != right_set {715let left_not_right: Vec<_> = left_set716.iter()717.filter(|col| !right_set.contains(*col))718.collect();719720if !left_not_right.is_empty() {721return Err(polars_err!(722assertion_error = "DataFrames",723format!(724"columns mismatch: {:?} in left, but not in right",725left_not_right726),727format!("{:?}", left_set),728format!("{:?}", right_set)729));730} else {731let right_not_left: Vec<_> = right_set732.iter()733.filter(|col| !left_set.contains(*col))734.collect();735736return Err(polars_err!(737assertion_error = "DataFrames",738format!(739"columns mismatch: {:?} in right, but not in left",740right_not_left741),742format!("{:?}", left_set),743format!("{:?}", right_set)744));745}746}747748if check_column_order && ordered_left_cols != ordered_right_cols {749return Err(polars_err!(750assertion_error = "DataFrames",751"columns are not in the same order",752format!("{:?}", ordered_left_cols),753format!("{:?}", ordered_right_cols)754));755}756757if check_dtypes {758if check_column_order {759let left_dtypes_ordered = left.dtypes();760let right_dtypes_ordered = right.dtypes();761if left_dtypes_ordered != right_dtypes_ordered {762return Err(polars_err!(763assertion_error = "DataFrames",764"dtypes do not match",765format!("{:?}", left_dtypes_ordered),766format!("{:?}", right_dtypes_ordered)767));768}769} else {770let left_dtypes: PlHashSet<DataType> = left.dtypes().into_iter().collect();771let right_dtypes: PlHashSet<DataType> = right.dtypes().into_iter().collect();772if left_dtypes != right_dtypes {773return Err(polars_err!(774assertion_error = "DataFrames",775"dtypes do not match",776format!("{:?}", left_dtypes),777format!("{:?}", right_dtypes)778));779}780}781}782783Ok(())784}785786/// Verifies that two DataFrames are equal according to a set of configurable criteria.787///788/// This function serves as the main entry point for comparing DataFrames, first validating789/// schema compatibility and then comparing the actual data values column by column.790///791/// # Arguments792///793/// * `left` - The first DataFrame to compare794/// * `right` - The second DataFrame to compare795/// * `options` - A `DataFrameEqualOptions` struct containing configuration parameters:796/// * `check_row_order` - If true, rows must be in the same order797/// * `check_column_order` - If true, columns must be in the same order798/// * `check_dtypes` - If true, verifies data types match for corresponding columns799/// * `check_exact` - If true, requires exact equality for float values800/// * `rel_tol` - Relative tolerance for float comparison801/// * `abs_tol` - Absolute tolerance for float comparison802/// * `categorical_as_str` - If true, converts categorical values to strings before comparison803///804/// # Returns805///806/// * `Ok(())` if DataFrames match according to all specified criteria807/// * `Err` with details about the first mismatch encountered:808/// * Schema mismatches (column names, order, or data types)809/// * Height (row count) mismatch810/// * Value mismatches in specific columns811///812/// # Order of Checks813///814/// 1. Schema validation (column names, order, and data types via `assert_dataframe_schema_equal`)815/// 2. DataFrame height (row count)816/// 3. Row ordering (sorts both DataFrames if `check_row_order` is false)817/// 4. Column-by-column value comparison (delegated to `assert_series_values_equal`)818///819/// # Behavior820///821/// When `check_row_order` is false, both DataFrames are sorted using all columns to ensure822/// consistent ordering before value comparison. This allows for row-order-independent equality823/// checking while maintaining deterministic results.824///825pub fn assert_dataframe_equal(826left: &DataFrame,827right: &DataFrame,828options: DataFrameEqualOptions,829) -> PolarsResult<()> {830// Short-circuit if they're the same DataFrame object831if std::ptr::eq(left, right) {832return Ok(());833}834835assert_dataframe_schema_equal(836left,837right,838options.check_dtypes,839options.check_column_order,840)?;841842if left.height() != right.height() {843return Err(polars_err!(844assertion_error = "DataFrames",845"height (row count) mismatch",846left.height(),847right.height()848));849}850851let left_cols = left.get_column_names_owned();852853let (left, right) = if !options.check_row_order {854(855left.sort(left_cols.clone(), SortMultipleOptions::default())?,856right.sort(left_cols.clone(), SortMultipleOptions::default())?,857)858} else {859(left.clone(), right.clone())860};861862for col in left_cols.iter() {863let s_left = left.column(col)?;864let s_right = right.column(col)?;865866let s_left_series = s_left.as_materialized_series();867let s_right_series = s_right.as_materialized_series();868869match assert_series_values_equal(870s_left_series,871s_right_series,872true,873options.check_exact,874options.check_dtypes,875options.rel_tol,876options.abs_tol,877options.categorical_as_str,878) {879Ok(_) => {},880Err(_) => {881return Err(polars_err!(882assertion_error = "DataFrames",883format!("value mismatch for column {:?}", col),884format!("{:?}", s_left_series),885format!("{:?}", s_right_series)886));887},888}889}890891Ok(())892}893894895