Я пытаюсь создать описательную таблицу, используя tbl_summary в R, которая будет показывать как процент столбца, так и процент строки для каждой категории. Рассмотрим следующий пример кода, использующий набор данных iris R:
iris_table <- iris %>%
mutate(Sepal.Length.Cat = if_else(Sepal.Length > 5, "Big","Small")) %>%
tbl_summary(by = Species,
include = c(Sepal.Length.Cat, Sepal.Width),
type = list(
Sepal.Width ~ "continuous2"
),
statistic = list(
all_continuous2() ~ c("{mean} ({sd})","{min} - {max}"),
all_categorical() ~ "{n} ({p}%)"
),
missing_text = "Missing") %>%
add_overall(col_label = "**Overall** <br>N = {n}") %>%
modify_footnote(update = everything() ~ NA)
print(iris_table)
Используя приведенный выше код, я получаю вывод по умолчанию для аргумента {p}, который равен проценту = «столбец». Однако я также хотел бы включить процент строки. Например, n = 22 в ячейке для видов Setosa с «большой» длиной чашелистика. Прямо сейчас ячейка показывает 22 (44%), что в сумме составляет 22/50 Setosa. Я хотел бы, чтобы он показывал что-то вроде «n = 22 (44%; 19%)», где 19% — это процент дополнительной строки (т. е. 22/118 всего Big).
Я попытался использовать аргумент процент = «строка», встроенный в tbl_summary. Однако это не только избавляет от процентов столбца, но также изменяет проценты в столбце «Общее» на проценты строк. Я бы хотел, чтобы проценты столбца остались, а категория «Общее» осталась только процентами столбца.
Пакет не предназначен для отображения процентных значений как по строкам, так и по столбцам. Но в новой версии пакета появились более универсальные способы создания индивидуальных таблиц. В приведенном ниже примере мы сначала вычисляем всю статистику, которая появится в таблице, а затем передаем ее новой функции под названием tbl_ard_summary()
. Это не самый простой для чтения код, но он принесет вам обоим процент.
library(cards)
library(gtsummary)
packageVersion("gtsummary")
#> [1] '2.0.1.9002'
iris2 <- iris |>
dplyr::mutate(Sepal.Length.Cat = ifelse(Sepal.Length > 5, "Big","Small"))
# create the primary ARD
ard <- iris2 |>
ard_stack(
.by = Species,
ard_continuous(variables = Sepal.Width),
ard_categorical(variables = Sepal.Length.Cat),
.missing = TRUE,
.attributes = TRUE
) |>
# create ARD for row percentages
bind_ard(
ard_categorical(iris2, by = Species, variables = Sepal.Length.Cat, statistic = ~"p", denominator = "row") |>
dplyr::mutate(stat_name = ifelse(stat_name == "p", "p_row", stat_name))
)
# pass the ARD to gtsummary to create table
ard |>
tbl_ard_summary(
by = Species,
include = c(Sepal.Length.Cat, Sepal.Width),
type = list(
Sepal.Width ~ "continuous2"
),
statistic = list(
all_continuous2() ~ c("{mean} ({sd})","{min} - {max}"),
all_categorical() ~ "{n} (Column {p}%; Row {p_row}%)"
),
missing_text = "Missing"
) |>
modify_footnote(all_stat_cols() ~ NA) |>
as_kable() # convert to kable to display on SO
Created on 2024-08-27 with reprex v2.1.1
Каркас tbl_ard_*()
совершенно новый, и я все еще его дорабатываю. На данный момент он поддерживает не все функции, которые поддерживает функция tbl_summary()
. Чтобы получить общие столбцы (на данный момент), вам нужно будет повторить приведенный выше код без переменной by
, а затем вы можете использовать tbl_merge()
для объединения двух таблиц. Аргумента метки сейчас нет, но если вы установите версию пакета gtsummary для разработчиков, аргумент label
будет присутствовать.
Вместо использования версии pkg для разработчиков для получения аргумента label
вы также можете назначить метки столбцов перед передачей фрейма данных в ard_stack()
. Самый простой способ назначить метки столбцов — использовать функцию labelled::set_variable_labels()
IMO.
Мне еще предстоит найти метод, встроенный в gtsummary
. Мне удалось создать обходной путь. К сожалению, это не очень динамично и универсально; это сильно зависит от расположения в таблице, которую вы использовали в своем вопросе. Вероятно, для повторного использования потребуется модификация.
Я создал функцию, которая изменяет таблицу по вашему запросу.
fixer <- function(tbl) { # add row percentages to table with column percentages
tabIn <- tbl$inputs
nms <- names(tabIn$type[tabIn$type %in% "categorical"]) # which columns?
# calculate the row %, arrange/format as per table
rp <- table(tabIn$data[, nms], tabIn$data[, tabIn[["by"]]]) %>%
prop.table(1) %>% t() %>% as.vector() %>% style_percent()
# add percentages to the table
map(2:3, \(j) { # for each row in the table
imap(paste0("stat_", 1:3), \(k, i) { # for the stat in each row
val <- tbl$table_body[[j, k]] # collect content
val2 <- str_replace(val, "\\)", paste0("/", rp[(j - 1) * i], "%\\)")) # fix
tbl$table_body[[j, k]] <<- val2 # add update back to table
})
})
tbl # return updated table
}
Когда вы создаете свою таблицу или после ее вызова, вы вызываете эту функцию. Например:
iris_table %>% fixer() # call table and update
Или со столом в том виде, в каком он сделан...
library(gtsummary)
iris_table <- iris %>%
mutate(Sepal.Length.Cat = if_else(Sepal.Length > 5, "Big","Small")) %>%
tbl_summary(by = Species,
include = c(Sepal.Length.Cat, Sepal.Width),
type = list(
Sepal.Width ~ "continuous2"
),
statistic = list(
all_continuous2() ~ c("{mean} ({sd})","{min} - {max}"),
all_categorical() ~ "{n} ({p}%)"
),
percent = "column",
missing_text = "Missing") %>%
add_overall(col_label = "**Overall** <br>N = {n}") %>%
modify_footnote(update = everything() ~ NA) %>% fixer() # <---- I'm new
Проверьте это
Эта функция действительно классная, спасибо за вашу тяжелую работу! Я пытаюсь изменить таблицу моих фактических данных, чтобы посмотреть, будет ли она работать. Мой главный камень преткновения — попытка выяснить, как внести изменения, чтобы включить все более 80 категориальных переменных, каждая из которых имеет разные уровни. Я предполагаю, что это где-то на карте (2:3...) часть функции (извиняюсь, я новичок в функциях)?
Без проблем. В приведенной выше таблице есть три переменные и две строки переменных. Вы видите 2:3 в первом вызове map
, это затронутые строки (строка 1 — это строка, содержащая только слова Sepal. Length Cat
). Во вложенном вызове imap
вы видите stat_....
, поскольку каждый столбец со статистикой имеет имя stat_1
, stat_2...and so on (where the
overall` всегда и только stat_0
. Если вы посмотрите на iris_table$itable_body
, вы увидите эту таблицу как фрейм данных. (Это может помочь.) Если у вас есть еще вопросы, дайте мне знать. Вызов, который создает rp
— он должен быть отдельным для ea. вар
Спасибо за быстрый и подробный ответ! Я опробовал ваше решение, и оно сработало с моими данными. Оставшиеся вопросы: (1) В tbl_ard_summary возникла ошибка, когда я включил исходную строку add_overall(). Есть ли способ добавить общий столбец? (2) Добавление аргумента «label = list(...)» из исходного tbl_summary также привело к ошибке. Как я могу обойти это, чтобы добавить ярлыки? Примечание для других: я создал список из более чем 80 категориальных переменных (например, all_categorical |> c(var1, var2, var3 и т. д.)), затем заменил строку ard_categorical на ard_categorical(variables = all_of(all_categorical)).