首先,以下两种方法完全不同,不是“base R vs dplyr”中的等价物。我确信其中一个可以翻译成另一个。
dplyr
这里的前提是首先重塑/旋转数据更长,以便每个药物/剂量都在自己的行上,适当地重新编号,然后将其恢复到宽状态。 p>
注意:坦率地说,我通常更喜欢处理 long 格式的数据,因此请考虑将其保持在紧邻 pivot_wider 之前的状态。这意味着您需要以某种方式将Age 和Place 带回其中。
为什么?长格式可以很好地处理多种类型的聚合; ggplot2 真的很喜欢长格式的数据;我不喜欢看到并且不得不处理所有NA/empty 值,因为许多PID 没有(例如)Drug6 或更高版本。这似乎是主观的,但它确实可以是对数据处理的客观更改/改进,具体取决于您的工作流程。
library(dplyr)
# library(tidyr) # pivot_longer, pivot_wider
dat0 <- select(dat, PID, Date, Age, Place) %>%
group_by(PID, Date) %>%
summarize(across(everything(), ~ .[!is.na(.) & nzchar(trimws(.))][1] ))
dat %>%
select(-Age, -Place) %>%
tidyr::pivot_longer(
-c(Row, PID, Date),
names_to = c(".value", "iter"),
names_pattern = "^([^0-9]+)([123]?)$") %>%
arrange(Row, iter) %>%
group_by(PID, Date) %>%
mutate(iter = row_number()) %>%
select(-Row) %>%
tidyr::pivot_wider(
c("PID", "Date"), names_sep = "",
names_from = "iter", values_from = c("Drug", "Dose")) %>%
left_join(dat0, by = c("PID", "Date"))
# # A tibble: 5 x 16
# # Groups: PID, Date [5]
# PID Date Drug1 Drug2 Drug3 Drug4 Drug5 Drug6 Dose1 Dose2 Dose3 Dose4 Dose5 Dose6 Age Place
# <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <int> <int> <int> <int> <int> <int> <int> <chr>
# 1 11A 25/10/2021 RPG NAT QRT BET "SET" "BLT" 12 34 5 10 43 45 45 PMk
# 2 12B 20/10/2021 ATY LTP CRT <NA> <NA> <NA> 13 3 3 NA NA NA 56 GTL
# 3 13A 22/10/2021 GGS GSF ERE DFS "" "" 7 12 45 5 NA NA 45 RKS
# 4 13A 26/10/2021 BRT ARR GSF <NA> <NA> <NA> 9 4 34 NA NA NA 46 GLO
# 5 14B 04/08/2021 GDS TRE HHS <NA> <NA> <NA> 2 55 34 NA NA NA 25 MTK
注意事项:
- 我很早就爆发了
dat0,因为Age 和Place 并不真正适合枢轴/重新编号/枢轴心态。
基础 R
这是一个基本 R 方法,它拆分(根据您的分组标准:PID 和 Date),找到需要重新编号的 Drug/Dose 列,重命名它们,以及 merges 所有帧重新组合在一起。
spl <- split(dat, ave(rep(1L, nrow(dat)), dat[,c("PID", "Date")], FUN = seq_along))
spl
# $`1`
# Row PID Date Drug1 Dose1 Drug2 Dose2 Drug3 Dose3 Age Place
# 1 1 11A 25/10/2021 RPG 12 NAT 34 QRT 5 45 PMk
# 3 3 12B 20/10/2021 ATY 13 LTP 3 CRT 3 56 GTL
# 4 4 13A 22/10/2021 GGS 7 GSF 12 ERE 45 45 RKS
# 5 5 13A 26/10/2021 BRT 9 ARR 4 GSF 34 46 GLO
# 7 7 14B 04/08/2021 GDS 2 TRE 55 HHS 34 25 MTK
# $`2`
# Row PID Date Drug1 Dose1 Drug2 Dose2 Drug3 Dose3 Age Place
# 2 2 11A 25/10/2021 BET 10 SET 43 BLT 45 NA
# 6 6 13A 22/10/2021 DFS 5 NA NA NA
nms <- lapply(spl, function(x) grep("^(Drug|Dose)", colnames(x), value = TRUE))
nms <- data.frame(i = rep(names(nms), lengths(nms)), oldnm = unlist(nms))
nms$grp <- gsub("[0-9]+$", "", nms$oldnm)
nms$newnm <- paste0(nms$grp, ave(nms$grp, nms$grp, FUN = seq_along))
nms <- split(nms, nms$i)
newspl <- Map(function(x, nm) {
colnames(x)[ match(nm$oldnm, colnames(x)) ] <- nm$newnm
x
}, spl, nms)
newspl[-1] <- lapply(newspl[-1], function(x) x[, c("PID", "Date", grep("^(Drug|Dose)", colnames(x), value = TRUE)), drop = FALSE ])
newspl
# $`1`
# Row PID Date Drug1 Dose1 Drug2 Dose2 Drug3 Dose3 Age Place
# 1 1 11A 25/10/2021 RPG 12 NAT 34 QRT 5 45 PMk
# 3 3 12B 20/10/2021 ATY 13 LTP 3 CRT 3 56 GTL
# 4 4 13A 22/10/2021 GGS 7 GSF 12 ERE 45 45 RKS
# 5 5 13A 26/10/2021 BRT 9 ARR 4 GSF 34 46 GLO
# 7 7 14B 04/08/2021 GDS 2 TRE 55 HHS 34 25 MTK
# $`2`
# PID Date Drug4 Dose4 Drug5 Dose5 Drug6 Dose6
# 2 11A 25/10/2021 BET 10 SET 43 BLT 45
# 6 13A 22/10/2021 DFS 5 NA NA
Reduce(function(a, b) merge(a, b, by = c("PID", "Date"), all = TRUE), newspl)
# PID Date Row Drug1 Dose1 Drug2 Dose2 Drug3 Dose3 Age Place Drug4 Dose4 Drug5 Dose5 Drug6 Dose6
# 1 11A 25/10/2021 1 RPG 12 NAT 34 QRT 5 45 PMk BET 10 SET 43 BLT 45
# 2 12B 20/10/2021 3 ATY 13 LTP 3 CRT 3 56 GTL <NA> NA <NA> NA <NA> NA
# 3 13A 22/10/2021 4 GGS 7 GSF 12 ERE 45 45 RKS DFS 5 NA NA
# 4 13A 26/10/2021 5 BRT 9 ARR 4 GSF 34 46 GLO <NA> NA <NA> NA <NA> NA
# 5 14B 04/08/2021 7 GDS 2 TRE 55 HHS 34 25 MTK <NA> NA <NA> NA <NA> NA
注意事项:
-
这样做的基本前提是您希望将这些行合并到之前的行中。这意味着(对我而言)使用base::merge 或dplyr::full_join;了解这些概念的两个很好的链接,以防您不知道:How to join (merge) data frames (inner, outer, left, right)、What's the difference between INNER JOIN, LEFT JOIN, RIGHT JOIN and FULL JOIN?
-
为此,我们需要确定哪些行与之前的行重复;此外,我们需要知道有多少先前的相同键行。有几种方法可以做到这一点,但我认为最简单的方法是使用base::split。在这种情况下,没有 PID/Date 组合有超过两行,但如果您有一个组合要求第三行,spl 的长度为 3,结果名称将输出到 Drug9/@987654342 @。
-
第二部分 (nms <- ...) 是我们处理名称的地方。前几个步骤创建了一个nms 数据框,我们将使用它来从旧名称映射到新名称。由于我们关心所有多行组的连续编号,因此我们在药物/剂量名称的基数(删除的编号)上进行聚合,以便我们从Drug1 到有多少列编号所有Drug 列。
注意:这里假设总是有完美的Drug#/Dose#;如果有任何不匹配,那么编号将是可疑的。
我们以 nms 结束,这是一个拆分数据框,就像数据的 spl 一样。这是有用且重要的,因为我们会将它们Map(类似zip 的lapply)放在一起。
-
第三个块用新名称更新spl。 newspl 中的结果只是重命名了列,这样当我们将它们合并在一起时,就不会发生列重复。
这里还有一个步骤是从列表中的第二个和后续帧中删除不相关的列。也就是说,我们将Age 和Place 保留在第一个这样的帧中,但将其从其余帧中删除。我的假设(基于重复行中这些字段的NA/empty 性质)是我们只想保留第一行的值。
-
最后一步是迭代地merge 他们在一起。 Reduce 函数很适合这个。