HOWTO · Python

How to Compare Two CSV Files and Print Differences Using Python

Compare complete CSV rows or match records by a shared key with Python's csv module, csv-diff, or pandas.

On this page

Choose the comparison rule before choosing a tool. A complete-row comparison asks whether every field in a record matches another record. A keyed comparison matches records by a shared column, such as id, then reports fields added, removed, or changed. Python’s standard-library csv module is enough for complete rows; csv-diff is convenient for keyed reports; pandas is useful when the files already fit a DataFrame workflow. The examples below use file1.csv and file2.csv consistently.

Choose the comparison that matches your data

Use complete-row comparison when the order of fields matters and a record is the same only if all its values match. Use a key-based comparison when the same entity may have changed values: an id column can pair its records even if another column differs. For a quick comparison of small files, the standard library avoids installing a package. For reports about changed fields, choose csv-diff or pandas. These methods answer different questions, so decide whether duplicates and record order matter before interpreting a result.

Compare complete CSV rows with Python’s csv module

Parse each file with csv.reader instead of comparing raw lines. CSV quoting can put commas or line breaks inside a field, so a physical line is not always a complete record. The example counts parsed rows in each file and prints rows that occur more often on one side. It writes its own sample files in a temporary directory, so it does not depend on files beside the script. This approach uses memory proportional to the input size; for very large files, consider a sorted streaming comparison or a database-backed workflow.

Run this example:

import csv
import tempfile
from collections import Counter
from pathlib import Path

with tempfile.TemporaryDirectory() as directory:
    root = Path(directory)
    (root / "file1.csv").write_text("id,name\n1,Ada\n2,Lin\n", encoding="utf-8")
    (root / "file2.csv").write_text("id,name\n1,Ada\n3,Sam\n", encoding="utf-8")

    def read_rows(path):
        with path.open(newline="", encoding="utf-8") as source:
            return list(csv.reader(source))

    left = Counter(map(tuple, read_rows(root / "file1.csv")))
    right = Counter(map(tuple, read_rows(root / "file2.csv")))
    print("Only in file1:", list((left - right).elements()))
    print("Only in file2:", list((right - left).elements()))
Only in file1: [('2', 'Lin')]
Only in file2: [('3', 'Sam')]

In the supplied runtime run, this example exited successfully and printed the output above. Its comparison includes the header as a row; omit the first parsed row from each input if you want to compare data records only. Counter preserves multiplicity: if a row appears twice on one side and once on the other, one occurrence remains in that side’s difference.

Compare keyed rows with csv-diff

For record-level changes, give both files a header containing the same unique key. For example, file1.csv can contain id,name followed by 1,Ada and 2,Lin; file2.csv can contain id,name followed by 1,Ada Lovelace and 3,Sam. Install the package with python3 -m pip install csv-diff, then run csv-diff file1.csv file2.csv --key=id. The key must exist in both headers and identify records consistently. The CLI reports added and removed records and changed values for matched keys; it does not simply compare whole rows. The csv-diff project documentation documents the command syntax and the Python API, including load_csv and compare. This external command is usage guidance and is not covered by the supplied runtime evidence.

Compare rows with pandas

Pandas can identify complete rows found on only one side with an outer merge. Read headerless files with header=None; when the first row contains column names, use the default header handling instead. For a keyed record comparison, merge on the key column and inspect the remaining columns for changed values. The _merge indicator marks whether each merged row came from the left input, the right input, or both; it does not represent row indices. The labels left_only and right_only identify unmatched rows from the respective inputs.

Run this example with pandas installed:

try:
    import pandas as pd
except ImportError:
    pd = None

if pd is not None:
    import tempfile
    from pathlib import Path

    with tempfile.TemporaryDirectory() as directory:
        root = Path(directory)
        (root / "file1.csv").write_text("id,name\n1,Ada\n2,Lin\n", encoding="utf-8")
        (root / "file2.csv").write_text("id,name\n1,Ada\n3,Sam\n", encoding="utf-8")

        left = pd.read_csv(root / "file1.csv")
        right = pd.read_csv(root / "file2.csv")
        merged = left.merge(right, how="outer", indicator=True)
        print(merged.loc[merged["_merge"] != "both", ["id", "_merge"]])

This dependency-backed example is not covered by the supplied runtime evidence, so no observed output is shown. The merge uses the shared id and the identical name column to match complete records; _merge identifies which file contains each unmatched row. For headerless files, pass header=None when reading both inputs and assign the same column names before merging.

Account for headers, duplicates, and row order

Decide whether the header is data or metadata: include it only if you intend to compare it, and use header=None for headerless input in pandas. Set matching column names when the two files use different headers. A set comparison discards duplicate counts, while a multiset comparison such as the Counter example retains them. Row-by-row comparison treats order as meaningful; membership, keyed matching, or sorting before comparison can ignore order, but each choice changes what “different” means. For keyed comparisons, duplicate key values make pairing ambiguous, so check that the selected key is unique or define how duplicates should be handled. These distinctions let you choose a comparison that reflects the data question instead of treating every difference as the same kind of change.