R - Vectorized implementation of ternary operator? - r

The title says it about as well as I can. What I have:
A B
TRUE FALSE
FALSE TRUE
TRUE TRUE
what I want:
C
if(A[1]&&B[1]){some.value.here}else if(A[1]){other.value}else{another.value}
if(A[2]&&B[2]){some.value.here}else if(A[2]){other.value}else{another.value}
if(A[3]&&B[3]){some.value.here}else if(A[3]){other.value}else{another.value}
I've tried ifelse but only got atomic results not vectors.

Using ifelse works fine if with a little nesting. (It would have been nice to see your attempt to figure out where you went wrong.)
A = c(TRUE, FALSE, TRUE)
B = c(FALSE, TRUE, TRUE)
C = ifelse(A & B, "both", ifelse(A, "A only", "not A"))
cbind(A, B, C)
# A B C
# [1,] "TRUE" "FALSE" "A only"
# [2,] "FALSE" "TRUE" "not A"
# [3,] "TRUE" "TRUE" "both"

If you have a data frame with two columns, try using conditionals.
As a placeholder for your real replacement values, I chose "justA", "justB", and "both".
df$result[df$A & df$B] <- "both"
df$result[df$A & !df$B] <- "justA"
df$result[df$B & !df$A] <- "justB"
df
A B result
1 TRUE FALSE justA
2 FALSE TRUE justB
3 TRUE TRUE both
4 FALSE TRUE justB
Data
df <- data.frame(A=sample(c(T,F), 4, T), B=sample(c(T,F), 4, T))
df$result <- NA

If A and B are vectors:
> A = c(TRUE, FALSE, TRUE)
> B = c(FALSE, TRUE, TRUE)
You can use mapply():
> mapply(function (x, y) ifelse(x && y, 1, 2), A, B)
[1] 2 2 1

Related

Adding a column name to a table column without a name

I have data as follows:
dat <- structure(c(TRUE, FALSE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE,
TRUE), dim = c(3L, 3L), dimnames = list(c("A", "B",
"C"), c("[0,25) D", "[0,25) E", NA)))
name_vec <- "[0,25) F"
What I want to do is the following:
colnames(dat )[length(dat )] <- name_vec[i]
But this gives the error:
Error in dimnames(x) <- dn :
length of 'dimnames' [2] not equal to array extent
I am failing to understand why this does not work or what the error means.
Any help would be appreciated.
I guess you want to do the following:
colnames(dat)[ncol(dat)] <- name_vec
[0,25) D [0,25) E [0,25) F
A TRUE TRUE TRUE
B FALSE TRUE TRUE
C TRUE TRUE TRUE
Since your dat is a matrix, length would return the number of elements in the matrix, which is 9 in your case. However, you do not have 9 columns, therefore it gives you the error.
class(dat)
[1] "matrix" "array"
length(dat)
[1] 9
ncol(dat)
[1] 3
So the correct function to use should be ncol.

Dealing with missing values when writing logical expressions

If you are familiar with SAS you know that missing values are considered as -inf, therefore, for an expression like this:
If a < 2 then c=1 ;
else c= 5;
Where "a" is missing; value of 1 will be assigned to c, because the logical expression "a < 2" will be "True". Please notice that my question is about the outcome of the logical expression. I do not care about "c".
I would like to get the same thing in R but the result of the logical expression will be "NA" :
a <-NA
a < 2
[1] NA
How can I change the output of this logical expression to "True" ?
I know I can do this:
output < ifelse( is.na(a), False, a <2)
But I am looking for something simple. Any ideas?
If you use this frequently enough, then you could define an infix operator to wrap around your ifelse:
`%<%` <- function(a, b) { ifelse(is.na(a), TRUE, a < b) }
So if a is
a <- c(NA, 1, 3, 5, NA)
Then you only need do:
a %<% 2
#> [1] TRUE TRUE FALSE FALSE TRUE
You can use the fact that NA is a logical object.
(a < 2) | is.na(a)
Just for fun, and I am absolutely not recommending this approach:
Ops.sas <- function (e1, e2) {
comparison <- switch(.Generic, `<` = , `>` = , `==` = , `!=` = ,
`<=` = , `>=` = TRUE, FALSE)
if (comparison) {
e1[is.na(e1)] <- -Inf
e2[is.na(e2)] <- -Inf
}
NextMethod(.Generic)
}
And now:
> foo <- structure(c(NA, 2,3,2, NA), class = "sas")
> bar <- structure(c(2,3,2, NA, NA), class = "sas")
> foo < bar
[1] TRUE TRUE FALSE FALSE FALSE
> foo <= bar
[1] TRUE TRUE FALSE FALSE TRUE
> foo == bar
[1] FALSE FALSE FALSE FALSE TRUE
> foo != bar
[1] TRUE TRUE TRUE TRUE FALSE
> foo > bar
[1] FALSE FALSE TRUE TRUE FALSE
> foo >= bar
[1] FALSE FALSE TRUE TRUE TRUE
if(is.na(a) | a < 2){
c=1
} else {
c=2
}
I think that the simplest way is just to add a new condition in your test. In R the | symbol stands for OR.
I think you will have to modify the values in a for example like this
a <- c(NA, 1, 3, 4, NA)
a
#> [1] NA 1 3 4 NA
a < 2
#> [1] NA TRUE FALSE FALSE NA
a[is.na(a)] <- -Inf
a < 2
#> [1] TRUE TRUE FALSE FALSE TRUE
Created on 2022-02-04 by the reprex package (v2.0.1)

R check if list of lists contains specific list

There are three lists:
a = list(1,2)
b = list(2,3)
c = list(a,b)
The command a %in% c yields FALSE FALSE. The result I would like to see is TRUE since a is an element of list c. How do I achieve this?
Check whether each component is identical to a and return TRUE if any of those comparisons are TRUE.
any(sapply(c, identical, a))
## [1] TRUE
This should also help:
list(a) %in% c
Examples:
a = list(1,2)
b = list(2,3)
c = list(a,b)
y = list(3,4)
z = list(1)
list(a) %in% c # True
list(b) %in% c # True
list(y) %in% c # False
list(z) %in% c # False

Apply, dataframes, and booleans don't work together?

In the following, logical operators don't seem to work properly.
a = c(TRUE, FALSE, TRUE, FALSE, TRUE, TRUE)
b = c('a', 'b', 'c', 'de', 'f', 'g')
c = c(1, 2, 3, 4, 5, 6)
d = c(0, 0, 0, 0, 0, 1)
wtf = data.frame(a, b, c, d)
wtf$huh = apply(wtf, 1, function(row) {
if (row['a'] == T) { return('we win') }
if (row['c'] < 5) { return('hooray') }
if (row['d'] == 1) { return('a thing') }
return('huh?')
})
Producing:
> wtf
a b c d huh
1 TRUE a 1 0 hooray
2 FALSE b 2 0 hooray
3 TRUE c 3 0 hooray
4 FALSE de 4 0 hooray
5 TRUE f 5 0 huh?
6 TRUE g 6 1 a thing
Where naively one would expect that in rows 1, 3, 5, and 6, there would be we win.
Can someone explain to me (1) why it does this, (2) how can this be fixed such that it doesn't happen, (3) why all my logical columns are seemingly changed to characters, and (4) how can a function be type-safely applied to rows in a data frame?
Why does this happen? Because is apply is made for matrices. When you give it a data frame, then the first thing that happens is it gets converted to a matrix:
m = as.matrix(wtf)
m
# a b huh huh1
# [1,] " TRUE" "a" "huh?" "hooray"
# [2,] "FALSE" "b" "huh?" "huh?"
# [3,] " TRUE" "c" "huh?" "hooray"
# [4,] "FALSE" "de" "huh?" "huh?"
# [5,] " TRUE" "f" "huh?" "hooray"
# [6,] " TRUE" "g" "huh?" "hooray"
When that happens, your different data types are lost and your data frame-style indexing doesn't work anymore:
m['a']
# [1] NA
Solution? Use a simple for loop:
wtf$huh1 = NA
for (i in 1:nrow(wtf)) {
wtf$huh1[i] = if(wtf[i, 'a']) "hooray" else "huh?"
}
If you have a function foo then
wtf$huh2 = NA
for (i in 1:nrow(wtf)) {
wtf$huh1[i] = foo(wtf[i, 'a'])
}
Functions that aren't vectorized can be vectorized to avoid the need for loops:
foov = Vectorize(foo)
# then you can
wtf$huh4 = foov(wtf$a)
Probably the easiest way to fix this is using ifelse which is vectorized, so you don't need to deal with loops, or apply:
myfunc <- function(row) {
ifelse (row['a'] == T,'hooray','huh?')
}
wtf$huh <- myfunc(wtf)
a b a
1 TRUE a hooray
2 FALSE b huh?
3 TRUE c hooray
4 FALSE de huh?
5 TRUE f hooray
6 TRUE g hooray
One advantage of a data.frame is that they can contain variables of different types of variables.
lapply(wtf, typeof)
$a
[1] "logical"
$b
[1] "factor"
$huh
[1] "character"
As noted by Gregor, apply requires a matrix and will convert the object you give it to one if possible. But matrices cannot contain multiple variable types and so as.matrix will look for a lowest common denominator that can represent the data, in this case, character.
typeof(as.matrix(wtf))
[1] "character"
class(as.matrix(wtf))
[1] "matrix"

which rows match a given vector in R

I have a matrix A,
A = as.matrix(data.frame(col1 = c(1,1,2,3,1,2), col2 = c(-1,-1,-2,-3,-1,-2), col3 = c(2,6,1,3,2,4)))
And I have a vector v,
v = c(-1, 2)
How can I get a vector of TRUE/FALSE that compares the last two columns of the matrix and returns TRUE if the last two columns match the vector, or false if they don't?
I.e., If I try,
A[,c(2:3)] == v
I obtain,
col2 col3
[1,] TRUE FALSE
[2,] FALSE FALSE
[3,] FALSE FALSE
[4,] FALSE FALSE
[5,] TRUE FALSE
[6,] FALSE FALSE
Which is not what I want, I want both columns to be the same as vector v, more like,
result = c(TRUE, FALSE, FALSE, FALSE, TRUE, FALSE)
Since the first, and 5th rows match the vector v entirely.
Here's a simple alternative
> apply(A[, 2:3], 1, function(x) all(x==v))
[1] TRUE FALSE FALSE FALSE TRUE FALSE
Ooops by looking into R mailing list I found an answer: https://stat.ethz.ch/pipermail/r-help/2010-September/254096.html,
check.equal <- function(x, y)
{
isTRUE(all.equal(y, x, check.attributes=FALSE))
}
result = apply(A[,c(2:3)], 1, check.equal, y=v)
Not sure I need to define a function and do all that, maybe there are easier ways to do it.
Here's another straightforward option:
which(duplicated(rbind(A[, 2:3], v), fromLast=TRUE))
# [1] 1 5
results <- rep(FALSE, nrow(A))
results[which(duplicated(rbind(A[, 2:3], v), fromLast=TRUE))] <- TRUE
results
# [1] TRUE FALSE FALSE FALSE TRUE FALSE
Alternatively, as one line:
duplicated(rbind(A[, 2:3], v), fromLast=TRUE)[-(nrow(A)+1)]
# [1] TRUE FALSE FALSE FALSE TRUE FALSE
A dirty one:
result <- c()
for(n in 1:nrow(A)){result[n] <-(sum(A[n,-1]==v)==2)}
> result
[1] TRUE FALSE FALSE FALSE TRUE FALSE

Resources