#r #dplyr #tidyverse #purrr
#r #dplyr #tidyverse #purrr
Вопрос:
УЧИТЫВАЯ, что я хочу преобразовать ряд переменных, используя информацию, содержащуюся в файле yaml
И у меня есть содержимое файла yaml, загруженного в память в виде списка
КОГДА я собираюсь применить повторное сопоставление, используя $responses
элемент каждой записи в файле yaml
ЗАТЕМ я получаю новое data.frame/tibble
поле со столбцами, добавленными с использованием имен и перекодировок в записи карты каждого поля
Ниже приведена единственная запись в файле yaml. Запись health
соответствует имени столбца в оригинале data.frame
. text
Поле не имеет отношения к этому вопросу. responses
Поле содержит рецепт повторного сопоставления.
health:
study_name: global_health
text: Would you say your health in general is excellent, very good, good, fair, or poor
responses:
1: Excellent
2: Very good
3: Good
4: Fair
5: Poor
Вот пример исходных данных — просто сосредоточьтесь на одном поле в качестве примера:
health
1 1
2 3
3 1
4 2
5 2
6 4
В этом случае (хотя и со многими другими переменными) я хотел бы следующее:
health global_health
1 1 Excellent
2 3 Good
3 1 Excellent
4 2 Very good
5 2 Very good
6 4 Fair
Что я получил до сих пор, так это следующее:
data_map <- yaml::read_yaml(map_filepath)
study_cols <- names(data_map)
И я смог использовать эту структуру для игнорирования записей, у которых нет заполненного responses
поля в файле yaml, используя это как средство отслеживания того, какие столбцы нужно перекодировать, а какие нет.
library(tidyverse)
# create simple local function to identify which have "responses" that need recoding
pluck_func <- function(x) !is.null(x$responses)
# create mask using the function
recode_cols_mask <- data_map %>%
map(~pluck(., pluck_func)) %>%
unlist()
# Identify variables that need recoding
recode_names <- study_cols[recode_cols_mask]
Теперь, что я хотел бы сделать, это взять переменные, которые существуют, recode_names
и применить карту переименования только для этих переменных. Я предполагаю, что есть какое-то умное решение с purrr
map
семейством функций. Я просто не смог найти правильную комбинацию map()
и mutate()
. Для полноты картины ниже приведена запись, которая не будет перекодирована и возвращена. Опять же, способ, которым это определяется, эффективно is.null(data_map[['ANALWT_C']][['responses']])
будет оцениваться TRUE
.
ANALWT_C:
study_name: weights
text: Case-level study weight based on Census estimates
Solutions do not need to be in tidyverse
for what it is worth. Happy to use base R
. I just tend to find tidyverse
processing a little more readable.
UPDATE
Here is my current solution which I would love to simplify if I can. But if not I can live with this for the moment.
recode_func <- function(df, data_map, recode_names, study_names) {
for(v in recode_names) {
df[data_map[[v]][['study_name']]] <- factor(
df[[v]],
levels = seq_along(data_map[[v]][['responses']]),
labels = data_map[[v]][['responses']] %>% unlist()
)
}
for(v in setdiff(names(data_map), recode_names)) {
df[data_map[[v]][['study_name']]] <- df[[v]]
}
study_df <- df[study_names]
return(list(base_df = df,
study_df = study_df))
}
с study_names
определением как:
get_study_names <- function(x) x[['study_name']]
study_names <- data_map %>%
map(~pluck(., get_study_names)) %>%
unlist()
РЕШАЕМАЯ
Моя текущая реализация основана на принятом ответе ниже:
#' Recodes dataframe using info pulled from the yaml file in the main function code{create_study_datasets}
#'
#' @param df target data.frame
#'
#' @param data_map list read in from yaml file containing data renames and recoding recipes
#'
#' @param recode_names a character vector of names derived from the data_map in pre-processing steps that take place in
#' the parent function. These are the fields that need to be recoded in some fashion.
#'
#' @param study_names a character vectory of names derived from the data_map in pre-processing steps that take place in
#' the parent function. These are all of the fields that should be returned - whether they are recoded and renamed or
#' just renamed according to the data_map.
#'
#' @seealso create_study_datasets
helper_recode_func <- function(df, data_map, recode_names, study_names) {
# Apply the recodes as appropriate from the data map
df <- df %>%
imap_dfc(~ if (hasName(data_map, .y) amp;amp; hasName(data_map[[.y]], "responses"))
recode(.x, !!! data_map[[.y]][["responses"]])) %>%
setNames(map_chr(data_map, "study_name")[names(.)]) %>%
bind_cols(df, .)
# Apply passthrough/name-change for variables that do not need recoding
df <- df %>%
select_if((names(.) %in% setdiff(names(data_map), recode_names))) %>%
setNames(map_chr(data_map, "study_name")[names(.)]) %>%
bind_cols(df, .)
study_df <- df[study_names]
return(list(base_df = df,
study_df = study_df))
}
Который при выполнении возвращает ожидаемые выходные данные как для переменных, требующих перекодирования, так и для тех, которые этого не делают:
ANALWT_C weights health global_health
1 1274.9937 1274.9937 1 Excellent
2 550.3692 550.3692 3 Good
3 6043.4082 6043.4082 1 Excellent
4 262.2439 262.2439 2 Very good
5 8354.2522 8354.2522 2 Very good
Ответ №1:
f <- function(x) recode(x, !!! data_map[[cur_column()]][["responses"]])
df %>%
mutate(across(any_of(names(keep(data_map, hasName, "responses"))),
f,
.names = "{data_map[[.col]][["study_name"]]}"))
#> health global_health
#> 1 1 Excellent
#> 2 3 Good
#> 3 1 Excellent
#> 4 2 Very good
#> 5 2 Very good
#> 6 4 Fair
Вариант:
df %>%
imap_dfc(~ if (hasName(data_map[[.y]], "responses"))
recode(.x, !!! data_map[[.y]][["responses"]])) %>%
setNames(map_chr(data_map, "study_name")[names(.)]) %>%
bind_cols(df, .)
Комментарии:
1. Спасибо за это. Обновил мой исходный пост примером того, как я интегрировал ваше предложение в свое окончательное решение.