I am interested identifying numeric scalars like:
doub <- 3.14
intg <- 8L
I know that these are treated as length one vectors. Thus, for any R object x
, is is.vector(x) && length(x) == 1
the right way to check whether x
is a scalar? length(x) == 1
by itself is not sufficient as it returns a true, when it should return false, for a data frame with one column or a list with one element.
Is there a reason why there is no such function is.scalar
implemented in base R
? For some reason the two I could find in other functions fail for the data frame case mentioned previously, these are:
assertthat::is.scalar(data.frame(a = 1:2))
lambda.tools::is.scalar(data.frame(a = 1:2))
Why are the results of these two function calls different to my understanding (and definition) of how a is.scalar
function should work?
vector(x) && length(x) == 1 the right way to check whether x is a scalar? length(x) == 1 by itself is not sufficient as it returns a true, when it should return false, for a data frame with one column or a list with one element.
First, R doesn't have scalar type which means the smallest unit in R language is vector. For instance, “John” is a vector that is represented as c(“John”), Likewise when we see a scalar value100, it is actually a vector of length 1 as c(100).
The simplest object type in R is a scalar. A scalar object is just a single value like a number or a name.
A scalar data type is something that has a finite set of possible values, following some scale, i.e. each value can be compared to any other value as either equal, greater or less. Numeric values (floating point and integer) are the obvious examples, while discrete/enumerated values can also be considered scalar.
I think is.atomic
suits your needs.
For why is.vector
is probably incompatible, see, e.g.:
is.atomic(list(1))
# [1] FALSE
is.vector(list(1))
# [1] TRUE
On your objects:
is.scalar <- function(x) is.atomic(x) && length(x) == 1L
is.scalar(doub)
# [1] TRUE
is.scalar(intg)
# [1] TRUE
is.scalar(c(doub, intg))
# [1] FALSE
Building on the answer by @MichaelChirico, there are a couple of other things that is.scalar()
should check for.
Firstly, complex numbers are not usually regarded as scalars (although I think this usage may vary between disciplines).
comp <- 2+3i
is.scalar <- function(x) is.atomic(x) && length(x) == 1L
is.scalar(comp)
# TRUE
so we should also check for complex numbers. The simple, but naive, way to do this is to use is.complex
is.scalar <- function(x) is.atomic(x) && length(x) == 1L && !is.complex(x)
is.scalar(comp)
# FALSE
Unfortunately, this is not quite right, because is.complex
just tests whether the class is "complex"
. But real numbers can have class=complex if their imaginary component is zero.
is.complex(2+0i)
# [1] TRUE
So to test for real numbers we are better off to check that the imaginary component is zero using Im(x)==0
. So, this leads us to a test for scalars that look like this
is.scalar <- function(x) is.atomic(x) && length(x) == 1L && Im(x)==0
More trivially, characters ought also be eliminated
is.scalar("x")
# TRUE
is.scalar <- function(x) is.atomic(x) && length(x) == 1L && !is.character(x) && Im(x)==0
is.scalar("x")
# FALSE
Note that we test for is.character(x)
before Im(x)==0
so that lazy evaluation ensures that the function never tries to find the imaginary component of a character, which would throw an error.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With