Reputation:
My task is to create a function which checks if the element of a vector either a number or a character. But the problem is that i have a vector with different types of data. Why does not my code work?
vector <- c(10L, 3.8, FALSE, "HELLO")
#enter code here
data_types <- function(x = vector){
for(i in 1:length(x)){
if(is.character(as.character(x[i]))){
print('it is a character')}
else if(is.numeric(as.numeric(x[i]))){
print('it is a number')}
else {
print('neither number nor a character')}
}
}
data_types()
Upvotes: 0
Views: 1635
Reputation: 161155
Problems with that approach:
In R, in a vector, all elements must be the same class. If this means that you have a number and a string defined, then they are both strings. See the contents of your vector
to see this:
vector <- c(10L, 3.8, FALSE, "HELLO")
vector
# [1] "10" "3.8" "FALSE" "HELLO"
While function(x = vector)
seems like a good idea to provide a sane default value, this is bad for two reasons:
It relies on the presence of a specific object in the calling environment. While this by itself is not bad, it should be done in a way that either will not fail or fails with clarity if the object cannot be found. I've occasionally done something like:
func <- function(x) {
if (missing(x) && exists("otherobject")) x <- otherobject
...
}
One caution from this is exists("vector")
will always be true because of the function base::vector
(leading me to the next point), I suggest perhaps you call your object vec
instead.
BTW: this really adds very little convenience here, and carries a bit more risk than necessary. I suggest you don't use it, instead requiring the user to provide it in the call.
In its current state, while you have vector
defined as your vector and it does exist, if you forget to define it in a future R session and then call this function, it will fail with cannot coerce type 'closure' to vector of type 'character'
, which is not helpful. Why? Because it finds base::vector
, and as.character(base::vector)
cannot work.
The use of is.character(as.character(.))
(and is.numeric(as.numeric(.))
) is defeating yourself. If something can be coerced to character
(and numeric
), then it will, and then it will pass the is.*
test. Period. The only time is.*(as.*(.))
will not be TRUE
is when it fails (as in as.*(base::vector)
. One must do the test without coercing/casting first.
Mild note: for (i in 1:length(x))
makes sense, but if for some reason you pass a vector/list of length 0
, this will fail. Why? Intuitively, length 0 suggests that the for
loop would not iterate, but 1:length(x)
(with an empty vector) reduces to 1:0
which is length 2, so i
will be 1
and then 0
. It is better to use for (i in seq_along(x))
, which will do 1:length(x)
when it is not empty, and will do nothing when it is empty.
I'm going to assume that you instead mean to test each element of a list
, in which different classes can coexist:
data_types <- function(x) {
for (i in seq_along(x)) {
if (is.character(x[[i]])) {
print('it is a character')
} else if (is.numeric(x[[i]])) {
print('it is a number')
} else {
print('it is neither a number nor a character')
}
}
}
mylist <- list(10L, 3.8, FALSE, "HELLO")
data_types(mylist)
# [1] "it is a number"
# [1] "it is a number"
# [1] "it is neither a number nor a character"
# [1] "it is a character"
Or one could just use:
sapply(mylist, typeof)
# [1] "integer" "double" "logical" "character"
You can also use class
instead of typeof
, but some objects in R have two or more strings there (e.g., class(Sys.time())
), so it's not always as homogeneous a return value).
Upvotes: 1