data.table fifelse() in R: Fast Vectorized If-Else
The data.table fifelse() function in R returns one of two values for each element of a logical test, in a single fast, type-safe pass. It is a stricter, quicker drop-in for base ifelse().
fifelse(x > 0, "pos", "neg") # binary label fifelse(x > 0, x, 0L) # keep value or zero fifelse(test, "y", "n", na = "unknown") # control NA rows fifelse(d > cut, d, as.Date(NA)) # Date class preserved DT[, flag := fifelse(am == 1, 1L, 0L)] # add a column by reference fifelse(v > 20, v, 0) # length-1 'no' recycled
Need explanation? Read on for examples and pitfalls.
What fifelse() does
fifelse() makes a binary choice across a whole vector at once. You give it a logical test, a yes value, and a no value. For every element where test is TRUE it returns the matching yes value, and everywhere test is FALSE it returns no.
It is data.table's answer to base R's ifelse(), rewritten for speed and type safety. The base function silently coerces types and drops attributes such as the Date or factor class. fifelse() refuses to mix types and keeps attributes intact, so the output is exactly the type you expect.
yes and no are selected in a single C-level pass. That is why it stays fast on columns with millions of rows.fifelse() syntax
The call takes one test and two values, plus an optional na. The signature is fifelse(test, yes, no, na = NA). Here test is a logical vector, yes and no are the values to return, and na sets what to return where test itself is NA.
Three rules govern every call. yes and no must be the same type, since the result is one typed vector. Each must have length 1 or the same length as test, and length-1 values are recycled. Where test is NA, the result is na, which defaults to a typed NA. See the official data.table fifelse reference for the full argument list.
fifelse() examples
These examples use the built-in mtcars dataset loaded as a data.table. Each one shows a different real task: labelling rows, keeping or replacing values, controlling NA, and preserving a date class.
The first example adds a labelled column inside a data.table. The am column is 1 for manual cars and 0 for automatic ones.
The yes value does not have to be a constant. Pass the vector itself to keep an element where the test holds and substitute something else where it does not.
When the test vector contains NA, the na argument decides what those positions return. Without it they would silently become NA in the output.
Because fifelse() keeps attributes, a Date input produces a Date output. Base ifelse() would strip the class and return raw day counts here.
fifelse() is numpy.where(cond, yes, no), which also returns one of two values element-wise across an array.fifelse vs ifelse
Pick fifelse() when type safety and speed matter, ifelse() only for quick base-R scripts. They share the same three-argument shape, but they behave differently when types disagree or attributes are involved.
| Function | Package | Type handling | NA control |
|---|---|---|---|
fifelse() |
data.table | strict, errors on mismatch | na = argument |
ifelse() |
base R | silent coercion, drops attributes | inherits test NA |
if_else() |
dplyr | strict, errors on mismatch | missing = argument |
fcase() |
data.table | strict, for 3 or more cases | default = |
The decision rule is short. If you already work in data.table, fifelse() is the natural binary choice: it is faster, refuses unsafe coercions, and preserves the Date or factor class. Base ifelse() is fine for throwaway scripts but will quietly convert a date to a number. For three or more branches, reach for data.table fcase in R instead of nesting fifelse() calls.
Common pitfalls
Most fifelse() bugs trace back to mismatched types, wrong lengths, or unhandled NA. All three surface quickly once you know the symptom.
Mixing the types of yes and no throws an error, because the result must be one typed vector. This is deliberate: base ifelse() would coerce silently and hide the bug.
na unset, which lets NA in the test leak straight into the output.Each value must be length 1 or the same length as test. A vector of any other length is rejected.
Finally, an NA in the test propagates into the result unless you supply na. The output below has a gap exactly where the test was missing.
Try it yourself
Try it: Use fifelse() to add a column mpg_flag to a data.table of mtcars: "efficient" when mpg is at least 25, otherwise "thirsty". Save the result to ex_dt.
Click to reveal solution
Explanation: fifelse() evaluates mpg >= 25 across all 32 rows, returns "efficient" where it holds, and "thirsty" everywhere else. The := operator writes the result back as a new column with no copy.
Related data.table functions
fifelse() sits in data.table's family of fast, vectorized helpers. These functions pair well with it for conditional logic and cleanup:
fcase(): the multi-case version, for three or more conditions. See data.table fcase in R.fcoalesce(): returns the first non-NAvalue across vectors. See data.table fcoalesce in R.shift(): lag or lead a column before a comparison. See data.table shift in R.setDT(): convert a data.frame to a data.table in place. See data.table setDT in R.uniqueN(): count distinct values in a result. See data.table uniqueN in R.
FAQ
What is the difference between fifelse() and base ifelse()?
Both pick between yes and no based on a logical test, but fifelse() is stricter and faster. Base ifelse() silently coerces yes and no to a common type and drops attributes, so a Date input comes back as a raw number. fifelse() errors when the types disagree and preserves the Date or factor class. It also runs in optimized C code, which is noticeably faster on large vectors.
What does the na argument in fifelse() do?
The na argument sets the value returned wherever the test vector is NA. By default it is a typed NA, so missing values in the test become missing values in the output. Passing na = "unknown" (or any value of the same type as yes and no) replaces those positions with an explicit fallback. This makes fifelse() safer than base ifelse() when your condition might itself contain NA.
Is fifelse() faster than ifelse()?
Yes, usually by a wide margin on large vectors. fifelse() runs as a single optimized C operation, while base ifelse() does more work in R and allocates extra intermediate objects. On a few hundred elements the difference is invisible, but on columns with millions of rows fifelse() is clearly faster. It is the recommended choice inside any data.table pipeline that already loads the package.
Does fifelse() preserve dates and factors?
Yes. Because fifelse() does not coerce types, a Date input returns a Date and a factor input returns a factor with its levels intact. Base ifelse() strips these attributes and hands back a plain integer or numeric vector instead. If you build conditional logic on date or factor columns, fifelse() saves you from re-applying as.Date() or factor() after every call.