Я хочу переименовать столбцы в своих данных в зависимости от того, удовлетворяют ли все значения в этих столбцах условию. Например, в числовом столбце, если все значения больше 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")
is_larger_than_val <- function(x, y) {
all(x > y)
}
&
?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
?).
Спасибо!
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
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
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
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()
максимально масштабируемым, я хотел бы, чтобы он принимал любой тест, для которого я могу создать функцию тестирования.
Спасибо. Я отредактировал вопрос, чтобы ответить на ваш комментарий.
Уверен, вы могли бы сократить этот вопрос до одного абзаца!
@geotheory, возможно, я мог бы, но моя первоначальная версия была краткой и привела к комментарию Яна. Поэтому я предоставил больше информации для полноты. Не стесняйтесь редактировать сообщение, если хотите. Моя единственная цель — внести ясность и позволить людям помочь мне решить проблему.
@Emman Достаточно честно, я согласен, что большая часть длины - это дополнение
Используйте 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)
?
Это векторизованное логическое ИЛИ, см. help('|')
.
Решение 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
язык.
Да, извините за это. Я такой же, только наоборот! С Рождеством :)
Можете ли вы объяснить, какова ваша настоящая конечная цель? Трудно дать ответ, который будет вам полезен, если мы не можем понять, какова конечная цель. См. Задача XY.