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.
Related
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'
}
)
I want to check if all elements in a list are named. I've came up with this solution, but I wanted to know if there is a more elegant way to check this.
x <- list(a = 1, b = 2)
y <- list(1, b = 2)
z <- list (1, 2)
any(stringr::str_length(methods::allNames(x)) == 0L) # FALSE, all elements are
# named.
any(stringr::str_length(methods::allNames(y)) == 0L) # TRUE, at least one
# element is not named.
# Throw an error here.
any(stringr::str_length(methods::allNames(z)) == 0L) # TRUE, at least one
# element is not named.
# Throw an error here.
I am not sure if the following base R code works for your general cases, but it seems work for the ones in your post.
Define a function f to check the names
f <- function(lst) length(lst) == sum(names(lst) != "",na.rm = TRUE)
and you will see
> f(x)
[1] TRUE
> f(y)
[1] FALSE
> f(z)
[1] FALSE
We can create a function to check if the the names attribute is NULL or (|) there is blank ("") name, negate (!)
f1 <- function(lst1) is.list(lst1) && !(is.null(names(lst1))| '' %in% names(lst1))
-checking
f1(x)
#[1] TRUE
f1(y)
#[1] FALSE
f1(z)
#[1] FALSE
Or with allNames
f2 <- function(lst1) is.list(lst1) && !("" %in% allNames(lst1))
-checking
f2(x)
#[1] TRUE
f2(y)
#[1] FALSE
f2(z)
#[1] FALSE
In R, how do you test for elements of one vector NOT present in another vector?
X <- c('a','b','c','d')
Y <- c('b', 'e', 'a','d','c','f', 'c')
I want to know whether all the elements of X are present in Y ? (TRUE or FALSE answer)
You can use all and %in% to test if all values of X are also in Y:
all(X %in% Y)
#[1] TRUE
You want setdiff:
> setdiff(X, Y) # all elements present in X but not Y
character(0)
> length(setdiff(X, Y)) == 0
[1] TRUE
A warning about setdiff : if your input vectors have repeated elements, setdiff will ignore the duplicates. This may or may not be what you want to do.
I wrote a package vecsets , and here's the difference in what you'll get. Note that I modified X to demonstrate the behavior.
library(vecsets)
X <- c('a','b','c','d','d')
Y <- c('b', 'e', 'a','d','c','f', 'c')
setdiff(X,Y)
character(0)
vsetdiff(X,Y)
[1] "d"
Is there an R idiom for performing a different (integer) range check for each element of a vector?
My function is passed a two-element (integer) vector of the form v = c(m, n) and must make the following range checks:
1 <= m <= M
1 <= n <= N
For my current task, I've implemented them by manually accessing each element, and running the associated range check against it.
# Check if this is a valid position on an M x N chess board.
validate = function (square) {
row = square[1]
col = square[2]
(row %in% 1:M) && (col %in% 1:N)
}
I wonder whether there's a compacter way of doing the range checks, especially if we were to generalize it to K-element vectors.
Since you're presumably setting up different criteria for each v[j], I'd recommend creating a list out of your range criteria. Like:
Rgames> set.seed(10)
Rgames> foo<-sample(1:5,5,rep=TRUE)
Rgames> foo
[1] 3 2 3 4 1
Rgames> bar<-list(one=1:5, two=3:5,three=1:3,four=c(2,4),five=c(1,4) )
Rgames> checkit<-NA
Rgames> for(j in 1:5) checkit[j]<-foo[j]%in%bar[[j]]
Rgames> checkit
[1] TRUE FALSE TRUE TRUE TRUE
If I understand your goal correctly, the inequality operators are vectorized in R, so you can make use of this fact.
limits <- c(M=3, N=4, 5)
v <- c(m=2, n=5, 8)
result <- 1 <= v & v <= limits
# m n
# TRUE FALSE FALSE
And if you want a single value that's FALSE if any of the limits are exceeded, then you can wrap the inequality expression with all.
all(1 <= v & v <= limits)
Maybe something like this:
`%between%` <- function(x,rng){
all(x <= max(rng,na.rm = TRUE)) && all(x >= min(rng,na.rm = TRUE))
}
> 3 %between% c(1,10)
[1] TRUE
> 3:5 %between% c(1,10)
[1] TRUE
> 9:12 %between% c(1,10)
[1] FALSE
With tweaks depending on how you want to handle NAs, and other edge cases.
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