I am trying to pass a string through a function to call a column from a data frame. I made a very simple if-else function to make sure the foundation was working before adding complexity. Essentially, if the column name is found within the data frame, print "Hi", if not, print "No". The function correctly identifies if the column name is in the data frame but it prints a duplicate "No".
I have tried using if(){}else(){}
Using ifelse()
Using break and next with the if(){}else(){} method
df <- as.data.frame(cbind("a" = 1:5,
"b" = 6:10))
testingIf <- function(x){
if(x %in% colnames(df)){
print("Hi")
}
else{
(print("No"))
}
}
testingIf("a")
testingIf("This is true")
if(){}else(){}
-This was my initial attempt and if the 'if' is true it works as intended, but if the 'if' is false it prints out [1] No [1] No.
Using ifelse()
-Prints out [1] Hi [1] Hi when true and [1] No [1] No when false
Using break and next with the if(){}else(){} syntax
-This works so far as it give me the correct output but I get
"Error: no loop for break/next, jumping to top level"
, which makes me feel like I am missing something.
Your function can be made simpler by using the ifelse() function.
Using your dataframe df <- as.data.frame(cbind("a" = 1:5,"b" = 6:10)), we can rewrite your function as:
testingIf <- function(x,dt){
ifelse(x %in% colnames(dt),"Hi","No")
}
Now testing the code, we have:
> testingIf("a",df)
[1] "Hi"
> testingIf("This is true",df)
[1] "No"
Hope this helps!
Related
I am new to R programming. I don't know whether we could use switch statements for numerical objects.
This is my code,
myfunction <- function() {
x <- 10
switch(x,
1={
print("one")
},
2={
print("two")
},
3={
print("three")
},
{
print("default") #Edited one..
}
)
}
I got this error,
test.R:4:18: unexpected '='
3: switch(x,
4: 1=
^
Please help me out this problem.
To take full advantage of switch's functionality (in particular its ability to handle arbitrary values with a final "default" expression) and to handle numbers other than 1,2,3,..., you'd be better off converting any input to a character string.
I would do something like this:
myfunction <- function(x) {
switch(as.character(x),
"1" = print("one"),
"2" = print("two"),
"3" = print("three"),
print("something other than 'one', 'two', or 'three'"))
}
myfunction(1)
# [1] "one"
myfunction(345)
# [1] "something other than 'one', 'two', or 'three'"
myfunction <- function(x) {
switch(x,
print("one"),
print("two"),
print("three"))}
myfunction(1)
## [1] "one"
Edit:
As mentioned in comments, this method isn't evaluating the values that are being entered, rather uses them as an index. Thus, it works in your case but it won't work if the statements were to be reordered (see #Joshs answer for better approach).
Either way, I don't think switch is the right function to use in this case, because it is mainly meant for switching between different alternatives, while in your case, you are basically running the same function over and over. Thus, adding extra a statement for each alternative seems like too much work (if you, for example, wanted to display 20 different numbers, you'll have to write 20 different statements).
Instead, you could try the english package which will allow you to display as many numbers as you will define in the ifelse statement
library(english)
myfunction2 <- function(x) {
ifelse(x %in% 1:3,
as.character(as.english(x)),
"default")}
myfunction2(1)
## [1] "one"
myfunction2(4)
## [1] "default"
Alternatively, you could also avoid using switch (though not necessarily recommended) by using match
myfunction3 <- function(x) {
df <- data.frame(A = 1:3, B = c("one", "two", "three"), stringsAsFactors = FALSE)
ifelse(x %in% 1:3,
df$B[match(x, df$A)],
"default")}
myfunction3(1)
## [1] "one"
myfunction3(4)
## [1] "default"
I would suggest reading the ?switch help page. This seems fairly well described there. Names in R can never be numeric, ie c(1=5) is not allowed, nor is f(1=5, 2=5). If you really have 1,2 or 3, then you want just
switch(x,
{print("one")},
{print("two")},
{print("three")}
)
(omit the names for numeric values)
I have two fields:
FirstVisit
SecondVisit
I am building a function to pull data from either field depending on user input (heavily reduced yet relevant version of function):
pullData(visit){
# Do something
}
What I am looking to do is for the function to take the user's input and use it to form part of the call to the data frame field.
For example, when the user runs:
pullData(First)
The function will run like this:
print(df$FirstVisit)
Conversely, when the user runs:
pullData(Second)
The function will run:
print(df$SecondVisit)
My function is considerably more complex than this, but this basic example relates to just the specific aspect of it that I am trying to work out.
So far I have tried something like:
print(paste0(df["df$", visit, "Visit", ])
# The intention is to result in df$FirstVisit or df$SecondVisit depending on the input
And this:
print(paste0(df[df$", visit, "Visit, ])
# Again, intended result should be df$FirstVisit or df$SecondVisit, depending on the input
among other alternatives (some with paste()), yet nothing has worked so far.
I suspect that it is possible and feel that I am close.
How can I achieve this?
If you really want to run the function like pullData(First), you need to use metaprogramming (to get the name of the argument instead of the arguements value) like
pullData <- function(...) {
arg <- rlang::ensyms(...)
if(length(arg)!=1) stop("invalid argument in pullData")
dataName <- paste0(as.character(arg[[1]]),"Visit")
print(df[[dataName]])
}
If you can manage to call the function with a character-argument like pullData("First"), you can simply do:
pullData <- function(choice = "First") {
dataName <- paste0(choice,"Visit")
print(df[[dataName]])
}
I am not quite sure if this is what you're going for, but here's a possible solution:
pullData <- function(visit){
visit <- rlang::quo_text(enquo(visit))
visit <- tolower(visit)
if (visit %in% c("first", "firstvisit")){
data <- df$FirstVisit
}
if (visit %in% c("second", "secondvisit")){
data <- df$SecondVisit
}
data
}
Using this sample data:
df <- data.frame(FirstVisit = c("first value"),
SecondVisit = c("second value"))
Gets us:
> pullData(first)
[1] "first value"
> pullData(second)
[1] "second value"
For the sake of completeness, R allows for partial matching when subsetting with character indices; see help("$").
df <- data.frame(FirstVisit = 11:12, SecondVisit = 21:22)
For interactive use:
df$F
[1] 11 12
df$S
[1] 21 22
For programming on computed indices, the [[ operator has to be used, e.g.,
df[["F", exact = FALSE]]
[1] 11 12
This can be wrapped in a function call:
pullData <- function(x) df[[x, exact = FALSE]]
Thus,
pullData("F")
pullData("Fi")
pullData("First")
pullData("FirstVisit")
return all
[1] 11 12
while
pullData("S")
pullData("Second")
return
[1] 21 22
But watchout when dealing with user supplied input as typos might lead to unexpected results:
pullData("f")
pullData("first")
pullData("Frist")
NULL
I am wondering what is the difference between hasArg() and exist() in a R function. It seems that hasArg() works while exist does not work to test if an argument exists in the input of a R function.
f_exists <- function(x){
if(exists("x")){
print("exist")
}else{
print("Non existence")
}}
When I test this f_exists function, the exist seems not to be working in the ifelse statement:
> f_exists(x = 1)
[1] "exist"
> f_exists(x)
[1] "exist"
> f_exists()
[1] "exist"
However, if I use the function hasArg() in the ifelse statement, the function works:
f_hasArg <- function(x){
if(hasArg("x")){
print("exist")
}else{
print("Non existence")
}
}
> f_hasArg(x = 1)
[1] "exist"
> f_hasArg(x)
[1] "exist"
> f_hasArg()
[1] "Non existence"
However, it is weird that exist() and hasArg() seem to be working in a reversed way when I test them in the environment:
> rm(list = ls())
> exists("x")
[1] FALSE
> hasArg("x")
[1] FALSE
> x <- 1
> exists("x")
[1] TRUE
> hasArg("x")
[1] FALSE
I am asking why is hasArg() and exists() work in a different way in R functions and in the environment? Is there a underlying reason for that? Thanks.
f_exists() returns "exist", because variable x actually exists inside function f_exists, yet it is bound to a special "missing" value. Using that value would result in an error ("argument 'x' is missing, with no default), but as the value is not used, no error is reported.
hasArg("x") returns false, because it is looking really at (formal) arguments of the surrounding function call, not at any variables in the current environment. The documentation has more details: ?exists and ?hasArg.
To check whether a value has been explicitly provided by the caller for a formal argument x of a function, one can use missing(x). If the same could be achieved by providing a default argument expression, it is probably cleaner to do so.
I am new to R programming. I don't know whether we could use switch statements for numerical objects.
This is my code,
myfunction <- function() {
x <- 10
switch(x,
1={
print("one")
},
2={
print("two")
},
3={
print("three")
},
{
print("default") #Edited one..
}
)
}
I got this error,
test.R:4:18: unexpected '='
3: switch(x,
4: 1=
^
Please help me out this problem.
To take full advantage of switch's functionality (in particular its ability to handle arbitrary values with a final "default" expression) and to handle numbers other than 1,2,3,..., you'd be better off converting any input to a character string.
I would do something like this:
myfunction <- function(x) {
switch(as.character(x),
"1" = print("one"),
"2" = print("two"),
"3" = print("three"),
print("something other than 'one', 'two', or 'three'"))
}
myfunction(1)
# [1] "one"
myfunction(345)
# [1] "something other than 'one', 'two', or 'three'"
myfunction <- function(x) {
switch(x,
print("one"),
print("two"),
print("three"))}
myfunction(1)
## [1] "one"
Edit:
As mentioned in comments, this method isn't evaluating the values that are being entered, rather uses them as an index. Thus, it works in your case but it won't work if the statements were to be reordered (see #Joshs answer for better approach).
Either way, I don't think switch is the right function to use in this case, because it is mainly meant for switching between different alternatives, while in your case, you are basically running the same function over and over. Thus, adding extra a statement for each alternative seems like too much work (if you, for example, wanted to display 20 different numbers, you'll have to write 20 different statements).
Instead, you could try the english package which will allow you to display as many numbers as you will define in the ifelse statement
library(english)
myfunction2 <- function(x) {
ifelse(x %in% 1:3,
as.character(as.english(x)),
"default")}
myfunction2(1)
## [1] "one"
myfunction2(4)
## [1] "default"
Alternatively, you could also avoid using switch (though not necessarily recommended) by using match
myfunction3 <- function(x) {
df <- data.frame(A = 1:3, B = c("one", "two", "three"), stringsAsFactors = FALSE)
ifelse(x %in% 1:3,
df$B[match(x, df$A)],
"default")}
myfunction3(1)
## [1] "one"
myfunction3(4)
## [1] "default"
I would suggest reading the ?switch help page. This seems fairly well described there. Names in R can never be numeric, ie c(1=5) is not allowed, nor is f(1=5, 2=5). If you really have 1,2 or 3, then you want just
switch(x,
{print("one")},
{print("two")},
{print("three")}
)
(omit the names for numeric values)
The following function does work, but the last as.Date part was more or less an result of trial and error that do not understand fully.
### This function creates a real date column out of year / period that is saved in
### in separate columns, plus it handles a 13th period in case of overlapping period
### terminology. Turns quarters into months.
realDate <- function (table,year="year_col",period="period_col"){
if (is.character(table) == TRUE)
{
dframe <- get(table)
}
else{
dframe <- table
}
x <- expression({resDate <- with(dframe,
as.Date(paste(get(year),"-",
ifelse(get(period) > 9, get(period),
paste("0", get(period), sep = "")),
"-01", sep = "")))
})
y <- expression({resDate <- with(dframe,as.Date(paste(get(year) + 1,"-","01","-01",sep="")))})
#### I do not get this? Why do I have to do this?
a <- ifelse(get(period) == 13,eval(y),eval(x))
a <-as.Date(a, origin="1970-01-01")
return(a)
}
Instead I tried to do it like this (because it was more intuitively to me):
{ ....
ifelse(get(period) == 13,eval(y),eval(x))
return(resDate)
}
This returned the corrected values whenever the condition was FALSE (no) but returned NA if the condition was TRUE (yes). Why is that? And if I use the function above, why do I have to define the origin again? Why I even have call as.Date again?
EDIT:
a <- rep(2002:2010,2)
b <- rep(1:13,2)
d<-cbind(a,b[1:length(a)])
names(d) <- c("year_col","period_col")
P.S.:
I found this thread on vectorized ifelse.
Your construct is "interesting" at least. To start with, neither x nor y gives output. I wonder why you use an assignment in your eval(). this gives you a resDate vector that is exactly what the last call has been. And that is not dependent on the condition, it's the last one written (eval(x) in your case). They get executed before the ifelse clause is executed.
Plus, the output you get is the numeric representation of your data, not the data object. That is in resDate. I guess that ifelse cannot determine the class of the output vector as you use the eval() inside. I'm surprised you get output at all, in fact you're effectively using something that could be called a "bug" in R (Microsoft would call it a feature :-) ).
Your mistake is in your ifelse : get(period) doesn't exist. it should be get(period, dframe). Then it works. The only reason why it works on your computer, is because you have a period in your workspace presumably. Classis problem when debugging.
In any case, I'd make it:
realDate <- function (table,year="year_col",period="period_col"){
if (is.character(table)){ # is.character(table) returns a boolean already.
dframe <- get(table)
} else {
dframe <- table
}
year <- get(year,dframe)
period <- get(period,dframe)
year[period==13] <- year[period==13]+1
period[period==13] <- 1
as.Date(paste(year,"-",period,"-01",sep=""))
}
This is quite a bit faster than your own, has less pitfalls and conversions, and is more the R way of doing it. You could change year[...] and period [...] by ifelse constructs, but using indices is generally faster.
EDIT :
This is easier for the data generation:
dframe <- data.frame(
year_col= rep(2006:2007,each=13),
period_col = rep(1:13,2)
)
realDate(dframe)
[1] "2006-01-01" "2006-02-01" "2006-03-01" "2006-04-01" "2006-05-01"
"2006-06-01" "2006-07-01" "2006-08-01" "2006-09-01"
[10] "2006-10-01" "2006-11-01" "2006-12-01" "2007-01-01" "2007-01-01"
"2007-02-01" "2007-03-01" "2007-04-01" "2007-05-01"
[19] "2007-06-01" "2007-07-01" "2007-08-01" "2007-09-01"
"2007-10-01" "2007-11-01" "2007-12-01" "2008-01-01"