rguides

dplyr::filter()

filter(.data, ...)

The filter() function from dplyr selects rows from a data frame or tibble based on logical conditions. It provides a readable alternative to base R’s subsetting with [ and is one of the most frequently used dplyr verbs. Unlike base R’s subsetting, filter() uses tidy evaluation, making expressions more readable and less prone to errors with non-standard evaluation.

Syntax

filter(.data, ...)

Parameters

ParameterTypeDefaultDescription
.datatibble / data.frameRequiredA tibble or data frame to filter
...logical expressionsRequiredConditions that must evaluate to TRUE for a row to be kept

Examples

Basic usage

library(dplyr)

# Create sample data
df <- data.frame(
  name = c("Alice", "Bob", "Charlie", "Diana", "Eve"),
  age = c(25, 30, 35, 28, 22),
  department = c("Sales", "Engineering", "Sales", "Marketing", "Engineering")
)

# Filter rows where age is greater than 25
filter(df, age > 25)
#      name age  department
# 1    Bob  30 Engineering
# 2 Charlie  35      Sales
# 3  Diana  28  Marketing

The example above keeps only rows where age exceeds 25, returning a reduced tibble with three matching rows. The condition can reference any columns in the data frame using standard comparison operators, and you can combine multiple logical tests to create more selective filters.

Multiple conditions

When you need to combine several criteria, separate them with commas (implicit AND) or use the & and | operators explicitly. All conditions must evaluate to TRUE for a row to be kept when combined with commas or &; with |, a row is kept if any condition is true:

# Filter with multiple conditions (AND logic)
filter(df, department == "Sales" & age > 30)
#      name age department
# 1 Charlie  35      Sales

# Use OR logic with |
filter(df, department == "Sales" | department == "Marketing")
#      name age  department
# 1 Charlie  35      Sales
# 2  Diana  28  Marketing

The OR operator is useful for checking membership in a small set of values without writing repeated == comparisons. For larger sets, %in% is more concise and readable than chaining multiple | conditions together.

Using helper functions

filter() works with any function that returns a logical vector, not just comparison operators. Functions like grepl() for pattern matching and between() for range checks integrate naturally into filter conditions, giving you expressive control over which rows are selected:

# Filter using grepl for pattern matching
filter(df, grepl("^A", name))
#   name age department
# 1 Alice  25     Sales

# Using between for range checks
filter(df, between(age, 25, 35))
#      name age  department
# 1    Alice  25      Sales
# 2      Bob  30 Engineering
# 3 Charlie  35      Sales
# 4   Diana  28  Marketing

Common patterns

  • Chaining with pipe: df %>% filter(condition) %>% select(col1, col2)
  • Using %in%: filter(df, name %in% c("Alice", "Bob"))
  • Negating conditions: filter(df, !is.na(column))
  • Filtering with slice: Combine with slice_head() or slice_sample() for subsetting
  • Filtering across multiple columns: Use if_all() or if_any() with column ranges

Advanced filtering techniques

# Filter with row-wise conditions using if_all
filter(df, if_all(everything(), ~ !is.na(.)))

# Filter with OR across columns
filter(df, if_any(c(age, name), ~ . > 30))

# Using na_if to convert values before filtering
filter(df, age > na_if(0, NA))

Performance considerations

The filter() function is optimized to work efficiently with large datasets. For very large data, consider using filter() after select() to minimize the data being processed. When working with database backends via dbplyr, filter() translates your conditions to SQL WHERE clauses, pushing the filtering to the database server for optimal performance.

filter() keeps rows where all conditions are TRUE. Multiple conditions separated by commas are combined with &. Use | for OR conditions. NA values in conditions produce NA (not TRUE or FALSE), so those rows are dropped. filter(across(where(is.numeric), ~ .x > 0)) filters to rows where all numeric columns are positive. For negation, use ! or filter(!condition).

Filter logic and NA handling

filter() returns rows where the condition evaluates to TRUE. Rows where the condition evaluates to FALSE or NA are dropped. This NA behavior is important: a comparison involving NA returns NA, not FALSE, and NA rows are silently excluded. If your data contains NA values in the filter columns and you want to retain rows where the value is NA, you must explicitly include them with is.na() combined with the condition using |.

Multiple conditions passed to filter() are combined with AND: all conditions must be TRUE for a row to be retained. This is equivalent to using & explicitly. To combine conditions with OR, where any condition being true is sufficient, use | explicitly within a single condition argument. Misunderstanding AND versus OR combination is a frequent source of filter bugs where too many or too few rows are retained.

Filtering with variables

filter() uses data masking, which means column names are referenced without quotes inside the condition. When the column name comes from a variable, a function argument or a computed string, use .data[[col_name]] with double brackets. This .data pronoun accesses columns by name from a character string, enabling programmatic filter conditions where the column to filter is determined at runtime.

For filtering based on whether a column value is in a set of values, %in% is more readable than multiple == conditions joined with |. filter(x %in% c("a", "b", "c")) is equivalent to filter(x == "a" | x == "b" | x == "c") but scales to larger sets without modification. The negation !x %in% c(...) excludes the specified values, which is the equivalent of a SQL NOT IN clause.

See also

  • dplyr::arrange()
  • dplyr::select()
  • dplyr::mutate() filter(if_any(where(is.numeric), ~ .x > 100)) keeps rows where any numeric column exceeds 100, a powerful tidy-select application.filter() handles NA in conditions strictly: NA & TRUE is NA, not TRUE, so rows with NA in a filter condition are dropped. This catches many bugs — filter(df, year > 2020) silently drops rows where year is NA. Use filter(df, !is.na(year), year > 2020) or filter(df, year > 2020 | is.na(year)) to handle NA explicitly. dplyr::na_if() converts specific values to NA; tidyr::replace_na() converts NA back to a specific value.