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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(vec![
350            Series::new("col1".into(), &[1.0000001, 2.0000002, 3.0000003]).into(),
351        ])
352        .unwrap();
353
354        let df2 =
355            DataFrame::new(vec![Series::new("col1".into(), &[1.0, 2.0, 3.0]).into()]).unwrap();
356
357        let options = crate::asserts::DataFrameEqualOptions::default().with_check_exact(true);
358        assert_dataframe_equal!(&df1, &df2, options);
359    }
360
361    #[test]
362    fn test_dataframe_numeric_tolerance_pass() {
363        let df1 = DataFrame::new(vec![
364            Series::new("col1".into(), &[1.0000001, 2.0000002, 3.0000003]).into(),
365        ])
366        .unwrap();
367
368        let df2 =
369            DataFrame::new(vec![Series::new("col1".into(), &[1.0, 2.0, 3.0]).into()]).unwrap();
370
371        assert_dataframe_equal!(&df1, &df2);
372    }
373
374    // Testing equality with special values
375    #[test]
376    fn test_empty_dataframe_equal() {
377        let df1 = DataFrame::default();
378        let df2 = DataFrame::default();
379
380        assert_dataframe_equal!(&df1, &df2);
381    }
382
383    #[test]
384    fn test_empty_dataframe_schema_equal() {
385        let df1 = DataFrame::new(vec![
386            Series::new("col1".into(), &Vec::<i32>::new()).into(),
387            Series::new("col2".into(), &Vec::<String>::new()).into(),
388        ])
389        .unwrap();
390
391        let df2 = DataFrame::new(vec![
392            Series::new("col1".into(), &Vec::<i32>::new()).into(),
393            Series::new("col2".into(), &Vec::<String>::new()).into(),
394        ])
395        .unwrap();
396
397        assert_dataframe_equal!(&df1, &df2);
398    }
399
400    #[test]
401    #[should_panic(expected = "value mismatch")]
402    fn test_dataframe_single_row_mismatch() {
403        let df1 = DataFrame::new(vec![
404            Series::new("col1".into(), &[42]).into(),
405            Series::new("col2".into(), &["value"]).into(),
406            Series::new("col3".into(), &[true]).into(),
407        ])
408        .unwrap();
409
410        let df2 = DataFrame::new(vec![
411            Series::new("col1".into(), &[42]).into(),
412            Series::new("col2".into(), &["different"]).into(),
413            Series::new("col3".into(), &[true]).into(),
414        ])
415        .unwrap();
416
417        assert_dataframe_equal!(&df1, &df2);
418    }
419
420    #[test]
421    fn test_dataframe_single_row_match() {
422        let df1 = DataFrame::new(vec![
423            Series::new("col1".into(), &[42]).into(),
424            Series::new("col2".into(), &["value"]).into(),
425            Series::new("col3".into(), &[true]).into(),
426        ])
427        .unwrap();
428
429        let df2 = DataFrame::new(vec![
430            Series::new("col1".into(), &[42]).into(),
431            Series::new("col2".into(), &["value"]).into(),
432            Series::new("col3".into(), &[true]).into(),
433        ])
434        .unwrap();
435
436        assert_dataframe_equal!(&df1, &df2);
437    }
438
439    #[test]
440    #[should_panic(expected = "value mismatch")]
441    fn test_dataframe_null_values_mismatch() {
442        let df1 = DataFrame::new(vec![
443            Series::new("col1".into(), &[Some(1), None, Some(3)]).into(),
444        ])
445        .unwrap();
446
447        let df2 = DataFrame::new(vec![
448            Series::new("col1".into(), &[Some(1), Some(2), None]).into(),
449        ])
450        .unwrap();
451
452        assert_dataframe_equal!(&df1, &df2);
453    }
454
455    #[test]
456    fn test_dataframe_null_values_match() {
457        let df1 = DataFrame::new(vec![
458            Series::new("col1".into(), &[Some(1), None, Some(3)]).into(),
459        ])
460        .unwrap();
461
462        let df2 = DataFrame::new(vec![
463            Series::new("col1".into(), &[Some(1), None, Some(3)]).into(),
464        ])
465        .unwrap();
466
467        assert_dataframe_equal!(&df1, &df2);
468    }
469
470    #[test]
471    #[should_panic(expected = "value mismatch")]
472    fn test_dataframe_nan_values_mismatch() {
473        let df1 = DataFrame::new(vec![
474            Series::new("col1".into(), &[1.0, f64::NAN, 3.0]).into(),
475        ])
476        .unwrap();
477
478        let df2 = DataFrame::new(vec![
479            Series::new("col1".into(), &[1.0, 2.0, f64::NAN]).into(),
480        ])
481        .unwrap();
482
483        assert_dataframe_equal!(&df1, &df2);
484    }
485
486    #[test]
487    fn test_dataframe_nan_values_match() {
488        let df1 = DataFrame::new(vec![
489            Series::new("col1".into(), &[1.0, f64::NAN, 3.0]).into(),
490        ])
491        .unwrap();
492
493        let df2 = DataFrame::new(vec![
494            Series::new("col1".into(), &[1.0, f64::NAN, 3.0]).into(),
495        ])
496        .unwrap();
497
498        assert_dataframe_equal!(&df1, &df2);
499    }
500
501    #[test]
502    #[should_panic(expected = "value mismatch")]
503    fn test_dataframe_infinity_values_mismatch() {
504        let df1 = DataFrame::new(vec![
505            Series::new("col1".into(), &[1.0, f64::INFINITY, 3.0]).into(),
506        ])
507        .unwrap();
508
509        let df2 = DataFrame::new(vec![
510            Series::new("col1".into(), &[1.0, f64::NEG_INFINITY, 3.0]).into(),
511        ])
512        .unwrap();
513
514        assert_dataframe_equal!(&df1, &df2);
515    }
516
517    #[test]
518    fn test_dataframe_infinity_values_match() {
519        let df1 = DataFrame::new(vec![
520            Series::new("col1".into(), &[1.0, f64::INFINITY, 3.0]).into(),
521        ])
522        .unwrap();
523
524        let df2 = DataFrame::new(vec![
525            Series::new("col1".into(), &[1.0, f64::INFINITY, 3.0]).into(),
526        ])
527        .unwrap();
528
529        assert_dataframe_equal!(&df1, &df2);
530    }
531
532    // Testing categorical operations
533    #[test]
534    #[should_panic(expected = "value mismatch")]
535    fn test_dataframe_categorical_as_string_mismatch() {
536        let mut categorical1 = Series::new("categories".into(), &["a", "b", "c", "d"]);
537        categorical1 = categorical1
538            .cast(&DataType::from_categories(Categories::global()))
539            .unwrap();
540        let df1 = DataFrame::new(vec![categorical1.into()]).unwrap();
541
542        let mut categorical2 = Series::new("categories".into(), &["a", "b", "c", "e"]);
543        categorical2 = categorical2
544            .cast(&DataType::from_categories(Categories::global()))
545            .unwrap();
546        let df2 = DataFrame::new(vec![categorical2.into()]).unwrap();
547
548        let options =
549            crate::asserts::DataFrameEqualOptions::default().with_categorical_as_str(true);
550        assert_dataframe_equal!(&df1, &df2, options);
551    }
552
553    #[test]
554    fn test_dataframe_categorical_as_string_match() {
555        let mut categorical1 = Series::new("categories".into(), &["a", "b", "c", "d"]);
556        categorical1 = categorical1
557            .cast(&DataType::from_categories(Categories::global()))
558            .unwrap();
559        let df1 = DataFrame::new(vec![categorical1.into()]).unwrap();
560
561        let mut categorical2 = Series::new("categories".into(), &["a", "b", "c", "d"]);
562        categorical2 = categorical2
563            .cast(&DataType::from_categories(Categories::global()))
564            .unwrap();
565        let df2 = DataFrame::new(vec![categorical2.into()]).unwrap();
566
567        let options =
568            crate::asserts::DataFrameEqualOptions::default().with_categorical_as_str(true);
569        assert_dataframe_equal!(&df1, &df2, options);
570    }
571
572    // Testing nested types
573    #[test]
574    #[should_panic(expected = "value mismatch")]
575    fn test_dataframe_nested_values_mismatch() {
576        let df1 = DataFrame::new(vec![
577            Series::new(
578                "list_col".into(),
579                &[
580                    Some(vec![1, 2, 3]),
581                    Some(vec![4, 5, 6]),
582                    None,
583                    Some(vec![7, 8, 9]),
584                ],
585            )
586            .into(),
587        ])
588        .unwrap();
589
590        let df2 = DataFrame::new(vec![
591            Series::new(
592                "list_col".into(),
593                &[
594                    Some(vec![1, 2, 3]),
595                    Some(vec![4, 5, 99]),
596                    None,
597                    Some(vec![7, 8, 9]),
598                ],
599            )
600            .into(),
601        ])
602        .unwrap();
603
604        assert_dataframe_equal!(&df1, &df2);
605    }
606
607    #[test]
608    fn test_dataframe_nested_values_match() {
609        let df1 = DataFrame::new(vec![
610            Series::new(
611                "list_col".into(),
612                &[Some(vec![1, 2, 3]), Some(vec![]), None, Some(vec![7, 8, 9])],
613            )
614            .into(),
615        ])
616        .unwrap();
617
618        let df2 = DataFrame::new(vec![
619            Series::new(
620                "list_col".into(),
621                &[Some(vec![1, 2, 3]), Some(vec![]), None, Some(vec![7, 8, 9])],
622            )
623            .into(),
624        ])
625        .unwrap();
626
627        assert_dataframe_equal!(&df1, &df2);
628    }
629}