Path: blob/main/crates/polars-testing/src/asserts/utils.rs
6940 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<()> {305let (left, right) = if categorical_as_str {306(307categorical_series_to_string(left)?,308categorical_series_to_string(right)?,309)310} else {311(left.clone(), right.clone())312};313314let (left, right) = if !check_order {315(316left.sort(SortOptions::default())?,317right.sort(SortOptions::default())?,318)319} else {320(left, right)321};322323// When `check_dtypes` is `false` and both series are entirely null,324// consider them equal regardless of their underlying data types325if !check_dtypes && left.dtype() != right.dtype() {326if left.null_count() == left.len() && right.null_count() == right.len() {327return Ok(());328}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 left_rechunked = left.rechunk();431let right_rechunked = right.rechunk();432433let zipped = left_rechunked.iter().zip(right_rechunked.iter());434435for (s1, s2) in zipped {436if s1.is_null() || s2.is_null() {437return Err(polars_err!(438assertion_error = "Series",439"nested value mismatch",440s1,441s2442));443} else {444let s1_series = Series::new("".into(), std::slice::from_ref(&s1));445let s2_series = Series::new("".into(), std::slice::from_ref(&s2));446447match assert_series_values_equal(448&s1_series.explode(false)?,449&s2_series.explode(false)?,450true,451check_exact,452check_dtypes,453rel_tol,454abs_tol,455categorical_as_str,456) {457Ok(_) => continue,458Err(e) => return Err(e),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();472473match assert_series_values_equal(474s1_series,475s2_series,476true,477check_exact,478check_dtypes,479rel_tol,480abs_tol,481categorical_as_str,482) {483Ok(_) => continue,484Err(e) => return Err(e),485}486}487}488489Ok(())490}491492/// Verifies that two Series are equal according to a set of configurable criteria.493///494/// This function serves as the main entry point for comparing Series, checking various495/// metadata properties before comparing the actual values.496///497/// # Arguments498///499/// * `left` - The first Series to compare500/// * `right` - The second Series to compare501/// * `options` - A `SeriesEqualOptions` struct containing configuration parameters:502/// * `check_names` - If true, verifies Series names match503/// * `check_dtypes` - If true, verifies data types match504/// * `check_order` - If true, elements must be in the same order505/// * `check_exact` - If true, requires exact equality for float values506/// * `rel_tol` - Relative tolerance for float comparison507/// * `abs_tol` - Absolute tolerance for float comparison508/// * `categorical_as_str` - If true, converts categorical Series to strings before comparison509///510/// # Returns511///512/// * `Ok(())` if Series match according to all specified criteria513/// * `Err` with details about the first mismatch encountered:514/// * Length mismatch515/// * Name mismatch (if checking names)516/// * Data type mismatch (if checking dtypes)517/// * Value mismatches (via `assert_series_values_equal`)518///519/// # Order of Checks520///521/// 1. Series length522/// 2. Series names (if `check_names` is true)523/// 3. Data types (if `check_dtypes` is true)524/// 4. Series values (delegated to `assert_series_values_equal`)525///526pub fn assert_series_equal(527left: &Series,528right: &Series,529options: SeriesEqualOptions,530) -> PolarsResult<()> {531// Short-circuit if they're the same series object532if std::ptr::eq(left, right) {533return Ok(());534}535536if left.len() != right.len() {537return Err(polars_err!(538assertion_error = "Series",539"length mismatch",540left.len(),541right.len()542));543}544545if options.check_names && left.name() != right.name() {546return Err(polars_err!(547assertion_error = "Series",548"name mismatch",549left.name(),550right.name()551));552}553554if options.check_dtypes && left.dtype() != right.dtype() {555return Err(polars_err!(556assertion_error = "Series",557"dtype mismatch",558left.dtype(),559right.dtype()560));561}562563assert_series_values_equal(564left,565right,566options.check_order,567options.check_exact,568options.check_dtypes,569options.rel_tol,570options.abs_tol,571options.categorical_as_str,572)573}574575/// Configuration options for comparing DataFrame equality.576///577/// Controls the behavior of DataFrame equality comparisons by specifying578/// which aspects to check and the tolerance for floating point comparisons.579pub struct DataFrameEqualOptions {580/// Whether to check that rows appear in the same order.581pub check_row_order: bool,582/// Whether to check that columns appear in the same order.583pub check_column_order: bool,584/// Whether to check that the data types match for corresponding columns.585pub check_dtypes: bool,586/// Whether to check for exact equality (true) or approximate equality (false) for floating point values.587pub check_exact: bool,588/// Relative tolerance for approximate equality of floating point values.589pub rel_tol: f64,590/// Absolute tolerance for approximate equality of floating point values.591pub abs_tol: f64,592/// Whether to compare categorical values as strings.593pub categorical_as_str: bool,594}595596impl Default for DataFrameEqualOptions {597/// Creates a new `DataFrameEqualOptions` with default settings.598///599/// Default configuration:600/// - Checks row order, column order, and data types601/// - Uses approximate equality comparisons for floating point values602/// - Sets relative tolerance to 1e-5 and absolute tolerance to 1e-8 for floating point comparisons603/// - Does not convert categorical values to strings for comparison604fn default() -> Self {605Self {606check_row_order: true,607check_column_order: true,608check_dtypes: true,609check_exact: false,610rel_tol: 1e-5,611abs_tol: 1e-8,612categorical_as_str: false,613}614}615}616617impl DataFrameEqualOptions {618/// Creates a new `DataFrameEqualOptions` with default settings.619pub fn new() -> Self {620Self::default()621}622623/// Sets whether to check that rows appear in the same order.624pub fn with_check_row_order(mut self, value: bool) -> Self {625self.check_row_order = value;626self627}628629/// Sets whether to check that columns appear in the same order.630pub fn with_check_column_order(mut self, value: bool) -> Self {631self.check_column_order = value;632self633}634635/// Sets whether to check that data types match for corresponding columns.636pub fn with_check_dtypes(mut self, value: bool) -> Self {637self.check_dtypes = value;638self639}640641/// Sets whether to check for exact equality (true) or approximate equality (false) for floating point values.642pub fn with_check_exact(mut self, value: bool) -> Self {643self.check_exact = value;644self645}646647/// Sets the relative tolerance for approximate equality of floating point values.648pub fn with_rel_tol(mut self, value: f64) -> Self {649self.rel_tol = value;650self651}652653/// Sets the absolute tolerance for approximate equality of floating point values.654pub fn with_abs_tol(mut self, value: f64) -> Self {655self.abs_tol = value;656self657}658659/// Sets whether to compare categorical values as strings.660pub fn with_categorical_as_str(mut self, value: bool) -> Self {661self.categorical_as_str = value;662self663}664}665666/// Compares DataFrame schemas for equality based on specified criteria.667///668/// This function validates that two DataFrames have compatible schemas by checking669/// column names, their order, and optionally their data types according to the670/// provided configuration parameters.671///672/// # Arguments673///674/// * `left` - The first DataFrame to compare675/// * `right` - The second DataFrame to compare676/// * `check_dtypes` - If true, requires data types to match for corresponding columns677/// * `check_column_order` - If true, requires columns to appear in the same order678///679/// # Returns680///681/// * `Ok(())` if DataFrame schemas match according to specified criteria682/// * `Err` with details about schema mismatches if DataFrames differ683///684/// # Behavior685///686/// The function performs schema validation in the following order:687///688/// 1. **Fast path**: Returns immediately if schemas are identical689/// 2. **Column name validation**: Ensures both DataFrames have the same set of column names690/// - Reports columns present in left but missing in right691/// - Reports columns present in right but missing in left692/// 3. **Column order validation**: If `check_column_order` is true, verifies columns appear in the same sequence693/// 4. **Data type validation**: If `check_dtypes` is true, ensures corresponding columns have matching data types694/// - When `check_column_order` is false, compares data type sets for equality695/// - When `check_column_order` is true, performs more precise type checking696///697fn assert_dataframe_schema_equal(698left: &DataFrame,699right: &DataFrame,700check_dtypes: bool,701check_column_order: bool,702) -> PolarsResult<()> {703let left_schema = left.schema();704let right_schema = right.schema();705706let ordered_left_cols = left.get_column_names();707let ordered_right_cols = right.get_column_names();708709let left_set: PlHashSet<&PlSmallStr> = ordered_left_cols.iter().copied().collect();710let right_set: PlHashSet<&PlSmallStr> = ordered_right_cols.iter().copied().collect();711712// Fast path for equal DataFrames713if left_schema == right_schema {714return Ok(());715}716717if left_set != right_set {718let left_not_right: Vec<_> = left_set719.iter()720.filter(|col| !right_set.contains(*col))721.collect();722723if !left_not_right.is_empty() {724return Err(polars_err!(725assertion_error = "DataFrames",726format!(727"columns mismatch: {:?} in left, but not in right",728left_not_right729),730format!("{:?}", left_set),731format!("{:?}", right_set)732));733} else {734let right_not_left: Vec<_> = right_set735.iter()736.filter(|col| !left_set.contains(*col))737.collect();738739return Err(polars_err!(740assertion_error = "DataFrames",741format!(742"columns mismatch: {:?} in right, but not in left",743right_not_left744),745format!("{:?}", left_set),746format!("{:?}", right_set)747));748}749}750751if check_column_order && ordered_left_cols != ordered_right_cols {752return Err(polars_err!(753assertion_error = "DataFrames",754"columns are not in the same order",755format!("{:?}", ordered_left_cols),756format!("{:?}", ordered_right_cols)757));758}759760if check_dtypes {761if check_column_order {762let left_dtypes_ordered = left.dtypes();763let right_dtypes_ordered = right.dtypes();764if left_dtypes_ordered != right_dtypes_ordered {765return Err(polars_err!(766assertion_error = "DataFrames",767"dtypes do not match",768format!("{:?}", left_dtypes_ordered),769format!("{:?}", right_dtypes_ordered)770));771}772} else {773let left_dtypes: PlHashSet<DataType> = left.dtypes().into_iter().collect();774let right_dtypes: PlHashSet<DataType> = right.dtypes().into_iter().collect();775if left_dtypes != right_dtypes {776return Err(polars_err!(777assertion_error = "DataFrames",778"dtypes do not match",779format!("{:?}", left_dtypes),780format!("{:?}", right_dtypes)781));782}783}784}785786Ok(())787}788789/// Verifies that two DataFrames are equal according to a set of configurable criteria.790///791/// This function serves as the main entry point for comparing DataFrames, first validating792/// schema compatibility and then comparing the actual data values column by column.793///794/// # Arguments795///796/// * `left` - The first DataFrame to compare797/// * `right` - The second DataFrame to compare798/// * `options` - A `DataFrameEqualOptions` struct containing configuration parameters:799/// * `check_row_order` - If true, rows must be in the same order800/// * `check_column_order` - If true, columns must be in the same order801/// * `check_dtypes` - If true, verifies data types match for corresponding columns802/// * `check_exact` - If true, requires exact equality for float values803/// * `rel_tol` - Relative tolerance for float comparison804/// * `abs_tol` - Absolute tolerance for float comparison805/// * `categorical_as_str` - If true, converts categorical values to strings before comparison806///807/// # Returns808///809/// * `Ok(())` if DataFrames match according to all specified criteria810/// * `Err` with details about the first mismatch encountered:811/// * Schema mismatches (column names, order, or data types)812/// * Height (row count) mismatch813/// * Value mismatches in specific columns814///815/// # Order of Checks816///817/// 1. Schema validation (column names, order, and data types via `assert_dataframe_schema_equal`)818/// 2. DataFrame height (row count)819/// 3. Row ordering (sorts both DataFrames if `check_row_order` is false)820/// 4. Column-by-column value comparison (delegated to `assert_series_values_equal`)821///822/// # Behavior823///824/// When `check_row_order` is false, both DataFrames are sorted using all columns to ensure825/// consistent ordering before value comparison. This allows for row-order-independent equality826/// checking while maintaining deterministic results.827///828pub fn assert_dataframe_equal(829left: &DataFrame,830right: &DataFrame,831options: DataFrameEqualOptions,832) -> PolarsResult<()> {833// Short-circuit if they're the same DataFrame object834if std::ptr::eq(left, right) {835return Ok(());836}837838assert_dataframe_schema_equal(839left,840right,841options.check_dtypes,842options.check_column_order,843)?;844845if left.height() != right.height() {846return Err(polars_err!(847assertion_error = "DataFrames",848"height (row count) mismatch",849left.height(),850right.height()851));852}853854let left_cols = left.get_column_names_owned();855856let (left, right) = if !options.check_row_order {857(858left.sort(left_cols.clone(), SortMultipleOptions::default())?,859right.sort(left_cols.clone(), SortMultipleOptions::default())?,860)861} else {862(left.clone(), right.clone())863};864865for col in left_cols.iter() {866let s_left = left.column(col)?;867let s_right = right.column(col)?;868869let s_left_series = s_left.as_materialized_series();870let s_right_series = s_right.as_materialized_series();871872match assert_series_values_equal(873s_left_series,874s_right_series,875true,876options.check_exact,877options.check_dtypes,878options.rel_tol,879options.abs_tol,880options.categorical_as_str,881) {882Ok(_) => {},883Err(_) => {884return Err(polars_err!(885assertion_error = "DataFrames",886format!("value mismatch for column {:?}", col),887format!("{:?}", s_left_series),888format!("{:?}", s_right_series)889));890},891}892}893894Ok(())895}896897898