在大型数据框中删除跨列的重复行值

4

我有一个 data.frame

ID  code1   code2  code3
A    143     143    144
A    35      453     35
A             35     15
B    46      46      45
B    12      43     765
C    255     455     344
C    343     343     343
C    343     23      23

每个代码只出现一次。

id可能会重复。真实的数据框非常大。

ID  code1   code2  code3
A    143             144
A    35      453     
A             35     15
B    46              45
B    12      43      765
C    255     455     344
C    343          
C    343     23      

谢谢

2个回答

4

这个怎么样?

df[, 2:4][t(apply(df[,2:4], 1, duplicated))] <- NA

编辑:一个更快的基础解决方案:

for (i in 2:(ncol(df)-1)) {
    for (j in (i+1):ncol(df)) {
        chk <- df[[i]] == df[[j]]
        df[[j]][chk] <- NA
    }
}

以下是对上述两种方法的基准测试结果,同时也包括了AnandaMahto使用的reshape2data.table方法在更大数据上的表现。使用正确的ij索引的for-loop似乎是最快速的。

基准测试结果:

require(microbenchmark)
microbenchmark(ar.f <- arun.f(df), ar.s <- arun.s(df), 
               an.f <- ananda.ave(df), 
               an.s <- ananda.dt(copy(DT)), times=10)

# Unit: milliseconds

#                 expr       min        lq   median        uq       max neval
#           arun.f(df) 4816.3937 5197.0626 6402.454 6955.9380 7534.6912    10
#           arun.s(df)  114.8372  118.7971  149.284  202.6081  297.4787    10
#       ananda.ave(df) 2877.7936 3288.5935 3650.660 3985.5390 4111.9064    10
#  ananda.dt(copy(DT)) 3383.1229 3861.6379 4432.751 4776.6108 5368.6504    10

创建数据:

set.seed(1234)
df <- cbind(data.frame(ID = rep(letters[1:20], each=1e4)), stringsAsFactors=FALSE),  
            matrix(sample(1:10, 6 * 1e5, replace=TRUE), ncol=3))
names(df)[2:4] <- paste0("code", 1:3)

我的第一个版本:

arun.f <- function(df) {
    df[, 2:4][t(apply(df[,2:4], 1, duplicated))] <- NA
    df
}

My second version:

arun.s <- function(df) {
    for (i in 2:(ncol(df)-1)) {
        for (j in (i+1):ncol(df)) {
            chk <- df[[i]] == df[[j]]
            df[[j]][chk] <- NA
        }
    }
    df
}

Ananda的ave+reshape2解决方案:

library(reshape2)
ananda.ave <- function(df) {
    df$ID2 <- with(df, ave(ID, ID, FUN = seq_along))
    m.df <- melt(df, id.vars=c("ID", "ID2"))
    m.df[duplicated(m.df[setdiff(names(m.df), "variable")]), "value"] <- NA
    dcast(m.df, ID + ID2 ~ variable)
}

Ananda的data.table解决方案:

(稍作修改以更加优化)

library(data.table)
DT <- data.table(df)
ananda.dt <- function(dt) {
    temp <- dt[, list(ID2 = 1:.N, Value = unlist(.SD, use.names=FALSE)), by ="ID"]
    temp[duplicated(temp), Value := NA]
    out <- setnames(temp[, as.list(Value), by=list(ID, ID2)], 3:5, paste0("code", 1:3))
}

备受诟病的 for 得到了胜利(但原始答案更具表现力)。 - Matthew Lundberg

1
这个解决方案可能有些低效,但主要是因为需要在宽格式和长格式之间来回转换数据。不过,您可能会发现在“长”格式下更容易处理数据。
首先,生成第二个ID,因为您的ID跨越了多行。
mydf$ID2 <- with(mydf, ave(ID, ID, FUN = seq_along))

其次,使用“reshape2”包中的melt将数据转换成长格式。

library(reshape2)
m.df <- melt(mydf, id.vars=c("ID", "ID2"))

将数据转换为长格式后,更容易识别重复项并用NA替换。

m.df[duplicated(m.df[setdiff(names(m.df), "variable")]), "value"] <- NA

如果您对长格式的数据感到满意,请停在这里。如果您想将其恢复为宽格式,请使用dcast(同样来自“reshape2”)。

dcast(m.df, ID + ID2 ~ variable)
#   ID ID2 code1 code2 code3
# 1  A   1   143    NA   144
# 2  A   2    35   453    NA
# 3  A   3    NA    35    15
# 4  B   1    46    NA    45
# 5  B   2    12    43   765
# 6  C   1   255   455   344
# 7  C   2   343    NA    NA
# 8  C   3   343    23    NA

作为参考,这在基本R中也是可行的,但语法更加笨拙(尽管它可能比“reshape2”等效方法表现更好)。

mydf$ID2 <- with(mydf, ave(ID, ID, FUN = seq_along))
m.df <- cbind(mydf[c("ID", "ID2")], 
              stack(mydf[setdiff(names(mydf), c("ID", "ID2"))]))
m.df[duplicated(m.df[setdiff(names(m.df), "ind")]), "values"] <- NA
cbind(mydf[c("ID", "ID2")], unstack(m.df, values ~ ind))

更新:可能的data.table解决方案

如果您提到数据很大,您可能希望探索data.table。这里是一个可能的解决方案(尽管@Arun可能有更直接的解决方案可以分享)。

library(data.table)
DT <- data.table(mydf)

## Creates your long data.table
temp <- DT[, list(ID2 = 1:.N, Value = unlist(.SD)), by ="ID"]
## Changes duplicates to NA and adds in the "Code" column
temp[duplicated(temp), Value := NA][, Variable := rep(names(DT)[-1], 
                                                      each = nrow(DT))]
## "Reshapes" the data from long to wide
temp[, as.list(setattr(Value, 'names', Variable)), by=list(ID, ID2)]
#    ID ID2 code1 code1 code1
# 1:  A   1   143    NA   144
# 2:  A   2    35   453    NA
# 3:  A   3    NA    35    15
# 4:  B   1    46    NA    45
# 5:  B   2    12    43   765
# 6:  C   1   255   455   344
# 7:  C   2   343    NA    NA
# 8:  C   3   343    23    NA

糟糕。正如@Arun所指出的那样,我在我的data.table答案中搞砸了名称。抱歉! - A5C1D2H2I1M1N2O1R2T1
抱歉如果有其他变量,但我只想更改code1、code2和code3的代码。在我的数据框中有变量"DATE"、"BIRTHDAY"、"ID"、"code1"、"code2"、"code3"和"SEX"。谢谢。 - Vivian
m.df = melt(data, measure.vars=c("code1","code1","code1"), id.vars=c("ID", "ID2")) 这样对吗? - Vivian

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接