Writing an R function that creates columns

Viewed 63

I want to add columns to my data frame df depending on the contents of the Column CountryID of the same dataframe. I got it to work but I have 60 of those copy-paste-lines.

df$Country1 <- ifelse(as.integer(df$CountryID == 1), 1, 0)
df$Country2 <- ifelse(as.integer(df$CountryID == 2), 1, 0)
df$Country3 <- ifelse(as.integer(df$CountryID == 3), 1, 0)
... 60 more lines

To make the code a bit more organized I want to write a function createColumns that I just have to call and the entire code within the brackets will be executed. My latest attempt did not create any columns. Does anyone know how to solve this?

createColumns <- function() {
df$Country1 <- ifelse(as.integer(df$CountryID == 1), 1, 0)
df$Country2 <- ifelse(as.integer(df$CountryID == 2), 1, 0)
df$Country3 <- ifelse(as.integer(df$CountryID == 3), 1, 0)
... 60 more lines
return(invisible())
}
2 Answers

You need to return the new object:

createColumns <- function(df) {
    df$Country1 <- ifelse(as.integer(df$CountryID == 1), 1, 0)
    df$Country2 <- ifelse(as.integer(df$CountryID == 2), 1, 0)
    df$Country3 <- ifelse(as.integer(df$CountryID == 3), 1, 0)
    ... 60 more lines
    df
}

And use it thus:

df <- createColumns(df)

You can’t (easily) modify non-local objects in R, and this is on purpose: you shouldn’t do that. Instead, make assignment explicit, as done above.


There are some other things to note; for example, repeating what you wrote 60 times should be a huge red flag. Rethink the problem: you probably don’t want 60 columns for this in the first place (instead, research and use the concept of tidy data) but if you really do, you can probably use pivot functions to replace those 60 lines of code with a single line of code.

Furthermore, R has a logical data type. This means that, in virtually all use-cases, you’d assign TRUE and FALSE instead of 1 and 0. So, instead of writing

ifelse(as.integer(df$CountryID == 1), 1, 0)

You’d simplify this to

df$CountryID == 1

In addition, the as.integer call is entirely redundant: ifelse requires a logical argument, so your current code takes a logical vector, explicitly converts it to integer, and the result is converted back to a logical vector by ifelse.

If you want the desired output in a simpler manner, you can try table + cbind like below

newCols <- as.data.frame.matrix(table(cbind(q = 1:nrow(df),df)))
dfout <- cbind(df,`colnames<-`(newCols,paste0("Country",colnames(newCols))))

such that

> dfout
  countryID Country1 Country2 Country3
1         1        1        0        0
2         3        0        0        1
3         1        1        0        0
4         2        0        1        0
5         1        1        0        0

Data

> dput(df)
structure(list(countryID = c(1L, 3L, 1L, 2L, 1L)), class = "data.frame", row.names = c(NA, 
-5L))
Related