Skip to main content

polars_testing/asserts/
frame.rs

1/// Asserts that two DataFrames are equal according to the specified options.
2///
3/// This macro compares two Polars DataFrame objects and panics with a detailed error message if they are not equal.
4/// It provides two forms:
5/// - With custom comparison options
6/// - With default comparison options
7///
8/// # Example
9///
10/// ```
11/// use polars_core::prelude::*;
12/// use polars_testing::assert_dataframe_equal;
13/// use polars_testing::asserts::DataFrameEqualOptions;
14///
15/// // Create two DataFrames to compare
16/// let df1 = df! {
17///     "a" => [1, 2, 3],
18///     "b" => [4.0, 5.0, 6.0],
19/// }.unwrap();
20/// let df2 = df! {
21///     "a" => [1, 2, 3],
22///     "b" => [4.0, 5.0, 6.0],
23/// }.unwrap();
24///
25/// // Assert with default options
26/// assert_dataframe_equal!(&df1, &df2);
27///
28/// // Assert with custom options
29/// let options = DataFrameEqualOptions::default()
30///     .with_check_exact(true)
31///     .with_check_row_order(false);
32/// assert_dataframe_equal!(&df1, &df2, options);
33/// ```
34///
35/// # Panics
36///
37/// Panics when the DataFrames are not equal according to the specified comparison criteria.
38///
39#[macro_export]
40macro_rules! assert_dataframe_equal {
41    ($left:expr, $right:expr $(, $options:expr)?) => {
42        #[allow(unused_assignments)]
43        #[allow(unused_mut)]
44        let mut options = $crate::asserts::DataFrameEqualOptions::default();
45        $(options = $options;)?
46
47        match $crate::asserts::assert_dataframe_equal($left, $right, options) {
48            Ok(_) => {},
49            Err(e) => panic!("{}", e),
50        }
51    };
52}
53
54#[cfg(test)]
55mod tests {
56    #[allow(unused_imports)]
57    use polars_core::prelude::*;
58
59    // Testing default struct implementation
60    #[test]
61    fn test_dataframe_equal_options() {
62        let options = crate::asserts::DataFrameEqualOptions::default();
63
64        assert!(options.check_row_order);
65        assert!(options.check_column_order);
66        assert!(options.check_dtypes);
67        assert!(!options.check_exact);
68        assert_eq!(options.rel_tol, 1e-5);
69        assert_eq!(options.abs_tol, 1e-8);
70        assert!(!options.categorical_as_str);
71    }
72
73    // Testing dataframe schema equality parameters
74    #[test]
75    #[should_panic(expected = "height (row count) mismatch")]
76    fn test_dataframe_height_mismatch() {
77        let df1 = DataFrame::new_infer_height(vec![
78            Series::new("col1".into(), &[1, 2]).into(),
79            Series::new("col2".into(), &["a", "b"]).into(),
80        ])
81        .unwrap();
82
83        let df2 = DataFrame::new_infer_height(vec![
84            Series::new("col1".into(), &[1, 2, 3]).into(),
85            Series::new("col2".into(), &["a", "b", "c"]).into(),
86        ])
87        .unwrap();
88
89        assert_dataframe_equal!(&df1, &df2);
90    }
91
92    #[test]
93    #[should_panic(expected = "columns mismatch")]
94    fn test_dataframe_column_mismatch() {
95        let df1 = DataFrame::new_infer_height(vec![
96            Series::new("col1".into(), &[1, 2, 3]).into(),
97            Series::new("col2".into(), &["a", "b", "c"]).into(),
98        ])
99        .unwrap();
100
101        let df2 = DataFrame::new_infer_height(vec![
102            Series::new("col1".into(), &[1, 2, 3]).into(),
103            Series::new("different_col".into(), &["a", "b", "c"]).into(),
104        ])
105        .unwrap();
106
107        assert_dataframe_equal!(&df1, &df2);
108    }
109
110    #[test]
111    #[should_panic(expected = "dtypes do not match")]
112    fn test_dataframe_dtype_mismatch() {
113        let df1 = DataFrame::new_infer_height(vec![
114            Series::new("col1".into(), &[1, 2, 3]).into(),
115            Series::new("col2".into(), &["a", "b", "c"]).into(),
116        ])
117        .unwrap();
118
119        let df2 = DataFrame::new_infer_height(vec![
120            Series::new("col1".into(), &[1.0, 2.0, 3.0]).into(),
121            Series::new("col2".into(), &["a", "b", "c"]).into(),
122        ])
123        .unwrap();
124
125        assert_dataframe_equal!(&df1, &df2);
126    }
127
128    #[test]
129    fn test_dataframe_dtype_mismatch_ignored() {
130        let df1 = DataFrame::new_infer_height(vec![
131            Series::new("col1".into(), &[1, 2, 3]).into(),
132            Series::new("col2".into(), &["a", "b", "c"]).into(),
133        ])
134        .unwrap();
135
136        let df2 = DataFrame::new_infer_height(vec![
137            Series::new("col1".into(), &[1.0, 2.0, 3.0]).into(),
138            Series::new("col2".into(), &["a", "b", "c"]).into(),
139        ])
140        .unwrap();
141
142        let options = crate::asserts::DataFrameEqualOptions::default().with_check_dtypes(false);
143        assert_dataframe_equal!(&df1, &df2, options);
144    }
145
146    #[test]
147    #[should_panic(expected = "columns are not in the same order")]
148    fn test_dataframe_column_order_mismatch() {
149        let df1 = DataFrame::new_infer_height(vec![
150            Series::new("col1".into(), &[1, 2, 3]).into(),
151            Series::new("col2".into(), &["a", "b", "c"]).into(),
152        ])
153        .unwrap();
154
155        let df2 = DataFrame::new_infer_height(vec![
156            Series::new("col2".into(), &["a", "b", "c"]).into(),
157            Series::new("col1".into(), &[1, 2, 3]).into(),
158        ])
159        .unwrap();
160
161        assert_dataframe_equal!(&df1, &df2);
162    }
163
164    #[test]
165    fn test_dataframe_column_order_mismatch_ignored() {
166        let df1 = DataFrame::new_infer_height(vec![
167            Series::new("col1".into(), &[1, 2, 3]).into(),
168            Series::new("col2".into(), &["a", "b", "c"]).into(),
169        ])
170        .unwrap();
171
172        let df2 = DataFrame::new_infer_height(vec![
173            Series::new("col2".into(), &["a", "b", "c"]).into(),
174            Series::new("col1".into(), &[1, 2, 3]).into(),
175        ])
176        .unwrap();
177
178        let options =
179            crate::asserts::DataFrameEqualOptions::default().with_check_column_order(false);
180        assert_dataframe_equal!(&df1, &df2, options);
181    }
182
183    #[test]
184    #[should_panic(expected = "columns mismatch: [\"col3\"] in left, but not in right")]
185    fn test_dataframe_left_has_extra_column() {
186        let df1 = DataFrame::new_infer_height(vec![
187            Series::new("col1".into(), &[1, 2, 3]).into(),
188            Series::new("col2".into(), &["a", "b", "c"]).into(),
189            Series::new("col3".into(), &[true, false, true]).into(),
190        ])
191        .unwrap();
192
193        let df2 = DataFrame::new_infer_height(vec![
194            Series::new("col1".into(), &[1, 2, 3]).into(),
195            Series::new("col2".into(), &["a", "b", "c"]).into(),
196        ])
197        .unwrap();
198
199        assert_dataframe_equal!(&df1, &df2);
200    }
201
202    #[test]
203    #[should_panic(expected = "columns mismatch: [\"col3\"] in right, but not in left")]
204    fn test_dataframe_right_has_extra_column() {
205        let df1 = DataFrame::new_infer_height(vec![
206            Series::new("col1".into(), &[1, 2, 3]).into(),
207            Series::new("col2".into(), &["a", "b", "c"]).into(),
208        ])
209        .unwrap();
210
211        let df2 = DataFrame::new_infer_height(vec![
212            Series::new("col1".into(), &[1, 2, 3]).into(),
213            Series::new("col2".into(), &["a", "b", "c"]).into(),
214            Series::new("col3".into(), &[true, false, true]).into(),
215        ])
216        .unwrap();
217
218        assert_dataframe_equal!(&df1, &df2);
219    }
220
221    // Testing basic equality
222    #[test]
223    #[should_panic(expected = "value mismatch for column")]
224    fn test_dataframe_value_mismatch() {
225        let df1 = DataFrame::new_infer_height(vec![
226            Series::new("col1".into(), &[1, 2, 3]).into(),
227            Series::new("col2".into(), &["a", "b", "c"]).into(),
228            Series::new("col3".into(), &[true, false, true]).into(),
229        ])
230        .unwrap();
231
232        let df2 = DataFrame::new_infer_height(vec![
233            Series::new("col1".into(), &[1, 2, 3]).into(),
234            Series::new("col2".into(), &["a", "b", "changed"]).into(),
235            Series::new("col3".into(), &[true, false, true]).into(),
236        ])
237        .unwrap();
238
239        assert_dataframe_equal!(&df1, &df2);
240    }
241
242    #[test]
243    fn test_dataframe_equal() {
244        let df1 = DataFrame::new_infer_height(vec![
245            Series::new("col1".into(), &[1, 2, 3]).into(),
246            Series::new("col2".into(), &["a", "b", "c"]).into(),
247            Series::new("col3".into(), &[true, false, true]).into(),
248        ])
249        .unwrap();
250
251        let df2 = DataFrame::new_infer_height(vec![
252            Series::new("col1".into(), &[1, 2, 3]).into(),
253            Series::new("col2".into(), &["a", "b", "c"]).into(),
254            Series::new("col3".into(), &[true, false, true]).into(),
255        ])
256        .unwrap();
257
258        assert_dataframe_equal!(&df1, &df2);
259    }
260
261    #[test]
262    #[should_panic(expected = "value mismatch")]
263    fn test_dataframe_row_order_mismatch() {
264        let df1 = DataFrame::new_infer_height(vec![
265            Series::new("col1".into(), &[1, 2, 3]).into(),
266            Series::new("col2".into(), &["a", "b", "c"]).into(),
267        ])
268        .unwrap();
269
270        let df2 = DataFrame::new_infer_height(vec![
271            Series::new("col1".into(), &[3, 1, 2]).into(),
272            Series::new("col2".into(), &["c", "a", "b"]).into(),
273        ])
274        .unwrap();
275
276        assert_dataframe_equal!(&df1, &df2);
277    }
278
279    #[test]
280    fn test_dataframe_row_order_ignored() {
281        let df1 = DataFrame::new_infer_height(vec![
282            Series::new("col1".into(), &[1, 2, 3]).into(),
283            Series::new("col2".into(), &["a", "b", "c"]).into(),
284        ])
285        .unwrap();
286
287        let df2 = DataFrame::new_infer_height(vec![
288            Series::new("col1".into(), &[3, 1, 2]).into(),
289            Series::new("col2".into(), &["c", "a", "b"]).into(),
290        ])
291        .unwrap();
292
293        let options = crate::asserts::DataFrameEqualOptions::default().with_check_row_order(false);
294        assert_dataframe_equal!(&df1, &df2, options);
295    }
296
297    // Testing more comprehensive equality
298    #[test]
299    #[should_panic(expected = "value mismatch")]
300    fn test_dataframe_complex_mismatch() {
301        let df1 = DataFrame::new_infer_height(vec![
302            Series::new("integers".into(), &[1, 2, 3, 4, 5]).into(),
303            Series::new("floats".into(), &[1.1, 2.2, 3.3, 4.4, 5.5]).into(),
304            Series::new("strings".into(), &["a", "b", "c", "d", "e"]).into(),
305            Series::new("booleans".into(), &[true, false, true, false, true]).into(),
306            Series::new("opt_ints".into(), &[Some(1), None, Some(3), Some(4), None]).into(),
307        ])
308        .unwrap();
309
310        let df2 = DataFrame::new_infer_height(vec![
311            Series::new("integers".into(), &[1, 2, 99, 4, 5]).into(),
312            Series::new("floats".into(), &[1.1, 2.2, 3.3, 9.9, 5.5]).into(),
313            Series::new("strings".into(), &["a", "b", "c", "CHANGED", "e"]).into(),
314            Series::new("booleans".into(), &[true, false, false, false, true]).into(),
315            Series::new("opt_ints".into(), &[Some(1), None, Some(3), None, None]).into(),
316        ])
317        .unwrap();
318
319        assert_dataframe_equal!(&df1, &df2);
320    }
321
322    #[test]
323    fn test_dataframe_complex_match() {
324        let df1 = DataFrame::new_infer_height(vec![
325            Series::new("integers".into(), &[1, 2, 3, 4, 5]).into(),
326            Series::new("floats".into(), &[1.1, 2.2, 3.3, 4.4, 5.5]).into(),
327            Series::new("strings".into(), &["a", "b", "c", "d", "e"]).into(),
328            Series::new("booleans".into(), &[true, false, true, false, true]).into(),
329            Series::new("opt_ints".into(), &[Some(1), None, Some(3), Some(4), None]).into(),
330        ])
331        .unwrap();
332
333        let df2 = DataFrame::new_infer_height(vec![
334            Series::new("integers".into(), &[1, 2, 3, 4, 5]).into(),
335            Series::new("floats".into(), &[1.1, 2.2, 3.3, 4.4, 5.5]).into(),
336            Series::new("strings".into(), &["a", "b", "c", "d", "e"]).into(),
337            Series::new("booleans".into(), &[true, false, true, false, true]).into(),
338            Series::new("opt_ints".into(), &[Some(1), None, Some(3), Some(4), None]).into(),
339        ])
340        .unwrap();
341
342        assert_dataframe_equal!(&df1, &df2);
343    }
344
345    // Testing float value precision equality
346    #[test]
347    #[should_panic(expected = "value mismatch")]
348    fn test_dataframe_numeric_exact_fail() {
349        let df1 = DataFrame::new_infer_height(vec![
350            Series::new("col1".into(), &[1.0000001, 2.0000002, 3.0000003]).into(),
351        ])
352        .unwrap();
353
354        let df2 =
355            DataFrame::new_infer_height(vec![Series::new("col1".into(), &[1.0, 2.0, 3.0]).into()])
356                .unwrap();
357
358        let options = crate::asserts::DataFrameEqualOptions::default().with_check_exact(true);
359        assert_dataframe_equal!(&df1, &df2, options);
360    }
361
362    #[test]
363    fn test_dataframe_numeric_tolerance_pass() {
364        let df1 = DataFrame::new_infer_height(vec![
365            Series::new("col1".into(), &[1.0000001, 2.0000002, 3.0000003]).into(),
366        ])
367        .unwrap();
368
369        let df2 =
370            DataFrame::new_infer_height(vec![Series::new("col1".into(), &[1.0, 2.0, 3.0]).into()])
371                .unwrap();
372
373        assert_dataframe_equal!(&df1, &df2);
374    }
375
376    // Testing equality with special values
377    #[test]
378    fn test_empty_dataframe_equal() {
379        let df1 = DataFrame::empty();
380        let df2 = DataFrame::empty();
381
382        assert_dataframe_equal!(&df1, &df2);
383    }
384
385    #[test]
386    fn test_empty_dataframe_schema_equal() {
387        let df1 = DataFrame::new_infer_height(vec![
388            Series::new("col1".into(), &Vec::<i32>::new()).into(),
389            Series::new("col2".into(), &Vec::<String>::new()).into(),
390        ])
391        .unwrap();
392
393        let df2 = DataFrame::new_infer_height(vec![
394            Series::new("col1".into(), &Vec::<i32>::new()).into(),
395            Series::new("col2".into(), &Vec::<String>::new()).into(),
396        ])
397        .unwrap();
398
399        assert_dataframe_equal!(&df1, &df2);
400    }
401
402    #[test]
403    #[should_panic(expected = "value mismatch")]
404    fn test_dataframe_single_row_mismatch() {
405        let df1 = DataFrame::new_infer_height(vec![
406            Series::new("col1".into(), &[42]).into(),
407            Series::new("col2".into(), &["value"]).into(),
408            Series::new("col3".into(), &[true]).into(),
409        ])
410        .unwrap();
411
412        let df2 = DataFrame::new_infer_height(vec![
413            Series::new("col1".into(), &[42]).into(),
414            Series::new("col2".into(), &["different"]).into(),
415            Series::new("col3".into(), &[true]).into(),
416        ])
417        .unwrap();
418
419        assert_dataframe_equal!(&df1, &df2);
420    }
421
422    #[test]
423    fn test_dataframe_single_row_match() {
424        let df1 = DataFrame::new_infer_height(vec![
425            Series::new("col1".into(), &[42]).into(),
426            Series::new("col2".into(), &["value"]).into(),
427            Series::new("col3".into(), &[true]).into(),
428        ])
429        .unwrap();
430
431        let df2 = DataFrame::new_infer_height(vec![
432            Series::new("col1".into(), &[42]).into(),
433            Series::new("col2".into(), &["value"]).into(),
434            Series::new("col3".into(), &[true]).into(),
435        ])
436        .unwrap();
437
438        assert_dataframe_equal!(&df1, &df2);
439    }
440
441    #[test]
442    #[should_panic(expected = "value mismatch")]
443    fn test_dataframe_null_values_mismatch() {
444        let df1 = DataFrame::new_infer_height(vec![
445            Series::new("col1".into(), &[Some(1), None, Some(3)]).into(),
446        ])
447        .unwrap();
448
449        let df2 = DataFrame::new_infer_height(vec![
450            Series::new("col1".into(), &[Some(1), Some(2), None]).into(),
451        ])
452        .unwrap();
453
454        assert_dataframe_equal!(&df1, &df2);
455    }
456
457    #[test]
458    fn test_dataframe_null_values_match() {
459        let df1 = DataFrame::new_infer_height(vec![
460            Series::new("col1".into(), &[Some(1), None, Some(3)]).into(),
461        ])
462        .unwrap();
463
464        let df2 = DataFrame::new_infer_height(vec![
465            Series::new("col1".into(), &[Some(1), None, Some(3)]).into(),
466        ])
467        .unwrap();
468
469        assert_dataframe_equal!(&df1, &df2);
470    }
471
472    #[test]
473    #[should_panic(expected = "value mismatch")]
474    fn test_dataframe_nan_values_mismatch() {
475        let df1 = DataFrame::new_infer_height(vec![
476            Series::new("col1".into(), &[1.0, f64::NAN, 3.0]).into(),
477        ])
478        .unwrap();
479
480        let df2 = DataFrame::new_infer_height(vec![
481            Series::new("col1".into(), &[1.0, 2.0, f64::NAN]).into(),
482        ])
483        .unwrap();
484
485        assert_dataframe_equal!(&df1, &df2);
486    }
487
488    #[test]
489    fn test_dataframe_nan_values_match() {
490        let df1 = DataFrame::new_infer_height(vec![
491            Series::new("col1".into(), &[1.0, f64::NAN, 3.0]).into(),
492        ])
493        .unwrap();
494
495        let df2 = DataFrame::new_infer_height(vec![
496            Series::new("col1".into(), &[1.0, f64::NAN, 3.0]).into(),
497        ])
498        .unwrap();
499
500        assert_dataframe_equal!(&df1, &df2);
501    }
502
503    #[test]
504    #[should_panic(expected = "value mismatch")]
505    fn test_dataframe_infinity_values_mismatch() {
506        let df1 = DataFrame::new_infer_height(vec![
507            Series::new("col1".into(), &[1.0, f64::INFINITY, 3.0]).into(),
508        ])
509        .unwrap();
510
511        let df2 = DataFrame::new_infer_height(vec![
512            Series::new("col1".into(), &[1.0, f64::NEG_INFINITY, 3.0]).into(),
513        ])
514        .unwrap();
515
516        assert_dataframe_equal!(&df1, &df2);
517    }
518
519    #[test]
520    fn test_dataframe_infinity_values_match() {
521        let df1 = DataFrame::new_infer_height(vec![
522            Series::new("col1".into(), &[1.0, f64::INFINITY, 3.0]).into(),
523        ])
524        .unwrap();
525
526        let df2 = DataFrame::new_infer_height(vec![
527            Series::new("col1".into(), &[1.0, f64::INFINITY, 3.0]).into(),
528        ])
529        .unwrap();
530
531        assert_dataframe_equal!(&df1, &df2);
532    }
533
534    // Testing categorical operations
535    #[test]
536    #[should_panic(expected = "value mismatch")]
537    fn test_dataframe_categorical_as_string_mismatch() {
538        let mut categorical1 = Series::new("categories".into(), &["a", "b", "c", "d"]);
539        categorical1 = categorical1
540            .cast(&DataType::from_categories(Categories::global()))
541            .unwrap();
542        let df1 = DataFrame::new_infer_height(vec![categorical1.into()]).unwrap();
543
544        let mut categorical2 = Series::new("categories".into(), &["a", "b", "c", "e"]);
545        categorical2 = categorical2
546            .cast(&DataType::from_categories(Categories::global()))
547            .unwrap();
548        let df2 = DataFrame::new_infer_height(vec![categorical2.into()]).unwrap();
549
550        let options =
551            crate::asserts::DataFrameEqualOptions::default().with_categorical_as_str(true);
552        assert_dataframe_equal!(&df1, &df2, options);
553    }
554
555    #[test]
556    fn test_dataframe_categorical_as_string_match() {
557        let mut categorical1 = Series::new("categories".into(), &["a", "b", "c", "d"]);
558        categorical1 = categorical1
559            .cast(&DataType::from_categories(Categories::global()))
560            .unwrap();
561        let df1 = DataFrame::new_infer_height(vec![categorical1.into()]).unwrap();
562
563        let mut categorical2 = Series::new("categories".into(), &["a", "b", "c", "d"]);
564        categorical2 = categorical2
565            .cast(&DataType::from_categories(Categories::global()))
566            .unwrap();
567        let df2 = DataFrame::new_infer_height(vec![categorical2.into()]).unwrap();
568
569        let options =
570            crate::asserts::DataFrameEqualOptions::default().with_categorical_as_str(true);
571        assert_dataframe_equal!(&df1, &df2, options);
572    }
573
574    // Testing nested types
575    #[test]
576    #[should_panic(expected = "value mismatch")]
577    fn test_dataframe_nested_values_mismatch() {
578        let df1 = DataFrame::new_infer_height(vec![
579            Series::new(
580                "list_col".into(),
581                &[
582                    Some(vec![1, 2, 3]),
583                    Some(vec![4, 5, 6]),
584                    None,
585                    Some(vec![7, 8, 9]),
586                ],
587            )
588            .into(),
589        ])
590        .unwrap();
591
592        let df2 = DataFrame::new_infer_height(vec![
593            Series::new(
594                "list_col".into(),
595                &[
596                    Some(vec![1, 2, 3]),
597                    Some(vec![4, 5, 99]),
598                    None,
599                    Some(vec![7, 8, 9]),
600                ],
601            )
602            .into(),
603        ])
604        .unwrap();
605
606        assert_dataframe_equal!(&df1, &df2);
607    }
608
609    #[test]
610    fn test_dataframe_nested_values_match() {
611        let df1 = DataFrame::new_infer_height(vec![
612            Series::new(
613                "list_col".into(),
614                &[Some(vec![1, 2, 3]), Some(vec![]), None, Some(vec![7, 8, 9])],
615            )
616            .into(),
617        ])
618        .unwrap();
619
620        let df2 = DataFrame::new_infer_height(vec![
621            Series::new(
622                "list_col".into(),
623                &[Some(vec![1, 2, 3]), Some(vec![]), None, Some(vec![7, 8, 9])],
624            )
625            .into(),
626        ])
627        .unwrap();
628
629        assert_dataframe_equal!(&df1, &df2);
630    }
631}