I have 2 factor columns, I want to create a third column which tells me what the second one has that the first does not.
It's very similar to this post but I'm having trouble going from a df
to using setdiff()
function.
For example:
library(dplyr)
y1 <- c("a.b.","a.","b.c.d.")
y2 <- c("a.b.c.","a.b.","b.c.d.")
df <- data.frame(y1,y2)
Column y1
has a.b.
and column y2
has a.b.c.
. I want a thirds column to return c.
or just c
.
> df
y1 y2 col3
1 a.b. a.b.c. c.
2 a. a.b. b.
3 b.c.d. b.c.d.
I think that is should be a combination of strsplit
and setdiff
, but I can't get it to work.
I've tried to convert the factor
into character
, then I've tried applying strsplit()
to the results, but the output seems a but weird to me. It seems to have created a list within a list, which makes it difficult to pass to setdiff()
#convert factor to character
df <- df %>% mutate_if(is.factor, as.character)
lapply(df$y1,function(x)(strsplit(x,split = "[.]")))
> lapply(df$y1,function(x)(strsplit(x,split = "[.]")))
[[1]]
[[1]][[1]]
[1] "a" "b"
[[2]]
[[2]][[1]]
[1] "a"
[[3]]
[[3]][[1]]
[1] "b" "c" "d"
To do this, we simply need to use minus sign. For example, if we have data-frames df1 and df2 then the subtraction can be found as df1-df2.
cbind() and rbind() both create matrices by combining several vectors of the same length. cbind() combines vectors as columns, while rbind() combines them as rows.
The setdiff() function in R can be used to find differences between two sets. This function uses the following syntax: setdiff(x, y) where: x, y: Vectors or data frames containing a sequence of items.
Update
There was an issue when the difference had more than 1 character, it created an additional row. To overcome that we paste
all the elements together for each difference. This also saves us from the unlist
step.
df$col3 <- mapply(function(x, y) paste0(setdiff(y, x), collapse = ""),
strsplit(as.character(df$y1), "\\."), strsplit(as.character(df$y2), "\\."))
Original Answer
We can use mapply
and split both the columns on "." using strsplit
and then take the difference between them using setdiff
.
df$col3 <- mapply(function(x, y) setdiff(y, x),
strsplit(as.character(df$y1), "\\."), strsplit(as.character(df$y2), "\\."))
df
# y1 y2 col3
#1 a.b. a.b.c. c
#2 a. a.b. b
#3 b.c.d. b.c.d.
If we don't want col3
as list we can unlist
it however, one issue in that is if we unlist
it removes the character(0)
value from it. To retain that value we need to perform an additional check on it. Taken from here.
unlist(lapply(df$col3,function(x) if(identical(x,character(0))) ' ' else x))
#[1] "c" "b" " "
You can also use purrr:map2
:
df %>%
mutate_if(is.factor, as.character) %>%
mutate(col3 = map2(strsplit(y2, "\\."), strsplit(y1, "\\."), setdiff))
# y1 y2 col3
#1 a.b. a.b.c. c
#2 a. a.b. b
#3 b.c.d. b.c.d.
Explanation: Convert factor
s to character
vectors, use setdiff
on the "."
-split columns y2
and y1
. Note that col3
is a list
.
It appears that unnest
drops the zero-length character
entries from the list
. So to convert col3
from a list
to a character
vector you can do:
df %>%
mutate_if(is.factor, as.character) %>%
mutate(col3 = map2(strsplit(y2, "\\."), strsplit(y1, "\\."), setdiff)) %>%
rowwise() %>%
mutate(col3 = paste(col3, collapse = "."))
## A tibble: 3 x 3
# y1 y2 col3
# <chr> <chr> <chr>
#1 a.b. a.b.c. c
#2 a. a.b. b
#3 b.c.d. b.c.d. ""
The idea here is to string-concatenate col3
entries (if there are multiple); using rowwise()
ensures row-wise paste
.
For the updated sample data from your comment:
y1 <- c("a.b.","a.","b.c.d.")
y2 <- c("a.b.c.e.","a.b.","b.c.d.")
df <- data.frame(y1,y2)
df %>%
mutate_if(is.factor, as.character) %>%
mutate(col3 = map2(strsplit(y2, "\\."), strsplit(y1, "\\."), setdiff)) %>%
rowwise() %>%
mutate(col3 = paste(col3, collapse = "."))
## A tibble: 3 x 3
# y1 y2 col3
# <chr> <chr> <chr>
#1 a.b. a.b.c.e. c.e
#2 a. a.b. b
#3 b.c.d. b.c.d. ""
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With