Compare Two Data Frames in R

R provides several ways to compare two data frames, depending on whether you need to check complete equality, identify changed values, find unmatched rows, compare column names, or match records by a key column.

This tutorial begins with the compare() function from the compare package and then covers commonly used base R and dplyr methods for inspecting specific differences.

Install and Load the compare Package in R

The compare() function discussed in the first part of this tutorial belongs to the compare package. Install the package once, and then load it in each new R session where it is required.

</>
Copy
install.packages("compare")
library(compare)

Syntax of compare() for R Data Frames

The syntax of compare() is shown below.

</>
Copy
 compare(model, comparison,
	equal = TRUE,
	coerce = allowAll,
	shorten = allowAll,
	ignoreOrder = allowAll,
	ignoreNameCase = allowAll,
	ignoreNames = allowAll,
	ignoreAttrs = allowAll,
	round = FALSE,
	ignoreCase = allowAll,
	trim = allowAll,
	dropLevels = allowAll,
	ignoreLevelOrder = allowAll,
	ignoreDimOrder = allowAll,
	ignoreColOrder = allowAll,
	ignoreComponentOrder = allowAll,
	colsOnly = !allowAll,
	allowAll = FALSE)

The two required arguments are model, which is treated as the reference object, and comparison, which is checked against the reference.

  • model The “correct” object.
  • comparison The object to be compared with the model.
  • equal Test for equality if test for identity fails.
  • coerce If objects are not the same, allow coercion of comparsion to model class.
  • shorten If the length of one object is less than the other, shorten the longer object.
  • ignoreOrder Ignore the order of values when comparing.
  • ignoreNameCase Ignore the case of names when comparing.
  • ignoreNames Ignore names attributes altogether.
  • ignoreAttrs Ignore attributes altogether.
  • round If objects are not the same, allow numbers to be rounded.
  • ignoreCase Ignore the case of string values.
  • trim Ignore leading and trailing spaces in string values.
  • dropLevels If factors are not the same, allow unused levels to be dropped.
  • ignoreLevelOrder Ignore the order of factor levels.
  • ignoreDimOrder Ignore the order of dimensions when comparing matrices, arrays, or tables.
  • ignoreColOrder Ignore the order of columns when comparing data frames.
  • ignoreComponentOrder Ignore the order of components when comparing lists.
  • colsOnly Only transform columns (not rows) when comparing data frames.
  • allowAll Allow any sort of transformation (almost; see Details).

Most comparisons only require the first two arguments. The remaining arguments control whether differences such as row order, column order, text case, attributes, factor levels, or numeric precision may be ignored.

Basic Comparison of Two R Data Frames with compare()

Consider two data frames named DF1 and DF2. They contain the same IDs, but the name in the fourth row is different.

</>
Copy
> DF1 = data.frame(id=c(1,2,3,4), name=c("John", "Manu", "Surya", "Amith"))
> DF2 = data.frame(id=c(1,2,3,4), name=c("John", "Manu", "Surya", "Tinu"))
> DF1
  id  name
1  1  John
2  2  Manu
3  3 Surya
4  4 Amith
> DF2
  id  name
1  1  John
2  2  Manu
3  3 Surya
4  4  Tinu
>

Now compare DF2 against DF1.

</>
Copy
> compare(DF1, DF2)
FALSE [TRUE, FALSE]
>

The result is FALSE because the data frames are not equal. The bracketed result indicates that one component matches while another does not.

When the data frames contain the same values, compare() returns TRUE.

</>
Copy
> DF1 = data.frame(id=c(1,2,3,4), name=c("John", "Manu", "Surya", "Amith"))
> DF2 = data.frame(id=c(1,2,3,4), name=c("John", "Manu", "Surya", "Amith"))
> compare(DF1, DF2)
TRUE

Check Whether Two Data Frames Are Exactly Identical

Use base R’s identical() when you need a strict comparison. It checks values, data types, dimensions, column order, row order, names, and relevant attributes.

</>
Copy
df1 <- data.frame(
  id = c(1L, 2L, 3L),
  score = c(81, 92, 75)
)

df2 <- data.frame(
  id = c(1L, 2L, 3L),
  score = c(81, 92, 75)
)

identical(df1, df2)
[1] TRUE

A difference in column type can make identical() return FALSE, even when the printed values look the same. For example, an integer column and a double column are not strictly identical.

Use all.equal() to Explain Data Frame Differences

The base R function all.equal() is useful when you want a descriptive result instead of only TRUE or FALSE. It reports the type of mismatch it finds.

</>
Copy
df1 <- data.frame(id = 1:3, score = c(80, 90, 70))
df2 <- data.frame(id = 1:3, score = c(80, 91, 70))

all.equal(df1, df2)
[1] "Component “score”: Mean relative difference: 0.01111111"

For a safe Boolean test, wrap all.equal() in isTRUE(). This is necessary because all.equal() returns either TRUE or a character description.

</>
Copy
isTRUE(all.equal(df1, df2))
[1] FALSE

Find Individual Cells That Differ Between Two Data Frames

When two data frames have the same dimensions and their rows and columns are already aligned, use an element-wise comparison to locate changed cells.

</>
Copy
df1 <- data.frame(
  id = 1:4,
  name = c("John", "Manu", "Surya", "Amith"),
  score = c(78, 85, 91, 73)
)

df2 <- data.frame(
  id = 1:4,
  name = c("John", "Manu", "Surya", "Tinu"),
  score = c(78, 88, 91, 73)
)

difference_matrix <- df1 != df2
difference_matrix
        id  name score
[1,] FALSE FALSE FALSE
[2,] FALSE FALSE  TRUE
[3,] FALSE FALSE FALSE
[4,] FALSE  TRUE FALSE

Use which(..., arr.ind = TRUE) to obtain the row and column positions of the changed cells.

</>
Copy
which(difference_matrix, arr.ind = TRUE)
     row col
[1,]   4   2
[2,]   2   3

This approach assumes both data frames have the same shape and corresponding records appear in the same row. If rows may be reordered, align them by a key column before comparing values.

Handle NA Values While Comparing R Data Frames

A direct expression such as df1 != df2 can produce NA when either side contains a missing value. A missing-value-aware comparison should distinguish three cases: equal non-missing values, missing values on both sides, and a missing value on only one side.

</>
Copy
different <- (is.na(df1) != is.na(df2)) |
             (!is.na(df1) & !is.na(df2) & df1 != df2)

which(different, arr.ind = TRUE)

The first condition detects positions where only one data frame contains NA. The second condition compares values only when both entries are present.

Find Rows Present in One Data Frame but Not the Other

Use dplyr::anti_join() when you need complete rows that appear in one data frame but do not have a matching row in another. This is often more useful than checking strict equality.

</>
Copy
library(dplyr)

df1 <- data.frame(
  id = c(1, 2, 3, 4),
  name = c("John", "Manu", "Surya", "Amith")
)

df2 <- data.frame(
  id = c(1, 2, 3, 5),
  name = c("John", "Manu", "Surya", "Tinu")
)

only_in_df1 <- anti_join(df1, df2, by = c("id", "name"))
only_in_df2 <- anti_join(df2, df1, by = c("id", "name"))

only_in_df1
only_in_df2
  id  name
1  4 Amith

  id name
1  5 Tinu

When the data frames have identical column structures, base R’s setdiff() can also identify unmatched rows.

</>
Copy
setdiff(df1, df2)
setdiff(df2, df1)

Run the comparison in both directions because setdiff(df1, df2) returns only rows found in df1 but not in df2.

Compare Matching Records by an ID Column

Two data frames may represent the same entities while storing them in a different row order. In that case, join the data frames by a stable key such as id and compare the paired columns.

</>
Copy
library(dplyr)

df_old <- data.frame(
  id = c(1, 2, 3),
  score = c(80, 90, 70)
)

df_new <- data.frame(
  id = c(3, 1, 2),
  score = c(72, 80, 90)
)

comparison <- full_join(
  df_old,
  df_new,
  by = "id",
  suffix = c("_old", "_new")
) |>
  mutate(changed = score_old != score_new)

comparison
  id score_old score_new changed
1  1        80        80   FALSE
2  2        90        90   FALSE
3  3        70        72    TRUE

A full_join() also retains keys that exist in only one data frame. Missing values in the paired columns can therefore indicate added or removed records.

Compare Column Names in Two R Data Frames

Before comparing row values, confirm that both data frames contain the expected columns.

</>
Copy
identical(names(df1), names(df2))

setdiff(names(df1), names(df2))
setdiff(names(df2), names(df1))

identical(names(df1), names(df2)) checks both column names and their order. The two setdiff() calls identify columns that occur in only one data frame.

To test whether both data frames contain the same column names regardless of order, sort the names first.

</>
Copy
identical(sort(names(df1)), sort(names(df2)))

Compare Two Columns from Different Data Frames

If the rows are already aligned, compare individual columns directly.

</>
Copy
df1$score == df2$score
which(df1$score != df2$score)

For records that are not guaranteed to be in the same order, first match the rows using an ID or another unique key.

</>
Copy
matched_positions <- match(df1$id, df2$id)

df1$score == df2$score[matched_positions]

Ignore Row Order When Comparing Two Data Frames

When row order is not meaningful, sort both data frames using the same unique key and reset row names before comparing them.

</>
Copy
df1_sorted <- df1[order(df1$id), ]
df2_sorted <- df2[order(df2$id), ]

row.names(df1_sorted) <- NULL
row.names(df2_sorted) <- NULL

identical(df1_sorted, df2_sorted)

The sorting column should uniquely identify each row. Sorting by a non-unique column may not align duplicate records consistently.

Compare Numeric Data Frames with a Tolerance

Floating-point calculations can produce very small differences even when two results are practically equivalent. Use all.equal() with a suitable tolerance instead of comparing decimal values with ==.

</>
Copy
df1 <- data.frame(value = c(0.3, 1.5))
df2 <- data.frame(value = c(0.1 + 0.2, 1.50000001))

isTRUE(all.equal(df1, df2, tolerance = 1e-7))
[1] TRUE

Select a tolerance that is appropriate for the precision of the data. A tolerance that is too large can hide meaningful differences.

Choosing the Right R Data Frame Comparison Method

Comparison requirementRecommended method
Strictly identical values, types, order, and attributesidentical(df1, df2)
Descriptive explanation of a mismatchall.equal(df1, df2)
Flexible comparison using the compare packagecompare(df1, df2)
Changed cells in aligned data framesdf1 != df2 with which()
Rows found in only one data frameanti_join() or setdiff()
Changed records matched by an IDfull_join() followed by column comparison
Different column namessetdiff(names(df1), names(df2))
Nearly equal floating-point valuesall.equal() with tolerance

Common Problems When Comparing R Data Frames

  • Comparing rows without a key: Two matching records may appear different only because their row order changed.
  • Ignoring column types: Character, factor, integer, and double columns can print similarly but behave differently in strict comparisons.
  • Using one-directional set difference: Run setdiff() or anti_join() in both directions to find all unmatched rows.
  • Comparing values before checking the schema: Confirm column names, order, and data types before locating cell-level differences.
  • Forgetting about missing values: Direct comparisons involving NA may return NA rather than TRUE or FALSE.
  • Using exact equality for calculated decimals: Apply a justified numeric tolerance when comparing floating-point results.

Frequently Asked Questions about Comparing Data Frames in R

How do I check whether two data frames are exactly the same in R?

Use identical(df1, df2). It returns TRUE only when values, data types, row order, column order, names, dimensions, and relevant attributes match.

How do I find rows that are different between two R data frames?

Use dplyr::anti_join(df1, df2) to find rows in df1 that are absent from df2, and reverse the arguments to find rows present only in df2. Base R’s setdiff() is another option when both data frames have compatible columns.

How do I compare two data frames when their rows are in a different order?

Align both data frames using a unique key. You can sort both by the key and then compare them, or join them by that key and compare the resulting paired columns.

How do I compare column names in two R data frames?

Use identical(names(df1), names(df2)) to check names and order. Use setdiff(names(df1), names(df2)) and its reverse to list columns missing from either data frame.

What is the difference between identical() and all.equal() in R?

identical() performs a strict test and returns a single Boolean value. all.equal() is designed to assess near equality and describe mismatches, including small numeric differences. Use isTRUE(all.equal(x, y)) when a Boolean result is required.

Editorial QA Checklist for This R Data Frame Comparison Tutorial

  • Confirm that the compare package is installed before running the original compare() examples.
  • Verify that data frames are aligned by a unique key before using element-wise comparisons.
  • Check column names, column order, and data types before interpreting value differences.
  • Test missing-value comparisons with cases where both values are NA and where only one value is NA.
  • Run row-difference methods in both directions so that added and removed records are both reported.
  • Use a documented and domain-appropriate tolerance for floating-point comparisons.

Conclusion

In this R Tutorial, we learned how to compare two data frames using compare(), identical(), all.equal(), element-wise comparisons, joins, and set-difference operations. The correct method depends on whether you need strict equality, changed cells, unmatched rows, schema differences, key-based record matching, or tolerance-aware numeric comparison.