Объединение цикла for и mutate внутри функции для нескольких кадров данных в R

Я надеюсь, что смогу получить некоторое представление о моей проблеме. По сути, я хочу создать один и тот же набор переменных в нескольких разных наборах данных (они являются подмножествами исходного набора данных на основе выбросов, ответов протеста и т. д.). Я пробовал много вещей, но я совершенно в тупике.

Во-первых, я понимаю, что использование eval(parse() против предложений, однако я не самый эффективный кодер, и это работало для моих целей. В любом случае, моя цель в основном состоит в том, чтобы создать набор переменных - X_3, X_4, X_5 и т. д. который представляет собой годовую сумму (x12) набора других переменных - z1_1, z2_1, z3_1 и т. д. для этого у меня есть приведенный ниже код, который работает, когда я индивидуально ввожу имя каждого набора данных, где «данные».

Изменить: ожидания данных

#what it looks like now
  responseid   z3_1 z4_1 z5_1
1          1  4.720 7.08   NA
2          2  1.180   NA 1.18
3          3  1.180   NA 1.18
4          4  2.596 3.54   NA
5          5 15.340   NA   NA
6          6  2.360   NA 2.36

#what i'd like it look like:
  responseid   z3_1 z4_1 z5_1    X_3  X_4  X_5
1          1  4.720 7.08   NA  56.640 84.96    NA
2          2  1.180   NA 1.18  14.160    NA 14.16
3          3  1.180   NA 1.18  14.160    NA 14.16
4          4  2.596 3.54   NA  31.152 42.48    NA
5          5 15.340   NA   NA 184.080    NA    NA
6          6  2.360   NA 2.36  28.320    NA 28.32

#dput
#original
structure(list(responseid = c(1L, 2L, 3L, 4L, 5L, 
6L), z3_1 = c(4.72, 1.18, 1.18, 2.596, 15.34, 2.36), z4_1 = c(7.08, 
NA, NA, 3.54, NA, NA), z5_1 = c(NA, 1.18, 1.18, NA, NA, 2.36),  class = "data.frame", row.names = c(NA, 6L))

#expected
structure(list(responseid = c(1L, 2L, 3L, 4L, 5L, 
6L), z3_1 = c(4.72, 1.18, 1.18, 2.596, 15.34, 2.36), z4_1 = c(7.08, 
NA, NA, 3.54, NA, NA), z5_1 = c(NA, 1.18, 1.18, NA, NA, 2.36), 
    X_3 = c(56.64, 14.16, 14.16, 31.152, 184.08, 28.32), X_4 = c(84.96, 
    NA, NA, 42.48, NA, NA), X_5 = c(NA, 14.16, 14.16, NA, NA, 
    28.32)), class = "data.frame", row.names = c(NA, 6L))

Но я хотел бы сделать это для нескольких наборов данных, поэтому я хочу использовать функцию для запуска каждого df.

for (i in 3:9){
    X.varname <- paste0("X_",i)
    data <- data %>%
      mutate(
        !!X.varname := eval(parse(text=paste0("z", i, "_1")))*12
      )
  }

Однако, когда я пытаюсь поместить это в функцию (чтобы я мог запустить ее по списку фреймов данных), ничего не происходит:

f.test <- function(data){
for (i in 3:9){
    X.varname <- paste0("X_",i)
    data <- data %>%
      mutate(
        !!X.varname := eval(parse(text=paste0("z", i, "_1")))*12
      )
  }
}

Кто-нибудь знает, почему это может быть? Это мой первый вопрос в StackOverflow, поэтому я прошу прощения, если какое-либо форматирование неверно.


person MegPD    schedule 02.04.2020    source источник
comment
Привет @Meg, добро пожаловать в StackOverflow, не могли бы вы привести более четкий пример того, что вы пытаетесь сделать? Упрощенная версия вашего фрейма данных (используйте dput(data)) и ожидаемый результат.   -  person GGamba    schedule 02.04.2020
comment
Спасибо, я сделал это сейчас!   -  person MegPD    schedule 02.04.2020


Ответы (2)


Ничего не происходит, потому что функция ничего не возвращает. Обычно вам не нужно явно return что-то в R, потому что R автоматически возвращает последний объект. Но это цикл for означает, что он ничего не возвращает.

Чтобы избежать этой проблемы, просто укажите, что должна возвращать ваша функция, т.е. data:

f.test <- function(data){
  for (i in 3:5){
    X.varname <- paste0("X_",i)
    data <- data %>%
      mutate(
        !!X.varname := eval(parse(text=paste0("z", i, "_1")))*12
      )
  }
  return(data)
}

Кстати, вы правы, что eval(parse(x)) не предлагается, потому что это опасно и медленно.

Не уверен, что то, что вы нам показали, действительно то, что вы пытаетесь сделать, но лучший способ сделать это:

library(dplyr)
library(stringr)


my_fun <- function(data) {
  data %>% 
    mutate_at(vars(starts_with("z")), .funs = list(TBC=~.*12)) %>% 
    rename_at(vars(ends_with('TBC')), .funs = ~{.x %>% str_extract('\\d') %>% str_c('X_', .)})
}

Более того, поскольку вам нужно сделать это для нескольких фреймов данных, вы можете использовать предложение @koenniem для использования purrr::map(). Поместите каждый кадр данных в список и примените функцию к каждому элементу списка:

library(purrr)
df2 <- df %>% mutate_at(vars(-responseid), ~.+4)

df_t <- list(df, df2)

df_t %>% 
  map_df(my_fun)
#>    responseid   z3_1  z4_1 z5_1     X_3    X_4   X_5
#> 1           1  4.720  7.08   NA  56.640  84.96    NA
#> 2           2  1.180    NA 1.18  14.160     NA 14.16
#> 3           3  1.180    NA 1.18  14.160     NA 14.16
#> 4           4  2.596  3.54   NA  31.152  42.48    NA
#> 5           5 15.340    NA   NA 184.080     NA    NA
#> 6           6  2.360    NA 2.36  28.320     NA 28.32
#> 7           1  8.720 11.08   NA 104.640 132.96    NA
#> 8           2  5.180    NA 5.18  62.160     NA 62.16
#> 9           3  5.180    NA 5.18  62.160     NA 62.16
#> 10          4  6.596  7.54   NA  79.152  90.48    NA
#> 11          5 19.340    NA   NA 232.080     NA    NA
#> 12          6  6.360    NA 6.36  76.320     NA 76.32
person GGamba    schedule 02.04.2020
comment
Я бы попробовал data[[X.varname]] <- data %>% transmute( !!X.varname := eval(parse(text=paste0("z", i, "_1")))*12) в f.test. Хорошая работа с str_* - person A. Suliman; 02.04.2020

Ничего не происходит, потому что вы присваиваете data аргументу data функции, которая data отличается от той, что используется в вашей глобальной среде. Вы можете использовать assign(data, YourAssignedValue, envir = .GlobalEnv) или использовать операцию глубокого назначения <<-. В качестве альтернативы, поскольку вы хотите заменить исходные данные, просто верните data следующим образом:

f.test <- function(data){
    for (i in 3:5){
        X.varname <- paste0("X_",i)
        data <- data %>%
            mutate(!!X.varname := eval(parse(text=paste0("z", i, "_1")))*12)
    }
    data
}
data <- f.test(data)

Если я могу предложить другой подход; Вы можете создавать вложенные наборы данных tibble, используя, например, tibble::tribble, а затем выполнять мутации и т. д., используя purrr::map.

person koenniem    schedule 02.04.2020
comment
поэтому я попробовал это, но использование GlobalEnvir и назначение просто приводит к переменным за пределами фрейма данных, что бесполезно, когда мне нужно создать одни и те же переменные, например, в 5 фреймах данных... - person MegPD; 02.04.2020
comment
Я вижу, что ты хочешь сделать сейчас. Я обновил свой ответ. - person koenniem; 02.04.2020