Начиная с data.frame indf
и colname
, являющегося одним из его столбцов, я хочу построить еще один фрейм данных outdf
с двумя столбцами: первый, называемый colname
, содержит уникальные значения indf$colname
, а второй, называемый n
, содержит количество дубликаты в indf$colname
(оригинал включен, поэтому sum(duplicated())+1
).
colname
должен быть указан динамически.
После некоторых исследований, проб и ошибок, похоже, что это можно сделать так:
indf <- data.frame(a=c("A","A","B"), b=c(1,2,3))
colname = "a"
outdf <- indf %>%
group_by(across(all_of(colname))) %>%
mutate(n = n()) %>%
select(!!colname, n)
Я хотел бы знать, почему group_by
требует across(all_of(colname))
для обработки динамически определенного имени столбца, а с select
мне приходится снимать кавычки с помощью !!
.
Если я использую across(all_of())
в select
, я получаю эту ошибку:
Error in `select()`:
ℹ In argument: `across(all_of(colname))`.
Caused by error in `across()`:
! Must only be used inside data-masking verbs like `mutate()`,
`filter()`, and `group_by()`.
А если я использую !!
в group_by
, создается новый столбец с именем "a"
(включая двойные кавычки).
Обновлено:
Внутри !!
тоже не работает arrange
.
# This doesn't work
outdf %>%
arrange(desc(!!colname))
Вместо этого вам нужно across(all_of())
:
outdf %>%
arrange(desc(across(all_of(colname))))
Функция all_of
является вспомогательной функцией выбора, как matches
и contains
. Внутри он просто преобразует имя вашего столбца в числовой индекс столбца. Однако, начиная с версии 1.2.0, помощники выбора необходимо использовать внутри контекста выбора (см. tidyselect
).
Это означает, что помощники выбора можно использовать непосредственно внутри ?`faq-selection-context`
, не заключая их в select
, поскольку это уже контекст выбора.
Другие глаголы, такие как across
и mutate
, не составляют контекст выбора, и функция group_by
необходима для преобразования индексов столбцов во входные данные, соответствующие этим функциям.
Просто используйте across
без select(all_of(colname))
:
library(dplyr)
indf <- data.frame(a = c("A", "A", "B"), b = c(1, 2, 3))
colname <- "a"
indf %>%
group_by(across(all_of(colname))) %>%
mutate(n = n()) %>%
select(all_of(colname), n)
#> # A tibble: 3 x 2
#> # Groups: a [2]
#> a n
#> <chr> <int>
#> 1 A 2
#> 2 A 2
#> 3 B 1
и почему !!
внутри не работает group_by
? Внутри это тоже не работает arrange
@robertspierre, потому что group_by(!!colname)
оценивается как group_by("a")
, а не group_by(a)
. Это потому, что colname
— это строка символов, а не символ. Вам понадобится что-то вроде group_by(!!str2lang(colname))
, чтобы это заработало. Это работает только внутри select
, потому что select
также может принимать строки символов в качестве аргументов (indf %>% select("a")
выберет столбец a). group_by
не принимает строки символов в качестве аргумента группировки.
Это выглядит слишком сложным и раздутым, чем должно быть, с большим количеством исключений, чем правил.
@robertspierre в каком-то смысле вы усложняете задачу, пытаясь использовать инструменты там, где они не предназначены для использования. Если ваши столбцы хранятся в виде символьной строки, используйте функции выбора indf %>% group_by(across(all_of(colname))) %>% select(all_of(colname))
. Tidyverse использует нестандартную оценку, чтобы упростить прямой выбор или группировку по именам столбцов. Как только вы начнете хранить имена столбцов в виде символов, вы сделаете что-то необычное, что требует обходных путей и, возможно, лучше сделать это за пределами tidyverse.
Мне нужно написать общую функцию, которая достигает этого независимо от кадра данных и имени столбца.
@robertspierre, так почему бы не использовать код в моем ответе? Оператор !!
здесь не нужен.
group_by( !!sym(colname) )
тоже работает.