How do I filter data in data frame and change column's cell values based on it using a loop?

Viewed 120

Currently working with a larger data frame with various participant IDs that looks like this:

#ASC_new Data Frame

           Pcp Choice Target ASC       Product choice_consis
2393 zwyn27soc      B      A   1     USB drive             0
2394 zwyn27soc      B      A   1           job             0
2395 zwyn27soc      B      B   1     USB drive             0
2397 zwyn27soc      B      A   1       printer             0
2399 zwyn27soc      B      B   1 walking shoes             0
2400 zwyn27soc      B      A   1       printer             0

I would like to try to loop through each participant (Pcp), and look at their choices in column "Choice." For example, under both of the products "USB drive," the participant chose "B" (Choice). Therefore, under "choice_consis," I want there to be a 1 to replace the 0 because the choices are consistent or equal. Although, my for loop for going through the participants and product names isn't working:

#Examples/snippets of my values

pcp_list <- list("ybg606k3l", "yk83d2asc", "yl55v0zhm", "zwyn27soc")
product_list <- list("USB drive", "printer", "walking shoes", "job")

#for loop that isn't working
for (i in pcp_list){ #iterating through participant codes
  for (j in product_list){ #iterating through product names
    comparison <- filter(ASC_new, Pcp == i & Product == j) #filtering participant data and products into new dataframe

    choice_1 <- ASC_new$Choice[1] #creating labels for choice 1 and 2
    choice_2 <- ASC_new$Choice[2]

    if (isTRUE(choice_1 == choice_2)){ #comparing choice 1 and choice 2 and adding value of 1 to Choice_consis column if they are equal

      ASC_new$choice_consis[1] <- 1
      ASC_new$choice_consis[2] <- 1

    } 
  }

}

In the end I would like a data frame where each participant's choice_consis is labeled with a 1 or 0 expressing if they chose the same item (A,B,D) both times that each product appeared.

2 Answers

This is something that's pretty natural to do using dplyr, if you don't care about collapsing across different choices. I'll illustrate on a toy data frame:

IDs <- 1:2
choices <- c('A', 'B')
products <- c('USB', 'Printer')
df <- data.frame(Pcp = rep(IDs, each = 4), 
                 Choice = c(rep(choices, each = 2), 
                            rep(choices, each = 2)),
                 Product = c(rep(products, times = 2), 
                             rep(products, each = 2)))

df %>%
  dplyr::group_by(Pcp, Product) %>%
  dplyr::summarize(choice_consis = as.numeric(length(unique(Choice)) == 1))

This does (in essence) the same thing you're trying to do with your for loop: look at each combination of participants and products (that's what the group_by does) and then analyze that combination (that's what the summarize does). It's a little more succinct and readable than a double for loop. I'd check out Chapter 5 of Hadley's book on R for Data Science to learn more about these sorts of things.

As far as what's wrong with your for loop, the issue is that even though you create your comparison data frame, all the subsequent operations are on ASC_new. So if you wanted to use a for loop and maintain the structure of your original data, you could do something like:

for (i in pcp_list) { 
  for (j in product_list) { 

    compare <- (ASC_new$Pcp == i) & (ASC_new$Product == j)
    choices <- ASC_new$Choice[compare]

    if (length(unique(choices)) == 1) {
      ASC_new$choice_consis[compare] <- 1
    } 
  }
}

Creating a new data frame as you did makes it a little harder to substitute values in the original (because we don't know "where" the filtered data frame came from), so I just get the indices of the original data frame corresponding to the participant-product combination. Note also that I eliminated the hard-coding of the fact that there are only two choices, as well as the isTRUE within the if statement (== will evaluate to TRUE or FALSE, as desired).

Hope this helps!

You can count the unique value of Choice for each Pcp and Product and assign 1 if it is 1 or 0 otherwise.

This can be done in base R :

df$choice_consis <- +(with(df, ave(Choice, Pcp, Product, FUN = function(x)
                           length(unique(x)))) == 1)

dplyr :

library(dplyr)
df %>%
  group_by(Pcp, Product) %>%
  mutate(choice_consis = +(n_distinct(Choice) == 1))

and data.table

library(data.table)
setDT(df)[, choice_consis := as.integer(uniqueN(Choice) == 1), .(Pcp, Product)]
Related