rguides

is.numeric()

is.numeric(x)

The is.numeric() function tests whether an object is numeric. This includes both integer and double (floating-point) types in R.

Syntax

is.numeric(x)

is.numeric() is a mode-checking function that tests whether an object’s underlying storage mode is numeric, which includes both "integer" and "double" types. It accepts any R object and returns a single logical value. The check operates on the object’s internal type, not on its printed representation — a character string "42" fails even though it looks like a number, and a factor created from integers fails because factors store their values as integer codes mapped to label strings.

Parameters

ParameterTypeDefaultDescription
xany object,Object to test for numeric type

Examples

Basic usage

# Numeric values
is.numeric(42)
# [1] TRUE

is.numeric(3.14)
# [1] TRUE

is.numeric(1:5)  # integer sequence
# [1] TRUE

A common point of confusion for R beginners is that is.numeric() returns TRUE for both doubles and integers. R stores 1:5 as an integer vector and c(1.1, 2.2) as double, yet both pass the numeric test. This inclusive behavior is usually what you want — if a function accepts any kind of number, checking is.numeric(x) is the right gate. The narrower checks is.integer() and is.double() exist for the rare cases where the internal storage type actually matters, such as when passing data to compiled C or Fortran code.

Non-numeric types

# Character
is.numeric("42")
# [1] FALSE

# Logical
is.numeric(TRUE)
# [1] FALSE

# Factor (even if numeric-looking)
is.numeric(factor(1:5))
# [1] FALSE

Character strings that look like numbers, such as "42", are not numeric — is.numeric("42") returns FALSE. Factors add another layer of subtlety: even a factor created from 1:5 is not numeric, because factors store their values as integer codes that map to labels, not as quantities you can do arithmetic on. Logical vectors also fail the test, though they coerce to 0 and 1 in arithmetic contexts. Being explicit about these type boundaries saves you from silent bugs when data arrives in unexpected formats.

Numeric coercion

# as.numeric() converts to numeric
is.numeric(as.numeric(c("1", "2", "3")))
# [1] TRUE

# Warning: NAs introduced by coercion
x <- c("1", "2", "hello", "4")
is.numeric(x)
# [1] FALSE

as.numeric(x)
# [1]  1  2 NA  4
# Warning message:
# NAs introduced by coercion

The coercion example shows an important gotcha: as.numeric(c("1", "2", "hello", "4")) produces NA for the non-numeric string "hello" with only a warning, not an error. This silent partial failure is why checking is.numeric() before coercion is often better than coercing and then cleaning up NA values afterward. A defensive pattern is to validate the input type first, then coerce only when you are certain the data is clean.

Common patterns

Type-safe operations

# Only operate on numeric data
safe_divide <- function(a, b) {
  if (!is.numeric(a) || !is.numeric(b)) {
    stop("Arguments must be numeric")
  }
  a / b
}

safe_divide(10, 2)
# [1] 5

safe_divide("10", 2)
# Error in safe_divide("10", 2) : Arguments must be numeric

The safe_divide() function shows the standard defensive-programming pattern: a type guard at the top of the function that rejects non-numeric arguments before any computation happens. This produces a clear, immediate error message rather than a cryptic failure deep in the arithmetic logic. The stopifnot(is.numeric(a), is.numeric(b)) one-liner is an even shorter alternative, though it gives a less descriptive message. Both patterns are idiomatic R for writing functions that fail early and loudly.

Checking data types in data frames

df <- data.frame(
  id = 1:3,
  name = c("Alice", "Bob", "Charlie"),
  score = c(85.5, 92.0, 78.5)
)

# Check column types
sapply(df, is.numeric)
#     id   name  score 
#  TRUE FALSE   TRUE

Using sapply(df, is.numeric) on a data frame returns a named logical vector that tells you at a glance which columns are numeric. This is a fast way to subset a data frame to only its numeric columns: df[, sapply(df, is.numeric)]. The pattern is especially useful in exploratory analysis when you receive a dataset with mixed column types and need to compute summary statistics only on the quantitative variables. It is also a lightweight schema check before feeding data into a modeling function.

Numeric validation

# Validate numeric input
is_valid_numeric <- function(x) {
  is.numeric(x) && all(!is.na(x))
}

is_valid_numeric(c(1, 2, 3))
# [1] TRUE

is_valid_numeric(c(1, NA, 3))
# [1] FALSE

is_valid_numeric("1")
# [1] FALSE

The is_valid_numeric() function combines two checks: is.numeric() confirms the type is right, and all(!is.na(x)) confirms no missing values are present. This compound guard is useful for functions that do not have a built-in na.rm argument, or for workflows where missing data should be handled explicitly by the caller rather than silently ignored. The && operator short-circuits: if the first check fails, the second never runs, avoiding unnecessary computation on non-numeric inputs.

is.numeric() vs is.double() vs is.integer()

is.numeric() returns TRUE for both double (floating-point) and integer types, because both represent numeric quantities in R. is.double() returns TRUE only for doubles; is.integer() returns TRUE only for integers. Factors are not numeric even though their underlying representation is integer, is.numeric(factor("a")) is FALSE.

Use is.numeric() for general-purpose type checking when you want to accept any numeric input. Use is.double() or is.integer() when the specific storage type matters, for example, when calling C functions that require a particular type.

A common pitfall: is.numeric(1L) is TRUE (integers are numeric), but is.double(1L) is FALSE. When reading integer columns from CSV files, the resulting type depends on whether the values fit in integer range and whether you used col_types in readr.

is.numeric() is useful in defensive function programming: stopifnot(is.numeric(x)) or if (!is.numeric(x)) stop("x must be numeric") at the top of a function ensures the input type is valid before proceeding. This makes the function’s expectations explicit and produces clearer error messages than letting a type error propagate through several operations.

Type checking functions like is.numeric() are also useful in tryCatch() or withCallingHandlers() blocks when handling mixed-type inputs. When writing generic functions that should work on both data frames and vectors, checking is.numeric(x) before calling numeric functions prevents cryptic error messages and allows graceful degradation for non-numeric inputs. The methods::is() function provides more general type testing including S4 class hierarchies, which base R’s is.numeric() function does not inherently recognize or handle automatically without extra setup.

The type check functions is.integer(), is.double(), and is.numeric() can all return TRUE for the same value. An integer 1L passes all three, is.numeric(1L) is TRUE because integers are a numeric subtype in R’s type hierarchy.

One subtle difference: is.numeric(1L) returns TRUE because integer is a numeric mode, but is.double(1L) returns FALSE. When you specifically need to distinguish double from integer, for instance, before passing a value to a C extension that only accepts double — use is.double(). For most data analysis purposes, is.numeric() is the right test, since arithmetic and comparison work the same across both integer and double storage classes.

See also