简体   繁体   中英

elegant way to loop through columns in r

Say I have a dataframe:

set.seed(10)

    col_a <- (c("mouse", "dog", "rabbit", "cat", "horse", "monkey", "pig", "shark", "flea","ant"))
    col_b <- (c(round(rnorm(10), digits=0)))
    col_c <- (c(round(rnorm(10), digits=0)))
    col_d <- (c(round(rnorm(10), digits=0)))
    col_e <- (c(round(rnorm(10), digits=0)))
    
df <- data.frame(col_a, col_b, col_c, col_d, col_e)

df
           col_a col_b col_c col_d col_e
    > 1   mouse     0     0     0     0
    > 2     dog     0     0     0     1
    > 3  rabbit    -2     0    -1    -1
    > 4     cat     0     0     1     0
    > 5   horse     1     2     0     0
    > 6  monkey     1     1    -1     1
    > 7     pig     0    -1     0     1
    > 8   shark    -2     0     0    -1
    > 9    flea     0     1     0     0
    > 10    ant     0     2     1     1

> 

and I want to replace the values in “col_b” and “col_d” corresponding to “shark” in with those of “mouse”. I want to index using the column headers and the values in col_a.

I can do this in two steps with:

df[df$col_a == “shark”, “col_b”] <- df[df$col_a == “mouse” , “col_b”]
df[df$col_a == “shark”, “col_d”] <- df[df$col_a == “mouse” , “col_d”]

With many columns the code becomes quite clunky. Is there a way to streamline this with a for loop or the apply function?

You could do:

library(tidyverse)
df %>%
  mutate(across(c(col_b, col_d), ~if_else(col_a == 'shark', .[col_a == 'mouse'], .)))

With across you csn very flexibly define the columns you want to do the replacement on.

Note I get different results with the same random seed as you, so I chose another one, ie set.seed(110)

Input data:

    col_a col_b col_c col_d col_e
1   mouse     0     1     0     0
2     dog     1     1    -2     0
3  rabbit     1     2     1     0
4     cat     1    -1    -1    -1
5   horse     0     2     1    -1
6  monkey     1     0     2     0
7     pig     0    -2    -1     1
8   shark     1     1    -1     0
9    flea    -2     2     0    -1
10    ant     0    -2     0     0

Output data:

    col_a col_b col_c col_d col_e
1   mouse     0     1     0     0
2     dog     1     1    -2     0
3  rabbit     1     2     1     0
4     cat     1    -1    -1    -1
5   horse     0     2     1    -1
6  monkey     1     0     2     0
7     pig     0    -2    -1     1
8   shark     0     1     0     0
9    flea    -2     2     0    -1
10    ant     0    -2     0     0

If you're dealing with many situations you want to specify, it might be easier with long data. Below, I make a long version of the data, a lookup table of names to swap, and a list of columns to do it in.

library(tidyverse)
df_long = pivot_longer(df, -col_a)
chg_tbl = tibble(col_a = c("shark", "mouse"), new   = c("mouse", "shark"))
cols <- c("col_b", "col_d")

Then these can be applied to the long table:

df_long %>%
  left_join(chg_tbl) %>%
  transmute(col_a = if_else(name %in% cols, coalesce(new, col_a), col_a), 
            name, value) %>%
  pivot_wider(names_from = name, values_from = value)

You don't have to do this individually for every column. You can replace multiple columns together.

cols <- c('col_b', 'col_d')
df[df$col_a == "shark", cols] <- df[df$col_a == "mouse" , cols]
df

Here is a simplified example since the same set.seed gives me different values.

df <- data.frame(col1 = letters[1:5], col2 = 1:5, col3 = 5:1, col4 = 1:5)
df

#  col1 col2 col3 col4
#1    a    1    5    1
#2    b    2    4    2
#3    c    3    3    3
#4    d    4    2    4
#5    e    5    1    5

cols <- c('col2', 'col4')
df[df$col1 == "d", cols] <- df[df$col1 == "a" , cols]
df

#  col1 col2 col3 col4
#1    a    1    5    1
#2    b    2    4    2
#3    c    3    3    3
#4    d    1    2    1
#5    e    5    1    5

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM