The hardware and bandwidth for this mirror is donated by METANET, the Webhosting and Full Service-Cloud Provider.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]metanet.ch.
Base R gives you plenty of is.*() checks —
is.null(), is.na(), is.numeric(),
is.vector() and so on — but it does not give you their
opposites. In practice, negated checks are extremely common: “is this
argument not missing”, “is this object not a data
frame”, “is this value not already in my lookup vector”.
Without a dedicated function, every one of these checks ends up written
as !is.null(x), !is.na(x),
!(x %in% table), scattered across a script with a leading
! that is easy to miss when skimming code, and easy to
accidentally drop or double up when editing.
The quickcode package’s NOT functions exist to make these checks
first-class, readable, and safe to compose. not.null(x)
reads exactly like the English sentence it represents, so validation
code and guard clauses stop requiring a mental “flip the meaning” step
every time a reader hits a !.
This vignette walks through the full family of NOT functions, what each one actually checks, and some patterns for using them together to validate function inputs and clean up data.
not.null Opposite of is.null() - check if entry is NOT NULL
not.na Opposite of is.na() - check if entry is NOT NA
not.empty Check if entry is NOT empty (NULL, NA, or "")
is.empty Check if entry IS empty (companion to not.empty)
not.numeric Opposite of is.numeric()
not.integer Opposite of is.integer()
not.logical Opposite of is.logical()
not.vector Opposite of is.vector()
not.data Opposite of is.data.frame()
not.environment Opposite of is.environment()
not.Date Opposite of inherits(x, "Date")
not.duplicated Opposite of duplicated()
not.image Opposite of is.image() - file extension is NOT an image type
not.inherits Opposite of inherits() - object does NOT inherit from given class(es)
not.exists Opposite of exists() - object does NOT exist in scope
has.error Whether an expression or function call produces an error
%nin% Opposite of %in% - value is NOT in a vector/table (alias %!in%)
Every function above returns a logical value (or logical vector), so
they drop directly into if(), while(),
stopifnot(), Filter(), or logical indexing,
exactly like their is.*() counterparts.
is.null("") # FALSE
not.null("") # TRUE
not.null(NULL) # FALSE
if (not.null(45)) message("something") # prints "something"
not.null() is the one you will reach for constantly,
especially at the top of a function to make sure a required argument was
actually supplied.
not.na(NA) # FALSE
not.na(NULL) # logical(0), since is.na(NULL) is also logical(0)
if (not.na(45)) message("something") # prints "something"
Note that, just like is.na(), not.na() is
vectorized: passing a vector returns a logical vector of the same
length, one result per element.
not.empty("empty") # TRUE
not.empty('') # FALSE
not.empty(y <- NULL) # FALSE
is.empty("") # TRUE
is.empty("empty") # FALSE
if (not.empty('')) message("yes") # nothing printed, condition is FALSE
not.empty() combines three checks in one call: it is
FALSE whenever the value is NULL, is NA, or is the empty string
"". This is convenient for validating text inputs, such as
form fields or CLI arguments, where any of those three “nothing was
provided” states should be treated the same way. is.empty()
is simply the inverse, provided so both directions read naturally
depending on context.
not.numeric("45") # TRUE (a character string is not numeric)
not.numeric(45) # FALSE
is.integer(78L) # TRUE
not.integer(78L) # FALSE
not.integer(23.43) # TRUE, since 23.43 is a double, not an integer
test.env <- TRUE
test.notenv <- 0
not.logical(test.env) # FALSE
not.logical(test.notenv) # TRUE
vect1 <- list(r = 1, t = 3:10)
vect2 <- LETTERS
not.vector(vect1) # FALSE, a list is still a vector in R's type system
not.vector(vect2) # FALSE
test.dt <- data.frame(ID = 1:200, Type = "RPKG.net")
test.notdf <- list(t = 1)
not.data(test.dt) # FALSE
not.data(test.notdf) # TRUE
test.env2 <- new.env()
test.notenv2 <- list(t = 1)
not.environment(test.env2) # FALSE
not.environment(test.notenv2) # TRUE
These map one-to-one onto their base R is.*()
counterparts, so they carry the exact same semantics —
not.integer(45) is TRUE for the same reason
is.integer(45) is FALSE: an unsuffixed numeric literal in R
is a double, not an integer, unless written as 45L.
d1 <- as.Date("2024-01-01")
d2 <- "2024-01-01"
is.Date(d1) # TRUE
not.Date(d1) # FALSE
not.Date(d2) # TRUE, a plain character string is not a Date object
keep.cols <- "a character"
class(keep.cols) # "character"
not.inherits(keep.cols, "character") # FALSE
num.var <- 1L
class(num.var) # "integer"
not.inherits(num.var, "double") # TRUE
not.inherits() is the negated form of base R’s
inherits(), and accepts the same what (a
character vector of class names) and which arguments, so
you can check against several possible classes at once.
set.seed(08082023)
dtf <- sample(1:10, 15, replace = TRUE)
dtf # 3 9 10 3 8 9 6 10 5 1 2 2 2 9 8
dtf[dtf > 4 & duplicated(dtf)] # values that ARE duplicates: 9 10 9 8
dtf[dtf > 4 & not.duplicated(dtf)] # values that are NOT duplicates: 9 10 8 6 5
Because not.duplicated() mirrors
duplicated() argument-for-argument (including
incomparables), it slots directly into logical indexing
anywhere you’d normally combine !duplicated(x).
5 %nin% c(1:10) # FALSE, 5 is in 1:10
5 %nin% c(11:20) # TRUE, 5 is not in 11:20
x <- "a"
if (x %nin% letters) x # condition is FALSE, so nothing runs
# exclude specific values from a vector
vector_num1 <- number(9, max.digits = 5, seed = 1)
vector_num1[vector_num1 %nin% c(83615, 85229)] # keep everything except those two values
%nin% also has the alias %!in%, for readers
who prefer an operator that visually echoes %in% with a
! in front of it.
img.1 <- "fjk.jpg"
not.image(img.1) # FALSE
img.2 <- "fjk.bmp"
not.image(img.2) # FALSE
img.3 <- "fjk.SVG" # extension matching is case-insensitive
not.image(img.3) # FALSE
v <- c("logo.png", "business process.pdf",
"front_cover.jpg", "intro.docx",
"financial_future.doc", "2022 buybacks.xlsx")
not.image(v) # FALSE TRUE FALSE TRUE TRUE TRUE
# when a file name has no extension, both is.image() and not.image()
# return NA rather than guessing
v2 <- c("img2.jpg", NA, "northbound.xlsx", "landimg")
not.image(v2) # FALSE NA TRUE NA
is.image()/not.image() recognize a broad
list of common raster and vector image formats (PNG, JPG/JPEG, GIF, BMP,
SVG, TIFF, HEIC/HEIF, WebP, PSD, AI, and several camera raw formats
among others), so both functions are handy for filtering a directory
listing down to (or away from) image files without hand-rolling a
regular expression.
# this should not produce an error, so the result is FALSE
has.error({
x <- 8
y <- number(10)
res <- x + y
})
# this produces an error ("non-numeric argument to binary operator"),
# so the result is TRUE - but the error itself is caught, not thrown
has.error({
x <- 8
y <- "random"
res <- x + y
})
# accessing a column that doesn't exist also errors, so this is TRUE
df1 <- mtcars
has.error(df1[, "rpkg.net"])
has.error() wraps its argument in
tryCatch() and reports whether the expression raised an
error, without letting that error propagate and stop your script. This
makes it useful for validating that user-supplied code, formulas, or
file paths behave as expected before you rely on them further
downstream, without wrapping every call site in its own
tryCatch() block.
A common pattern is to combine several NOT functions at the top of a function to validate its inputs before doing any real work:
process_dataset <- function(df, id_col) {
if (not.data(df)) stop("df must be a data.frame")
if (is.empty(id_col)) stop("id_col must not be empty")
if (id_col %nin% names(df)) stop("id_col must be one of the column names in df")
if (not.numeric(df[[id_col]])) stop("the id_col column must be numeric")
# ... continue processing, confident the inputs are valid
df
}
Because every NOT function returns a plain logical value, they
compose with &&, ||,
all(), and any() exactly like base R’s own
is.*() functions — the only difference is that your
validation code reads the way you’d say it out loud.
vignette("quickcode_r_introduction") for a broader tour
of the packagevignette("nullish_coalescing_operator_r") for the
closely related %or% and %eo% operators, which
supply a fallback value instead of just testing for oneThese binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.