Using map() I wrote a simple function that was supposed to return 'yes' if x is 1, 'no' if x is 2, 'maybe' if x is 3.
map(
.x = c(1,2,3),
.f = function(x) {
if (x==1) y= 'yes'
if (x==2) y='no'
if (x==3) y='maybe'})
I expected to get a list with 'yes', 'no', 'maybe', yes I got:
[[1]]
NULL
[[2]]
NULL
[[3]]
[1] "maybe"
So it seems that every if() is evaluated and only the last one is returned, which is NULL when x is 1 or 2. My question is why? I would expected if() to behave like in the global environment:
x = 1
if (x==1) y='yes'
if (x==2) y='no'
if (x==3) y='maybe'})
y
[1] "yes"
You're assigning a new value to y three times. Use else to keep make it one expression.
map(
.x = c(1,2,3),
.f = function(x) {
if (x==1) y = 'yes'
else if (x==2) y = 'no'
else if (x==3) y = 'maybe'})
#[[1]]
#[1] "yes"
#
#[[2]]
#[1] "no"
#
#[[3]]
#[1] "maybe"
R is an expressions-based language. Expressions have value. (Almost) everything is an expression. Meaning everything ultimately has one value.
The function body in the map ends up only having one value, the last if-statement.
So add else, e.g.
map(
.x = c(1, 2, 3),
.f = function(x) {
if (x == 1)
y = 'yes'
else if (x == 2)
y = 'no'
else if (x == 3)
y = 'maybe'
}
)
Related
I am mapping some values to different categories. Some of the values have character(0), which I want to map to 'Other'. But I cannot get it to return anything else than character(0). I have tried using different methods (using length(character(0) == 0 for example).
### Map code 1100 to House.
test_vec = '1100'
case_when(
test_vec == '1100' ~ 'House'
)
#[1] House
### Map character(0) to Other
test_vec = character(0)
case_when(
test_vec == '1100' ~ 'House',
identical(test_vec, character(0)) ~ 'Other'
)
#Character(0)
I would say that it comes from the test test_vec == 1100, which will throw a logical(0) instead of a TRUE/FALSE if test_vec is a character(0).
One way to bypass could be to add a positive length condition on every test of test_vec value:
test_vec = character(0)
case_when(
length(test_vec) > 0 && test_vec == '1100' ~ 'House',
identical(test_vec, character(0)) ~ 'Other'
)
[1] "Other"
So here, i get errors with imap, when using the index (".y") to go through a list. below I have made it work with map2, but this is confusing, because the way I made the map2() function is exacly the same way I would've thought that imap would do it. But it clearly isn't, otherwise it wouldn't error out.
I would love to understand the purrr logic as good as possible, could anyone tell me what's going on?
library(purrr)
l1 <- list(a='a', b='b')
# single brackets - 'missing value where TRUE/FALSE needed'
imap(l1, ~{
y1 <- names(l1)[.y]
if(y1 == 'a') out1 <- TRUE
if(y1 == 'b') out1 <- FALSE
out
})
# double brackets - subscript out of bounds
imap(l1, ~{
y1 <- names(l1)[[.y]]
if(y1 == 'a') out1 <- TRUE
if(y1 == 'b') out1 <- FALSE
out
})
# emulating what I think imap() does
map2(l1, seq_along(l1), ~{
y1 <- names(l1)[.y]
if(y1 == 'a') out1 <- TRUE
if(y1 == 'b') out1 <- FALSE
out1
})
If the names are present in the list, .y is the name of the list and not it's index. So,
names(l1)['a'] #returns
#[1] NA
which explains 'missing value where TRUE/FALSE needed'.
and
names(l1)[['a']]
returns
Error in names(l1)[["a"]] : subscript out of bounds
What you need is -
purrr::imap(l1, ~{
if(.y == 'a') out1 <- TRUE
if(.y == 'b') out1 <- FALSE
out1
})
#$a
#[1] TRUE
#$b
#[1] FALSE
perhaps you simply need
imap(l1, ~ if(.y == 'a') TRUE else FALSE)
$a
[1] TRUE
$b
[1] FALSE
How to program a condition in r that chooses one or two elements of a vector?
I tried to use ifelse() but it demands to have the same length both in test and yes, no arguments.
object <- sample(c("A","B","C"),1)
ifelse(object %in% c("A","A"), c(1,2),
ifelse(object=="B", 1,
ifelse(object=="C",2,NaN)))
I want to obtain 1,2 when object is "A"
If the object is of length 1, we can use if/else
f1 <- function(obj) if(obj == 'A') 1:2 else if(obj == 'B') 1 else if(obj == 'C') 2 else NaN
f1(object)
#[1] 1 2
Or with switch
f2 <- function(obj) switch(obj, 'A' = 1:2, 'B' = 1, 'C' = 2, NaN)
f2(object)
#[1] 1 2
f2('B')
#[1] 1
f2('C')
#[1] 2
f2('D')
#[1] NaN
My question is, does there exist a function that, given a logical statement, identifies the source of FALSE (if it is false)?
For example,
x=1; y=1; z=1;
x==1 & y==1 & z==2
Obviously it is the value of z that makes the statement false. In general though, is there a function that let's me identify the variable(s) in a logical statement who's value makes a logical statement false?
Instead of writing x==1 & y==1 & z==2 you could define
cn <- c(x == 1, y == 1, z == 2)
or
cn <- c(x, y, z) == c(1, 1, 2)
and use all(cn). Then
which(!cn)
# [1] 3
gives the source(s) of FALSE.
In general, no, there is no such function that you are looking for, but for different logical statements a similar approach should work, although it might be too lengthy to pursue.
Considering (!(x %in% c(1,2,3)) & y==3) | z %in% c(4,5), we get FALSE if z %in% c(4,5) is FALSE and (!(x %in% c(1,2,3)) & y==3) is FALSE simultaneously. So, if (!(x %in% c(1,2,3)) & y==3) | z %in% c(4,5) returns FALSE, we are sure about z and still need to check x and y, so that the list of problematic variables can be obtained as follows:
if(!((!(x %in% c(1,2,3)) & y==3) | z %in% c(4,5)))
c("x", "y", "z")[c(x %in% c(1,2,3), !y == 3, TRUE)]
# [1] "x" "y" "z"
or
a <- !(x %in% c(1,2,3))
b <- y == 3
c <- z %in% c(4,5)
if(!((a & b) | c))
c("x", "y", "z")[c(!a, !b, TRUE)]
# [1] "x" "y" "z"
I like #julius's answer but there is also the stopifnot function.
x <- 1; y <- 1; z <- 2
stopifnot(x == 1, y == 1, z == 1)
#Error: z == 1 is not TRUE
Not that the result is an error if there are any false statements and nothing if they're all true. It also stops at the first false statement so if you had something like
x <- T; y <- F; z <- F
stopifnot(x, y, z)
#Error: y is not TRUE
you would not be told that z is FALSE in this case.
So the result isn't a logical or an index but instead is either nothing or an error. This doesn't seem desirable but it is useful if the reason you're using it is for checking inputs to a function or something similar where you want to produce an error on invalid inputs and just keep on moving if everything is fine. I mention stopifnot because it seems like this might be the situation you're in. I'm not sure.
Here is a silly example where you might use it. In this case you apparently only want positive numbers as input and reject everything else:
doublePositiveNumber <- function(x){
stopifnot(is.numeric(x), x >= 0)
return(2*x)
}
which results in
> doublePositiveNumber("hey")
Error: is.numeric(x) is not TRUE
> doublePositiveNumber(-2)
Error: x >= 0 is not TRUE
> doublePositiveNumber(2)
[1] 4
So here you guarantee you get the inputs you want and produce and error message for the user that hopefully tells them what the issue is.
I'm trying to do if else statement which includes a condition if three variables in the data frame equal each other.
I was hoping to use the identical function but not sure whether this works for three variables.
I've also used the following but R doesn't seem to like this:
geno$VarMatch <- ifelse((geno[c(1)] != '' & geno[c(2)] != '' & geno[c(3)] != '')
& (geno[c(5)] == geno[c(4)] == geno[c(6)]), 'Not Important', 'Important')
Keeps telling me:
Error: unexpected '=='
Am I supposed to specify something as data.frame/vector etc... Coming from an SPSS stand point, I'm slightly confused.
Sorry for the simplistic query.
I see so complicated results, mine is simple:
all(sapply(list(a,b,c,d), function(x) x == d))
returns TRUE, if all equals d all equals each other.
Here's a recursive function which generalises to any number of inputs and runs identical on them. It returns FALSE if any member of the set of inputs is not identical to the others.
ident <- function(...){
args <- c(...)
if( length( args ) > 2L ){
# recursively call ident()
out <- c( identical( args[1] , args[2] ) , ident(args[-1]))
}else{
out <- identical( args[1] , args[2] )
}
return( all( out ) )
}
ident(1,1,1,1,1)
#[1] TRUE
ident(1,1,1,1,2)
#[1] FALSE
If it's about numeric values, you can put the numbers in an array, then check the array's max and min, as well:
if(max(list) == min(list))
# all numbers in list are equal
else
# at least one element has a different value
You need to use:
geno$VarMatch <- ifelse((gene[c(1)] != '' & gene[c(2)] != '' &
gene[c(3)] != '') &
((gene[c(5)] == gene[c(4)]) &
(gene[c(4)] == gene[c(6)]))),
'Not Important', 'Important')
The == is a binary operator which returns a single logical value. R doesn't expect further input past your first evaluation, unless you feed it a Boolean & for vectors. You may want to modify this, but here's one attempt at a functional programming approach:
testEqual <- function(x, y) ifelse(x == y, x, FALSE)
all(!!Reduce(testEqual, list(1:10, 1:10))) # True
all(!!Reduce(testEqual, rep(T, 3))) # True
all(!!Reduce(testEqual, list(1, 5, 10))) # False
all(!!Reduce(testEqual, list(T, T, F))) # False
The double negation is used to convert values to logical vectors, and the all command returns a single Boolean. This only works for numeric values or logical vectors.
I'm throwing this out here just for fun. I'm not sure if I would actually use this approach, but any critiques are welcomed.
This answer is based on #John's comment under the OP. This is by far the easiest way to go about this.
geno$VarMatch <- ifelse((geno[c(1)] != '' & geno[c(2)] != '' & geno[c(3)] != '')
& (geno[c(5)] == geno[c(4)] & geno[c(5)] == geno[c(6)]), 'Not Important', 'Important')
Simpler than the other answers, and can be used with basic subsetting/ assignment too, e.g.
geno$VarMatch[geno[c(5)] == geno[c(4)] & geno[c(5)] == geno[c(6)]] <– 'Important'
I think you can just come up with simple generic function comparing three elements and then using mutate and rowwise from dplyr apply those to each combination.
library("tidyverse")
set.seed(123)
dta_sample <- tibble(
colA = sample(letters, 10000, TRUE),
colB = sample(letters, 10000, TRUE),
colC = sample(letters, 10000, TRUE)
)
compare_strs <- function(one, two, three) {
if (one == two) {
if (two == three) {
return(TRUE)
} else {
return(FALSE)
}
} else {
return(FALSE)
}
}
dta_sample %>%
rowwise() %>%
mutate(all_cols_identical = compare_strs(colA, colB, colC)) %>%
# For results
filter(all_cols_identical)
Preview
# A tibble: 25 x 4
# Rowwise:
colA colB colC all_cols_identical
<chr> <chr> <chr> <lgl>
1 w w w TRUE
2 k k k TRUE
3 m m m TRUE
4 b b b TRUE
5 y y y TRUE
6 n n n TRUE
7 e e e TRUE
8 j j j TRUE
9 q q q TRUE
10 a a a TRUE
# … with 15 more rows