I'd like to be able to create a character vector based on the names supplied to the ... part of a function.
For instance, if I have the function foo(...) and I type foo(x, y), how do I create a character vector that looks like c("x", "y")?
I'm most interested in figuring out how to use rlang for this, but base solutions would be great as well.
Do you mean something like this?
foo <- function(...) unname(purrr::map_chr(rlang::exprs(...), as.character))
foo(x, y)
#[1] "x" "y"
identical(foo(x, y), c("x", "y"))
#[1] TRUE
Alternatively we can use as.character directly on the list returned from rlang::exprs
foo <- function(...) as.character(rlang::exprs(...))
In response to #joran's question, I'm not sure to be honest; consider the following case
as.character(rlang::exprs(NULL, a, b))
#[1] "NULL" "a" "b"
map_chr(rlang::exprs(NULL, a, b), as.character)
#Error: Result 1 is not a length 1 atomic vector
So as.character converts NULL to "NULL" whereas map_chr(..., as.character) throws an error on account of the NULL list entry.
Related
I have a function that I want to pass as an argument a vector of symbols and then internally I want to convert that vector to a character vector.
Minimal example:
fun <- function(symbols = c(a, b, c)) {
# code to convert to character vector
}
fun()
Output:
[1] "a" "b" "c"
Here's an approach with rlang::quo_name:
library(rlang)
fun <- function(symbols = c(a, b, c)) {
symbols <- enquo(symbols)
string <- quo_name(symbols)
unlist(strsplit(gsub("(c\\(|\\)|\\s)","",string),","))
}
fun(c(apple, orange, pear))
#[1] "apple" "orange" "pear"
I suspect you're actually trying to solve another problem with this, so it probably makes sense to post that as another question.
Base R solution:
fun <- function(symbols = c(a, b, c)) {
# code to convert to character vector
return(unlist(strsplit(
gsub("c\\(|\\)|\\(|\\s+", "",
deparse(substitute(symbols))), ","
)))
}
fun()
This question already has answers here:
Making a string concatenation operator in R
(5 answers)
Closed 4 years ago.
I would like to create a + method to paste character objects. One possible approach is by doing it with an infix operator:
`%+%` <- function(x, y) paste0(x, y)
"a" %+% "b" # returns "ab"
However, I'm wondering if it is possible to do the same with an S3 method. You could do it by creating a new class, let's say char, and do something like:
# Create a function to coerce to class 'char'
as.char <- function(x, ...) structure(as.character(x, ...), class = "char")
# S3 method to paste 'char' objects
"+.char" <- function(x, y) paste0(x, y)
a <- as.char("a")
b <- as.char("b")
c(class(a), class(b)) # [1] "char" "char"
a + b # returns "ab"
But, if you try to create a +.character method, it does not work:
"+.character" <- function(x, y) paste0(x, y)
a <- "a"
b <- "b"
c(class(a), class(b)) # [1] "character" "character"
a + b # Error in a + b : non-numeric argument to binary operator
However, if you assign the class character manually it does work:
as.character_ <- function(x, ...) structure(as.character(x, ...), class = "character")
a <- as.character_("a")
b <- as.character_("b")
c(class(a), class(b)) # [1] "character" "character"
a + b # returns "ab"
So I'm just wondering what I'm missing here, and if it is possible to actually define an S3 method for a generic class.
Edit: Based on #Csd answer, it is not clear that this is due to the attributes, because if you define your own function, e.g.:
concat <- function(e1, e2) UseMethod("concat")
concat.character <- function(e1, e2) paste0(e1, e2)
concat("a", "b") # returns "ab"
Then it does work.
It seems that you need to define the class of the variable to be "character". That's exactly what you do! except one thing... which I didn't know either...
Here is an example:
a <- "a"
class(a) # "character"
attributes(a) # NULL!!!
while using you function:
a <- as.character_("a")
class(a) # "character"
attributes(a) # "class" is "character"
So it seems that it has to be defined the attribute class of the variable.
For an arbitrary function
f <- function(x, y = 3){
z <- x + y
z^2
}
I want to be able take the argument names of f
> argument_names(f)
[1] "x" "y"
Is this possible?
formalArgs and formals are two functions that would be useful in this case. If you just want the parameter names then formalArgs will be more useful as it just gives the names and ignores any defaults. formals gives a list as the output and provides the parameter name as the name of the element in the list and the default as the value of the element.
f <- function(x, y = 3){
z <- x + y
z^2
}
> formalArgs(f)
[1] "x" "y"
> formals(f)
$x
$y
[1] 3
My first inclination was to just suggest formals and if you just wanted the names of the parameters you could use names like names(formals(f)). The formalArgs function just is a wrapper that does that for you so either way works.
Edit: Note that technically primitive functions don't have "formals" so this method will return NULL if used on primitives. A way around that is to first wrap the function in args before passing to formalArgs. This works regardless of it the function is primitive or not.
> # formalArgs will work for non-primitives but not primitives
> formalArgs(f)
[1] "x" "y"
> formalArgs(sum)
NULL
> # But wrapping the function in args first will work in either case
> formalArgs(args(f))
[1] "x" "y"
> formalArgs(args(sum))
[1] "..." "na.rm"
I am using the bit64 package in some R code. I have created a vector
of 64 bit integers and then tried to use sapply to iterate over these
integers in a vector. Here is an example:
v = c(as.integer64(1), as.integer64(2), as.integer64(3))
sapply(v, function(x){is.integer64(x)})
sapply(v, function(x){print(x)})
Both the is.integer64(x) and print(x) give the incorrect
(or at least) unexpected answers (FALSE and incorrect float values).
I can circumvent this by directly indexing the vector c but I have
two questions:
Why the type conversion? Is their some rule R uses in such a scenario?
Any way one can avoid this type conversion?
TIA.
Here is the code of lapply:
function (X, FUN, ...)
{
FUN <- match.fun(FUN)
if (!is.vector(X) || is.object(X))
X <- as.list(X)
.Internal(lapply(X, FUN))
}
Now check this:
!is.vector(v)
#TRUE
as.list(v)
#[[1]]
#[1] 4.940656e-324
#
#[[2]]
#[1] 9.881313e-324
#
#[[3]]
#[1] 1.482197e-323
From help("as.list"):
Attributes may be dropped unless the argument already is a list or
expression.
So, either you creaste a list from the beginning or you add the class attributes:
v_list <- lapply(as.list(v), function(x) {
class(x) <- "integer64"
x
})
sapply(v_list, function(x){is.integer64(x)})
#[1] TRUE TRUE TRUE
The package authours should consider writing a method for as.list. Might be worth a feature request ...
I have a data set df and I have 300 columns. I also have a vector names which is a vector of characters. I'm trying to eliminate the columns that match the characters in names. I tried
> head(names)
[1] "X749.-4" "X339" "X449" "X486" "X300" "X301"
real.final<-df[-names]
Error in -names : invalid argument to unary operator
Would there be a way to remove the columns mentioned in the names?
I would use setdiff instead. Here's an example:
## This is head(names)
x <- c("X749.-4", "X339", "X449", "X486", "X300", "X301")
## Imagine this is names(df)
y <- c(letters[1:2], x, LETTERS[1:2])
setdiff(y, x)
# [1] "a" "b" "A" "B"
## So, you could try:
df[, setdiff(y, x)]
The negation operator "-" will not work with character arguments passed as arguments to "[". You need to either use a lgocal vector with "!" as illustrated by user2568648, or you need to convert the character vector into numeric vector with grep:
#Failed attemtpt : real.final <- df[-grep(names, names(df) )]
Perhaps:
real.final <- df[ -as.vector(sapply(names[1], grep, x=c(names,names)))]
Another error:
real.final <- subset( df, select=-names)
Error in -"Result" : invalid argument to unary operator
Success with:
subset(df, select=-which(names(df) %in% names))
I don't like to use -which() because it will bite you if there are no "hits", but it's probably safe as an argument to subset.
You can use the which function. For example to drop the columns named "X749.-4" and "X486":
df <- df[ , -which(names(df) %in% c("X749.-4", "X486"))]
Would this work? [NO - see comment from Dwin below for correction]
subset.df<-subset(df, !(colnames(df) %in% names))