我正在研究临床试验的数据。在此数据中,存在涉及同一患者的多个观察结果,并且它们具有不同程度的缺失数据。我有四个变量用作标识符,并且我决定,如果患者的四个变量中的三个具有相同的值,则他/她应被视为重复。
所有这些变量中的数据缺失程度极低,因此不存在具有三个 NA 的患者并被视为另一具有三个 NA 的患者的重复的问题。
我已经能够通过创建变量的
comb
inations 并使用 dplyr::distinct()
来删除重复项。不幸的是,这导致其中一个观察结果被保留,该观察结果可能缺少对同一患者的观察结果的数据,但被排除在外。我将用一些假数据来演示:
library(tidyverse)
library(knitr)
tibble(Name = c("Pedro", "Pedro","Pedro","Cristina","Walter","Sara","Mateus"),
Birth = dmy(c("29/07/1994","29/07/1994","29/07/1994","01/04/1960", "22/12/1956", "20/02/1997","25/07/1994")),
CNS = c("700",NA,"700","701","702","703","704"),
Document = c("104","104",NA,"105","106","107","108"),
SystolicBP1 = c(NA,NA,120,160,152,114,NA),
DiastolicBP1 = c(NA,NA,80,100,100,92,NA),
SystolicBP2 = c(NA,NA,NA,148,NA,NA,100),
DiastolicBP2= c(NA,NA,NA,90,NA,NA,82),
HBA1c = c(7,7,7,8.2,8,9,6.5)) -> dt
kable(dt)
姓名 | 出生 | 中枢神经系统 | 文件 | 收缩压1 | 舒张压1 | 收缩压2 | 舒张压2 | HBA1c |
---|---|---|---|---|---|---|---|---|
佩德罗 | 1994-07-29 | 700 | 104 | 不适用 | 不适用 | 不适用 | 不适用 | 7.0 |
佩德罗 | 1994-07-29 | 不适用 | 104 | 不适用 | 不适用 | 不适用 | 不适用 | 7.0 |
佩德罗 | 1994-07-29 | 700 | 不适用 | 120 | 80 | 不适用 | 不适用 | 7.0 |
克里斯蒂娜 | 1960-04-01 | 701 | 105 | 160 | 100 | 148 | 90 | 8.2 |
沃尔特 | 1956-12-22 | 702 | 106 | 152 | 100 | 不适用 | 不适用 | 8.0 |
莎拉 | 1997-02-20 | 703 | 107 | 114 | 92 | 不适用 | 不适用 | 9.0 |
马特乌斯 | 1994-07-25 | 704 | 108 | 不适用 | 不适用 | 100 | 82 | 6.5 |
然后我可以运行以下命令来删除重复项,但最终会丢失一些数据:
identifiers <- c("Name","Birth","CNS","Document")
combn(identifiers, 3) -> comb
kable(comb)
姓名 姓名 姓名 出生 出生 出生 中枢神经系统 中枢神经系统 中枢神经系统 文件 文件 文件
## this takes the result of distinct() for each
## combination of the 4 identifiers, taken 3 at a time
dt %>%
distinct (!!! syms(comb[,1]), .keep_all = TRUE) %>%
distinct (!!! syms(comb[,2]), .keep_all = TRUE) %>%
distinct (!!! syms(comb[,3]), .keep_all = TRUE) %>%
distinct (!!! syms(comb[,4]), .keep_all = TRUE) %>%
kable()
姓名 出生 中枢神经系统 文件 收缩压1 舒张压1 收缩压2 舒张压2 HBA1c 佩德罗 1994-07-29 700 104 不适用 不适用 不适用 不适用 7.0 克里斯蒂娜 1960-04-01 701 105 160 100 148 90 8.2 沃尔特 1956-12-22 702 106 152 100 不适用 不适用 8.0 莎拉 1997-02-20 703 107 114 92 不适用 不适用 9.0 马特乌斯 1994-07-25 704 108 不适用 不适用 100 82 6.5
我的目标是,当面对丢失的数据时,尝试从重复的观察中获取丢失的数据。如果我能做到这一点,在上面的示例中,第一个观察将从原始小标题中的第三个观察中获取“SystolicBP1”和“DiastolicBP1”的值。
如果您想要做的是从每组中获取第一个非
NA
值,那么我们可以做
library(dplyr)
dt |>
summarize(
.by = c(Name, Birth),
across(everything(), ~ na.omit(.x)[1])
)
# # A tibble: 5 × 9
# Name Birth CNS Document SystolicBP1 DiastolicBP1 SystolicBP2 DiastolicBP2 HBA1c
# <chr> <date> <chr> <chr> <dbl> <dbl> <dbl> <dbl> <dbl>
# 1 Pedro 1994-07-29 700 104 120 80 NA NA 7
# 2 Cristina 1960-04-01 701 105 160 100 148 90 8.2
# 3 Walter 1956-12-22 702 106 152 100 NA NA 8
# 4 Sara 1997-02-20 703 107 114 92 NA NA 9
# 5 Mateus 1994-07-25 704 108 NA NA 100 82 6.5
这(默默地)丢弃可能具有不同值(示例数据中不存在)的
Name, Birth
对的多行。我们可以通过更改一个值并看到它被删除来显示这一点:
dt2 <- mutate(dt, Document = replace(Document, 1, "999"))
head(dt2, 3)
# # A tibble: 3 × 9
# Name Birth CNS Document SystolicBP1 DiastolicBP1 SystolicBP2 DiastolicBP2 HBA1c
# <chr> <date> <chr> <chr> <dbl> <dbl> <dbl> <dbl> <dbl>
# 1 Pedro 1994-07-29 700 999 NA NA NA NA 7
# 2 Pedro 1994-07-29 NA 104 NA NA NA NA 7
# 3 Pedro 1994-07-29 700 NA 120 80 NA NA 7
dt2 |>
summarize(
.by = c(Name, Birth),
across(everything(), ~ na.omit(.x)[1])
)
# # A tibble: 5 × 9
# Name Birth CNS Document SystolicBP1 DiastolicBP1 SystolicBP2 DiastolicBP2 HBA1c
# <chr> <date> <chr> <chr> <dbl> <dbl> <dbl> <dbl> <dbl>
# 1 Pedro 1994-07-29 700 999 120 80 NA NA 7
# 2 Cristina 1960-04-01 701 105 160 100 148 90 8.2
# 3 Walter 1956-12-22 702 106 152 100 NA NA 8
# 4 Sara 1997-02-20 703 107 114 92 NA NA 9
# 5 Mateus 1994-07-25 704 108 NA NA 100 82 6.5
如果是这种情况,我们可以通过输入值(我将使用非
NA
值的随机采样)然后进行重复数据删除来使过程变得更智能。
dt2 |>
mutate(
.by = c(Name, Birth),
across(everything(), ~ if_else(is.na(.x), na.omit(.x)[1], .x))
) |>
distinct()
# # A tibble: 6 × 9
# Name Birth CNS Document SystolicBP1 DiastolicBP1 SystolicBP2 DiastolicBP2 HBA1c
# <chr> <date> <chr> <chr> <dbl> <dbl> <dbl> <dbl> <dbl>
# 1 Pedro 1994-07-29 700 999 120 80 NA NA 7
# 2 Pedro 1994-07-29 700 104 120 80 NA NA 7
# 3 Cristina 1960-04-01 701 105 160 100 148 90 8.2
# 4 Walter 1956-12-22 702 106 152 100 NA NA 8
# 5 Sara 1997-02-20 703 107 114 92 NA NA 9
# 6 Mateus 1994-07-25 704 108 NA NA 100 82 6.5
执行
na.omit(.x)[1]
的一个副作用是,如果所有值(该列的该组中)都是 NA
,那么虽然 na.omit(.x)
将返回 c()
,但 [1]
将强制其返回 NA
.
仅供参考,使用
.by=
需要 dplyr_1.1.0
或更新版本;如果您有旧版本,请从 mutate(.by=c(..), stuff)
更改为 group_by(..) |> mutate(stuff) |> ungroup()
。
我们可以从前四个 ident 列中创建字符串,并根据距
hclust
的字符串距离运行 adist
以获得 id
。接下来,使用 by
,我们沿着这些 id
分割数据并减少到一行,留下第一个值或 NA
。
> cl_fun <- \(data, h., ident=c("Name", "Birth", "CNS", 'Document')) {
+ strings <- Reduce(paste0, data[ident]) ## create ident strings
+ ld <- adist(tolower(strings)) ## distance matrix
+ hc <- hclust(as.dist(ld)) ## cluster analysis
+ id <- cutree(hc, h=h.) ## cut tree to groups
+ within(data, id <- id) ## add id to data
+ }
> na_fun <- \(.) {
+ o <- vapply(., \(x) {
+ o <- if (all(is.na(x))) {
+ NA_character_
+ } else {
+ na.omit(as.character(x))[[1]]
+ }
+ }, FUN.VALUE=character(1))
+ o <- type.convert(as.data.frame(t(o)), as.is=TRUE)
+ within(o, {Birth <- as.Date(Birth)})
+ }
> by(cl_fun(df, h.=8.0), ~id, na_fun) |> do.call(what='rbind')
Name Birth CNS Document SystolicBP1 DiastolicBP1 SystolicBP2 DiastolicBP2 HBA1c id
1 Pedro 1994-07-29 700 104 120 80 100 NA 7.0 1
2 Cristina 1960-04-01 701 105 160 100 148 90 8.2 2
3 Walter 1956-12-22 702 106 152 100 NA NA 8.0 3
4 Sara 1997-02-20 703 107 114 92 NA NA 9.0 4
5 Mateus 1994-07-25 704 108 NA NA 100 82 6.5 5
6 Ana <NA> NA NA 130 94 100 89 NA 6
Ordep 被认为是 Pedro 的复制品,Laura 被认为是 Ana 的复制品,根据您的说法,这很好。作为
cl_fun()
中的调整参数,我使用 h
中的 cutree()
,它指定了绘制 hc
时的高度,您也可以尝试使用 k
,它会指定具有缺点的簇的数量, k
是一个整数,灵活性较差。您还可以指定更多 adist()
参数(例如 costs
)进行微调。在此h=8.0
产生了想要的结果。
数据:
> dput(df)
structure(list(Name = c("Pedro", "Pedro", "Pedro", "Ordep", "Cristina",
"Walter", "Sara", "Mateus", "Ana", "Ana", "Laura"), Birth = structure(c(8975,
8975, 8975, 8975, -3562, -4758, 9912, 8971, NA, NA, NA), class = "Date"),
CNS = c("700", "<NA>", "700", "700", "701", "702", "703",
"704", NA, NA, NA), Document = c("104", "104", "<NA>", "104",
"105", "106", "107", "108", NA, NA, NA), SystolicBP1 = c(NA,
NA, 120L, NA, 160L, 152L, 114L, NA, NA, 130L, 120L), DiastolicBP1 = c(NA,
NA, 80L, NA, 100L, 100L, 92L, NA, 94L, NA, 96L), SystolicBP2 = c(NA,
NA, NA, 100L, 148L, NA, NA, 100L, 100L, 110L, 110L), DiastolicBP2 = c(NA,
NA, NA, NA, 90L, NA, NA, 82L, 89L, NA, 91L), HBA1c = c(7,
7, 7, NA, 8.2, 8, 9, 6.5, NA, NA, NA)), row.names = c(NA,
-11L), class = "data.frame")
看起来像:
> df
Name Birth CNS Document SystolicBP1 DiastolicBP1 SystolicBP2 DiastolicBP2 HBA1c
1 Pedro 1994-07-29 700 104 NA NA NA NA 7.0
2 Pedro 1994-07-29 <NA> 104 NA NA NA NA 7.0
3 Pedro 1994-07-29 700 <NA> 120 80 NA NA 7.0
4 Ordep 1994-07-29 700 104 NA NA 100 NA NA
5 Cristina 1960-04-01 701 105 160 100 148 90 8.2
6 Walter 1956-12-22 702 106 152 100 NA NA 8.0
7 Sara 1997-02-20 703 107 114 92 NA NA 9.0
8 Mateus 1994-07-25 704 108 NA NA 100 82 6.5
9 Ana <NA> <NA> <NA> NA 94 100 89 NA
10 Ana <NA> <NA> <NA> 130 NA 110 NA NA
11 Laura <NA> <NA> <NA> 120 96 110 91 NA