Recoding values by row based on values of other columns

Viewed 24

I have a data set containing the genetic information of two parents and 300+ offspring. I'm trying to change the row values of the offspring based on the values of the parents in that row such that:

   P1 P2 o1 o2 o3
1  A  T  A  T  AT
2  C  A  CA A  C
3  G  C  G  G  C
4  T  C  C  TC CT

becomes:

   P1 P2 o1 o2 o3
1  A  T  a  b  h
2  C  A  b  b  a
3  G  C  a  a  b
4  T  C  b  b  h

where 'a' in the offspring indicates that it's like P1, 'b' for P2, and 'h' for having both. I've split the parent columns from the offspring for ease (Parents and Test, respectively), but my loop doesn't work or changes the entire row to NA. I've just been trying to tackle recoding to 'a' and 'b' for now with the following code:

for (i in 1:nrow(Test)) {
  if (Parents[i, 1] == "A") {
    Test[Test[i, ] == "A"] <- "a"
    } else
      if (Parents[i, 2] =="A") {
        Test[Test[i, ] == "A"] <-"b"
      }
}

I'd appreciate any help, I'm desperately trying to avoid doing this by hand.

1 Answers

I wonder if your expected output is inconsistent with your rules; assuming that it is, try this:

dat
#   P1 P2 o1 o2 o3
# 1  A  T  A  T AT
# 2  C  A CA  A  C
# 3  G  C  G  G  C
# 4  T  C  C TC CT

vecgrepl <- Vectorize(grepl)
dat[,3:5] <- lapply(dat[,3:5], function(Z) 
    sapply(paste0(+(vecgrepl(dat$P1, Z)), +(vecgrepl(dat$P2, Z))),
           switch, "01"="b", "10"="a", "11"="h", "-")
)
dat
#   P1 P2 o1 o2 o3
# 1  A  T  a  b  h
# 2  C  A  h  b  a
# 3  G  C  a  a  b
# 4  T  C  b  h  h

Breakdown:

  • grepl accepts a pattern of length 1 only, so we need to Vectorize it. There are other ways to do this with equivalent results.
  • vecgrepl(dat$P1, Z) should return (for each column Z) whether P1's letter is found in its value.
  • +(.) is a shortcut for converting FALSE/TRUE to 0/1, used for switch below; admittedly we could use FALSETRUE, TRUEFALSE,TRUETRUE and such, I thought this might appear cleaner.
  • switch is an easy way to emulate multiple if/then conditionals, looking at the three combinations. The trailing "-" is a default value, if none of "01", "10", or "11" is seen (effectively "00" here). (This could also be emulated with dplyr::case_when or data.table::fcase with little adjustment.)
  • Because switch is also length-1 only, I vectorize it using sapply(..., switch, ...) where the second ... are arguments sent to switch. Equivalent to sapply(paste0(...), function(x) switch(z, "01"="b", ..)).
  • dat[,3:5] <- lapply(dat[,3:5], ..), only do this for three of the volumns; could also have done dat[,-(1:2)] to do all columns except the first two.

Data

dat <- structure(list(P1 = c("A", "C", "G", "T"), P2 = c("T", "A", "C", "C"), o1 = c("A", "CA", "G", "C"), o2 = c("T", "A", "G", "TC"), o3 = c("AT", "C", "C", "CT")), class = "data.frame", row.names = c("1", "2", "3", "4"))
Related