Ali
Ali

Reputation: 1080

How to access an element of a vector while using lapply over a list

I want to add rows to a data frame such that the first column contains all numbers between 1 to 10 and depending on the number, the second column should have a certain output.

I can do this using a for-loop as below:

Dt <- data.frame(ID = c(1,2,5,7,8), Value = "x", stringsAsFactors = FALSE)
Special_cases <- c(3,4)

for (i in 1:10){

  if( i %in% Dt$ID){

    Dt <- Dt

  } else if (i %in% Special_cases){

    Dt <- rbind(Dt, c(i,"y"))

  } else {

    Dt <- rbind(Dt, c(i,"z"))

  }

}

 ID Value
1   1     x
2   2     x
3   5     x
4   7     x
5   8     x
6   3     y
7   4     y
8   6     z
9   9     z
10 10     z

Although this works, I want to get out of this bad habit of using for-loops, but i've been struggling to re-write this using lapply. I'm not sure how to deal with the first if statement where I call on i.

So how can I transform this for-loop to lapply? I'm also not sure what the first argument of lapply would be.

Upvotes: 1

Views: 396

Answers (2)

MrFlick
MrFlick

Reputation: 206446

Here would be another way using merges from dplyr

library(dplyr)
data.frame(ID=1:10) %>% left_join(Dt) %>% 
  left_join(tibble(ID=Special_cases, Value2="y")) %>% 
  mutate(Value=coalesce(Value, Value2, "z"), Value2=NULL)

Or here's another way that doesn't involved loops or lapply. Just see what's missing and add it all in one go.

if (any(!Special_cases %in% Dt$ID)) {
  Dt <- rbind(Dt, data.frame(ID = setdiff(Special_cases, Dt$ID), Value = "y", stringsAsFactors = FALSE))
}
if (any(!1:10 %in% Dt$ID)) {
  Dt <- rbind(Dt, data.frame(ID = setdiff(1:10, Dt$ID), Value = "z", stringsAsFactors = FALSE))
}

Upvotes: 1

ulfelder
ulfelder

Reputation: 5335

I'm sure there is a more elegant solution, but this one works. First, run lapply over your vector of desired IDs, creating a list of one-row data frames per your specifications.

results <- lapply(seq(10), function(i) {

    if (i %in% Dt$ID) {

        Dt[which(Dt$ID==i),]

    } else if (i %in% c(3,4)) {

        data.frame(ID = i, Value = "y")        

    } else {

        data.frame(ID = i, Value = "z") 

    }

})

Then, collapse that list into a data frame. You could also roll this into the previous step by nesting the call to lapply where results appears here.

Dt2 <- do.call(rbind.data.frame, results)

If you don't mind introducing a dependency on tidyverse' or purrr in particular, you can also substitute map_dfr for lapply in the code block above, and it will go ahead and collapse the resulting list into one data frame in that same step. Note, though, that it will also throw warnings about converting a factor to a character to do that, even though all those IDs were characters in the first place.

Here's the result. Note that you would still need to sort according to Value if you cared about having things grouped by that feature.

   ID Value
1   1     x
2   2     x
11  3     y
12  4     y
3   5     x
13  6     z
4   7     x
5   8     x
14  9     z
15 10     z

Upvotes: 1

Related Questions