diff --git a/pandas/_testing/asserters.py b/pandas/_testing/asserters.py index 160c362d0dbc1..5ee75d8e308d2 100644 --- a/pandas/_testing/asserters.py +++ b/pandas/_testing/asserters.py @@ -1315,6 +1315,9 @@ def assert_frame_equal( if check_like: left = left.reindex_like(right) + column_errors = [] + first_error_message = None + # compare by blocks if by_blocks: rblocks = right._to_dict_of_blocks() @@ -1338,29 +1341,45 @@ def assert_frame_equal( # use check_index=False, because we do not want to run # assert_index_equal for each column, # as we already checked it for the whole dataframe before. - with warnings.catch_warnings(): - warnings.filterwarnings( - "ignore", - message="the 'check_datetimelike_compat' keyword", - category=Pandas4Warning, - ) - assert_series_equal( - lcol, - rcol, - check_dtype=check_dtype, - check_index_type=check_index_type, - check_exact=check_exact, - check_names=check_names, - check_datetimelike_compat=check_datetimelike_compat, - check_categorical=check_categorical, - check_freq=check_freq, - obj=f'{obj}.iloc[:, {i}] (column name="{col}")', - rtol=rtol, - atol=atol, - check_index=False, - check_flags=False, - ) - + try: + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", + message="the 'check_datetimelike_compat' keyword", + category=Pandas4Warning, + ) + assert_series_equal( + lcol, + rcol, + check_dtype=check_dtype, + check_index_type=check_index_type, + check_exact=check_exact, + check_names=check_names, + check_datetimelike_compat=check_datetimelike_compat, + check_categorical=check_categorical, + check_freq=check_freq, + obj=f'{obj}.iloc[:, {i}] (column name="{col}")', + rtol=rtol, + atol=atol, + check_index=False, + check_flags=False, + ) + except AssertionError as e: + column_errors.append((i, col)) + if first_error_message is None: + first_error_message = str(e) + + if column_errors: + column_indices = [idx for idx, _ in column_errors] + column_names = [name for _, name in column_errors] + + error_summary = f"{obj} are different\n\n" + error_summary += f"Columns with differences (positions {column_indices}):\n" + error_summary += f"{column_names}\n\n" + error_summary += f"First difference details:\n" + error_summary += first_error_message + + raise AssertionError(error_summary) def assert_equal(left, right, **kwargs) -> None: """ diff --git a/pandas/tests/util/test_assert_frame_equal.py b/pandas/tests/util/test_assert_frame_equal.py index 19abfe727fb4b..a633f6a86c774 100644 --- a/pandas/tests/util/test_assert_frame_equal.py +++ b/pandas/tests/util/test_assert_frame_equal.py @@ -423,3 +423,59 @@ def test_assert_frame_equal_nested_df_na(na_value): df1 = DataFrame({"df": [inner]}) df2 = DataFrame({"df": [inner]}) tm.assert_frame_equal(df1, df2) + +def test_assert_frame_equal_reports_all_different_columns(): + df1 = pd.DataFrame({ + "a": [1, 2, 3], + "b": [4, 5, 6], + "c": [7, 8, 9] + }) + df2 = pd.DataFrame({ + "a": [1, 99, 3], + "b": [4, 5, 6], + "c": [7, 8, 99] + }) + + with pytest.raises(AssertionError) as exc_info: + tm.assert_frame_equal(df1, df2) + + error_msg = str(exc_info.value) + + assert "Columns with differences" in error_msg + + assert "'a'" in error_msg or '"a"' in error_msg + assert "'c'" in error_msg or '"c"' in error_msg + + assert "[0, 2]" in error_msg or "0, 2" in error_msg + + lines = error_msg.split('\n') + for i, line in enumerate(lines): + if "Columns with differences" in line: + if i + 1 < len(lines): + column_list_line = lines[i + 1] + assert "'a'" in column_list_line or '"a"' in column_list_line + assert "'c'" in column_list_line or '"c"' in column_list_line + assert not ("'b'" in column_list_line or '"b"' in column_list_line) + +def test_assert_frame_equal_all_columns_different(): + df1 = pd.DataFrame({ + "a": [1, 2], + "b": [3, 4], + "c": [5, 6] + }) + df2 = pd.DataFrame({ + "a": [10, 20], + "b": [30, 40], + "c": [50, 60] + }) + + with pytest.raises(AssertionError) as exc_info: + tm.assert_frame_equal(df1, df2) + + error_msg = str(exc_info.value) + + assert "'a'" in error_msg or '"a"' in error_msg + assert "'b'" in error_msg or '"b"' in error_msg + assert "'c'" in error_msg or '"c"' in error_msg + + assert "[0, 1, 2]" in error_msg or "0, 1, 2" in error_msg