Help with function using mutate and ifelse

I need some help converting a simple script to a function.
Here's my dataframe:

dat <- data.frame(
        cat1 = c(4, 0, 1, 0, 4, 4),
        cat2 = c(0, 4, 3, 2, 0, 0),
        cat3 = c(0, 0, 0, 2, 0, 0)
)

I want to create new columns that tell me how many coders agreed across all of the the categories (cat1, cat2, cat3).
I use mutate to create the column "agree4" to indicate if all 4 coders agreed on the content that corresponds to the "id".
This script works to create the result I want:


dat1 <- dat %>% 
    mutate(agree4 = ifelse(cat1==4, "1",
                            ifelse(cat2==4, "1",
                                   ifelse(cat3==4,"1","0")))) %>% 
    
        mutate(agree3 = ifelse(cat1==3, "1",
                            ifelse(cat2==3, "1",
                                   ifelse(cat3==3,"1","0")))) %>% 

        mutate(agree2 = ifelse(cat1==2, "1",
                            ifelse(cat2==2, "1",
                                   ifelse(cat3==2,"1","0")))) %>% 
        
                mutate(agree1 = ifelse(cat1==1, "1",
                            ifelse(cat2==1, "1",
                                   ifelse(cat3==1,"1","0")))) %>% 
                
                                mutate (agree0 = ifelse(cat1==0, "1",
                            ifelse(cat2==0, "1",
                                   ifelse(cat3==0,"1","0"))))

Here's the function I tried, (as well as numerous other versions trying to create the function for just 1 or 2 additional arguments, and trying different ways to write the function, none of which worked)

myFun <- function (data, agreeNumCol, myCol1, myCol2, myCol3, agreeNum)
    data %>% 
    mutate (agreeNumCol) = ifelse(myCol1 == agreeNum, "1",
                            ifelse (myCol2 == agreeNum, "1",
                                   ifelse(myCol3 ==agreeNum,"1","0")))

dat2 <- dat %>% 
    myFun(agree4, cat1, cat2, cat3, 4)

When the function above didn't work, I tried entering inputs using quotes, and various different versions

dat2 <- dat %>% 
    myFun(agree4, 'cat1', 'cat2', 'cat3', 4)

Which generated the following:
Error in data %>% mutate(agreeNumCol) <- ifelse(myCol1 == agreeNum, "1", :
could not find function "%>%<-"
I would appreciate any guidance here, thank you.

Edited to add what I expect, and now I realize "agree0" is not useful, but this is what I would expect!

expect <- data.frame(
          id = c(1, 2, 3, 4, 5, 6),
        cat1 = c(4, 0, 1, 0, 4, 4),
        cat2 = c(0, 4, 3, 2, 0, 0),
        cat3 = c(0, 0, 0, 2, 0, 0),
      agree4 = c(1, 1, 0, 0, 1, 1),
      agree3 = c(0, 0, 1, 0, 0, 0),
      agree2 = c(0, 0, 0, 1, 0, 0),
      agree1 = c(0, 0, 1, 0, 0, 0),
      agree0 = c(1, 1, 1, 1, 1, 1)
)

Can you give an example of the data output you expect?

Sorry, I forgot to include the outcome I expected. I edited my post to include that. Thank you.

Hi, here's a solution:

I started just to see if you can extract the unique values in dat like this:

test_cat <- sort(unique(unlist(dat)), decreasing = TRUE)

Then looking across row-wise, sounds like a good job for rowSums, which would sum across rows to the count of index entries, but we want just a binary 0/1, so I set it ... > 0 to force a logical TRUE/FALSE, then wrapped in as.integer for binary 0/1 - others may have easier way here

Also need to be sure to select just the subset of dat you are searching, otherwise every column you add to dat will also be searched & scored... iteratively ... forever ...

Then test the concept for a couple of entries

idx <- startsWith(names(dat), 'cat')

dat$test4 <- as.integer(rowSums(dat[idx] == 4) > 0)
dat$test3 <- as.integer(rowSums(dat[idx] == 3) > 0)
dat$test2 <- as.integer(rowSums(dat[idx] == 2) > 0)
dat$test1 <- as.integer(rowSums(dat[idx] == 1) > 0)
dat$test0 <- as.integer(rowSums(dat[idx] == 0) > 0)

Then look for the repetitive bits and extract a function:

add_test_cat <- function(dat) {
  test_cat <- sort(unique(unlist(dat)), decreasing = TRUE) #Sort in order you want columns
  for (i in test_cat) {
    idx <- startsWith(names(dat), 'cat')
    dat[[paste0('agree', i)]] <- as.integer(rowSums(dat[idx] == i) > 0)
  }
  return(dat)
}

The dat[[paste0('agree', i)]] is a good trick to add named columns within a for loop.

Then see if it works:

> dat
  cat1 cat2 cat3
1    4    0    0
2    0    4    0
3    1    3    0
4    0    2    2
5    4    0    0
6    4    0    0

> add_test_cat(dat)
  cat1 cat2 cat3 agree4 agree3 agree2 agree1 agree0
1    4    0    0      1      0      0      0      1
2    0    4    0      1      0      0      0      1
3    1    3    0      0      1      0      1      1
4    0    2    2      0      0      1      0      1
5    4    0    0      1      0      0      0      1
6    4    0    0      1      0      0      0      1

Would need to assign the result if you wanted to store output, but doesn't overwrite dat input.

Let me know what you think? This should scale in case other entries are possible - more coders or similar

1 Like

Similar dplyr solution, inspired from this StackOverflow post:

Maybe code a bit clearer?

dat %>% 
  mutate(agree4 = as.integer(rowSums(.[1:3] == 4) > 0),
         agree3 = as.integer(rowSums(.[1:3] == 3) > 0),
         agree2 = as.integer(rowSums(.[1:3] == 2) > 0),
         agree1 = as.integer(rowSums(.[1:3] == 1) > 0),
         agree0 = as.integer(rowSums(.[1:3] == 0) > 0))

Maybe someone can work a purrr map to simplify this?

1 Like

added to dat will clarify the required logic.

Either of these options will do it:

library(tidyverse)

# Get initial data frame
dat = expect[,1:4]

0:max(dat[,2:4]) %>% 
  map_dfc(~dat %>% 
        mutate(!!paste0('agree', .x) := as.integer(rowSums(.[2:4] == .x) > 0)) %>% 
        select(matches("agree"))) %>% 
  bind_cols(dat, .)

0:max(dat[,2:4]) %>% 
  map(~dat %>% 
        mutate(!!paste0('agree', .x) := as.integer(rowSums(.[2:4] == .x) > 0))) %>% 
  reduce(full_join)
  id cat1 cat2 cat3 agree0 agree1 agree2 agree3 agree4
1  1    4    0    0      1      0      0      0      1
2  2    0    4    0      1      0      0      0      1
3  3    1    3    0      1      1      0      1      0
4  4    0    2    2      1      0      1      0      0
5  5    4    0    0      1      0      0      0      1
6  6    4    0    0      1      0      0      0      1
2 Likes

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.

If you have a query related to it or one of the replies, start a new topic and refer back with a link.