Переименовывать столбец, только если все его значения удовлетворяют условию

Я хочу переименовать столбцы в своих данных в зависимости от того, удовлетворяют ли все значения в этих столбцах условию. Например, в числовом столбце, если все значения больше 5, переименуйте столбец в large_values, в противном случае оставьте имя столбца как есть. Другой пример: если все значения в столбце символов свободны от символа &, переименуйте столбец в no_ampersand.

Данные

library(tibble)

df <- 
  tribble(~age_1, ~age_2, ~string_1,
        2, 7, "abc",
        3, 8, "efg",
        1, 11, "hi&",
        10, 6, "klmn",
        50, 100, "opq")

Определение двух функций для демонстрации

  1. Элементы вектора больше значения
is_larger_than_val <- function(x, y) {
  all(x > y)
}
  1. Содержат ли векторные элементы &?
does_contain_ampersand <- function(x) {
  all(grepl("&", x))
}

Вопрос

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

is_larger_than_val(df$age_2, 5)

[1] TRUE

Переименуем age_2 в large_values, но иначе в случае, если

is_larger_than_val(df$age_1, 5)

[1] FALSE

останется age_1 как есть?

И аналогично, потому что

does_contain_ampersand(df$string_1)

[1] FALSE

сохранит string_1 как есть (но если бы это было TRUE, то string_1 было бы переименовано в no_ampersand)?

Желаемый результат

Учитывая текущие данные, переименование на основе условий, которые я указал в is_larger_than_val() и does_contain_ampersand(), должно вернуть:

# A tibble: 5 x 3
  age_1 large_values string_1
  <dbl>        <dbl> <chr>   
1     2            7 abc     
2     3            8 efg     
3     1           11 hi&     
4    10            6 klmn    
5    50          100 opq  

Я уверен, что этого можно добиться, вложив некоторые операторы if else, но мне интересно, есть ли более простой способ (возможно, с использованием трюков tidyverse?).

Спасибо!


РЕДАКТИРОВАТЬ


Ставим вопрос в контекст

Following @Ian's comment below, here's the context for my problem.
  1. У меня есть объект данных R, который происходит из файла JSON. Когда я читаю файл JSON в R, он оказывается в следующем формате:
vec <- c(1, 2, 3)
names(vec) <- c("A", "B", "C")
my_data_object_as_list <- as.list(vec)

my_data_object_as_list
## $A
## [1] 1

## $B
## [1] 2

## $C
## [1] 3
  1. Я хочу создать функцию, которая берет my_data_object_as_list и преобразует ее в таблицу.
require(tidyr)
require(dplyr)
require(tidyselect)

organize_in_table <- function(as_list_object) {
    as_list_object %>%
    bind_rows() %>%
    pivot_longer(cols = tidyselect::everything())
}
organize_in_table(my_data_object_as_list)

## # A tibble: 3 x 2
##   name  value
##   <chr> <dbl>
## 1 A         1
## 2 B         2
## 3 C         3
  1. Поскольку organize_in_table() довольно общий, он возвращает таблицу с именами столбцов (name и value), которые не указывают на то, о чем каждый столбец. Чтобы решить эту проблему, я хочу добавить аргумент purpose к organize_in_table(), и вот один из примеров:
organize_in_table <-
  function(as_list_object,
           purpose = NULL) {
    table <- as_list_object %>%
      bind_rows() %>%
      pivot_longer(cols = tidyselect::everything())
    
    if (is.null(purpose)) {
      return(table)
    } else if (purpose == "match_letters_and_numbers") {
      table <- rename(table, letters = name, numbers = value)
    }
    return(table)
  }

Теперь organize_in_table() может возвращать объект со значимыми именами:

df_letters_and_numbers <- 
  organize_in_table(my_data_object_as_list, "match_letters_and_numbers")

> df_letters_and_numbers
## # A tibble: 3 x 2
##   letters numbers
##   <chr>     <dbl>
## 1 A             1
## 2 B             2
## 3 C             3
  1. Здесь я столкнулся с проблемой: как я узнаю, что df_letters_and_numbers[1] было правильно названо как letters, тогда как df_letters_and_numbers[2] это numbers? На каком основании я проверил — когда переименовывал table в organize_in_table() — что первый столбец (name) состоит только из букв, а второй столбец (value) — из цифр? Я не проверял.

Что, если бы my_data_object_as_list выглядел так:

vec_2 <- c("A", "B", "C")
names(vec_2) <- c(1, 2, 3)
my_data_object_as_list_2 <- as.list(vec_2)

> my_data_object_as_list_2 
## $`1`
## [1] "A"

## $`2`
## [1] "B"

## $`3`
## [1] "C"

Затем, если я запущу organize_in_table(my_data_object_as_list_2, "match_letters_and_numbers"), я получу фрейм данных с несовпадающими именами столбцов:

## # A tibble: 3 x 2
##   letters numbers
##   <chr>   <chr>  
## 1 1       A      
## 2 2       B      
## 3 3       C     

Нижняя граница

Итак, мой вывод состоит в том, что я должен обусловить шаг переименования в organize_in_table(), основываясь на содержимом каждого переименовываемого столбца. С этой целью я подумал, что первым шагом должно быть определение отдельной функции для каждого теста, который я хочу выполнить (например, все ли значения в столбцах имеют номера? все ли значения буквенные?). И поскольку я хочу сделать organize_in_table() максимально масштабируемым, я хотел бы, чтобы он принимал любой тест, для которого я могу создать функцию тестирования.

Можете ли вы объяснить, какова ваша настоящая конечная цель? Трудно дать ответ, который будет вам полезен, если мы не можем понять, какова конечная цель. См. Задача XY.

Ian Campbell 23.12.2020 17:36

Спасибо. Я отредактировал вопрос, чтобы ответить на ваш комментарий.

Emman 24.12.2020 12:59

Уверен, вы могли бы сократить этот вопрос до одного абзаца!

geotheory 24.12.2020 20:29

@geotheory, возможно, я мог бы, но моя первоначальная версия была краткой и привела к комментарию Яна. Поэтому я предоставил больше информации для полноты. Не стесняйтесь редактировать сообщение, если хотите. Моя единственная цель — внести ясность и позволить людям помочь мне решить проблему.

Emman 24.12.2020 20:48

@Emman Достаточно честно, я согласен, что большая часть длины - это дополнение

geotheory 24.12.2020 21:43
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
5
801
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Используйте sapply для данных, чтобы создать логический индекс для столбцов, но будьте осторожны с символьными столбцами в случае амперсанда:

i <- sapply(df, is_larger_than_val, y = 5)
names(df)[i] <- "large_values"

i <- sapply(df, does_contain_ampersand)
i <- i | !sapply(df, is.character)
names(df)[!i] <- "no_ampersand"

names(df)
#[1] "age_1"        "large_values" "no_ampersand"

Не могли бы вы объяснить роль | в i <- i | !sapply(df, is.character)?

Emman 24.12.2020 14:41

Это векторизованное логическое ИЛИ, см. help('|').

Rui Barradas 24.12.2020 16:21

Решение sapply великолепно своей простотой. Вот альтернативное решение с data.table, которое требует немного больше усилий. Я думаю, что это тот же принцип, но я добавил шаг для идентификации числовых и строковых столбцов, чтобы убедиться, что проверяются только определенные столбцы.

# data.table solution
library(data.table)
dt <- as.data.table(df)

# I think you need !(any()) rather than all
# This will identify if there is no_ampersand.
# Design tests so that if they are TRUE then change the column name for consistency
# Also & needs to be escaped with \\ to find it
no_ampersand <- function(x) {
  !any(grepl("\\&", x))
}

# function for taking data.table (dt), the test result and the new column names
# and updating the column name
alterColumnIfMatch <- function(dt, test_data, new_column = "large_values"){
  # find ones to change
  alter_data <- names(test_data)[which(test_data == TRUE)]
  # if there are any, then use setnames to update to the new column value
  if (length(alter_data) > 0) setnames(dt, alter_data, new_column)
  return(dt)
}

# identify which columns to run through the tests
col_class <- sapply(dt, class)
numeric_cols <- names(col_class)[col_class == "numeric" | col_class == "integer"]
character_cols <- names(col_class)[col_class == "character" ]

# test for larger than 5 and update
test_larger <- dt[, lapply(.SD, function(col) is_larger_than_val(col, 5)),
                  .SDcols = numeric_cols]
dt <- alterColumnIfMatch(dt, test_larger, "large_values")

# test for no ampersand and update
test_ampersand <- dt[, lapply(.SD, function(col) does_contain_ampersand(col)),
                     .SDcols = character_cols]
dt <- alterColumnIfMatch(dt, test_ampersand, "no_ampersand")

# convert back to tibble for you
out <- as_tibble(dt)
out

Большое спасибо! Это делает в значительной степени то, что я искал. Это решение дает прекрасный фреймворк, но, к сожалению, у меня мало опыта работы с data.table. Так что, думаю, мне просто нужно преобразовать это в tidyverse язык.

Emman 24.12.2020 14:24

Да, извините за это. Я такой же, только наоборот! С Рождеством :)

Jonny Phelps 24.12.2020 15:29

Другие вопросы по теме