问题 将变量值重新整形为列的最快​​方法


我有一个大约300万行的数据集和以下结构:

PatientID| Year | PrimaryConditionGroup
---------------------------------------
1        | Y1   | TRAUMA
1        | Y1   | PREGNANCY
2        | Y2   | SEIZURE
3        | Y1   | TRAUMA

作为R的新手,我在找到将数据重塑为下面结构的正确方法时遇到了一些麻烦:

PatientID| Year | TRAUMA | PREGNANCY | SEIZURE
----------------------------------------------
1        | Y1   | 1      | 1         | 0
2        | Y2   | 0      | 0         | 1
3        | Y1   | 1      | 0         | 1

我的问题是:创建data.frame的最快/最优雅的方法是什么,其中PrimaryConditionGroup的值成为列,按PatientID和Year(计算出现的次数)分组?


4464
2017-11-15 19:43


起源



答案:


可能有更简洁的方法可以做到这一点,但是对于纯粹的速度,它很难击败 data.table基于解决方案:

df <- read.table(text="PatientID Year  PrimaryConditionGroup
1         Y1    TRAUMA
1         Y1    PREGNANCY
2         Y2    SEIZURE
3         Y1    TRAUMA", header=T)

library(data.table)
dt <- data.table(df, key=c("PatientID", "Year"))

dt[ , list(TRAUMA =    sum(PrimaryConditionGroup=="TRAUMA"),
           PREGNANCY = sum(PrimaryConditionGroup=="PREGNANCY"),
           SEIZURE =   sum(PrimaryConditionGroup=="SEIZURE")),
   by = list(PatientID, Year)]

#      PatientID Year TRAUMA PREGNANCY SEIZURE
# [1,]         1   Y1      1         1       0
# [2,]         2   Y2      0         0       1
# [3,]         3   Y1      1         0       0

编辑:  aggregate() 提供“基础R”解决方案,可能会或可能不会更惯用。 (唯一的复杂因素是聚合返回一个矩阵,而不是data.frame;下面的第二行修复了这个。)

out <- aggregate(PrimaryConditionGroup ~ PatientID + Year, data=df, FUN=table)
out <- cbind(out[1:2], data.frame(out[3][[1]]))

第二次编辑 最后,使用了一个简洁的解决方案 reshape 包让你到同一个地方。

library(reshape)
mdf <- melt(df, id=c("PatientID", "Year"))
cast(PatientID + Year ~ value, data=j, fun.aggregate=length)

12
2017-11-15 20:06



+1 ddply 打字不会少得多,真的,当然会慢得多。 - joran
为什么你会考虑ddply这个问题? - hadley
嗨Josh,谢谢你,这可以按预期工作并且表现很好。什么是重塑数据最简洁/惯用的方法(如果性能不是一个问题) - Matt
嗨,马特 - 我刚刚将一个替代解决方案拉到一起,并将其添加到帖子中。这看起来更简洁/惯用吗? - Josh O'Brien
非常好,非常感谢你。 - Matt


答案:


可能有更简洁的方法可以做到这一点,但是对于纯粹的速度,它很难击败 data.table基于解决方案:

df <- read.table(text="PatientID Year  PrimaryConditionGroup
1         Y1    TRAUMA
1         Y1    PREGNANCY
2         Y2    SEIZURE
3         Y1    TRAUMA", header=T)

library(data.table)
dt <- data.table(df, key=c("PatientID", "Year"))

dt[ , list(TRAUMA =    sum(PrimaryConditionGroup=="TRAUMA"),
           PREGNANCY = sum(PrimaryConditionGroup=="PREGNANCY"),
           SEIZURE =   sum(PrimaryConditionGroup=="SEIZURE")),
   by = list(PatientID, Year)]

#      PatientID Year TRAUMA PREGNANCY SEIZURE
# [1,]         1   Y1      1         1       0
# [2,]         2   Y2      0         0       1
# [3,]         3   Y1      1         0       0

编辑:  aggregate() 提供“基础R”解决方案,可能会或可能不会更惯用。 (唯一的复杂因素是聚合返回一个矩阵,而不是data.frame;下面的第二行修复了这个。)

out <- aggregate(PrimaryConditionGroup ~ PatientID + Year, data=df, FUN=table)
out <- cbind(out[1:2], data.frame(out[3][[1]]))

第二次编辑 最后,使用了一个简洁的解决方案 reshape 包让你到同一个地方。

library(reshape)
mdf <- melt(df, id=c("PatientID", "Year"))
cast(PatientID + Year ~ value, data=j, fun.aggregate=length)

12
2017-11-15 20:06



+1 ddply 打字不会少得多,真的,当然会慢得多。 - joran
为什么你会考虑ddply这个问题? - hadley
嗨Josh,谢谢你,这可以按预期工作并且表现很好。什么是重塑数据最简洁/惯用的方法(如果性能不是一个问题) - Matt
嗨,马特 - 我刚刚将一个替代解决方案拉到一起,并将其添加到帖子中。这看起来更简洁/惯用吗? - Josh O'Brien
非常好,非常感谢你。 - Matt


有快 melt 和 dcast 在C版本中实现的data.table特定方法 >=1.9.0。这里与@Josh关于300万行数据的帖子中的其他优秀答案进行了比较(仅仅排除了基础:::聚合,因为它花了很长时间)。

有关NEWS条目的更多信息,请转到 这里

我假设您有1000名患者,总共5年。您可以调整变量 patients 和 year 因此。

require(data.table) ## >= 1.9.0
require(reshape2)

set.seed(1L)
patients = 1000L
year = 5L
n = 3e6L
condn = c("TRAUMA", "PREGNANCY", "SEIZURE")

# dummy data
DT <- data.table(PatientID = sample(patients, n, TRUE),
                 Year = sample(year, n, TRUE), 
                 PrimaryConditionGroup = sample(condn, n, TRUE))

DT_dcast <- function(DT) {
    dcast.data.table(DT, PatientID ~ Year, fun.aggregate=length)
}

reshape2_dcast <- function(DT) {
    reshape2:::dcast(DT, PatientID ~ Year, fun.aggregate=length)
}

DT_raw <- function(DT) {
    DT[ , list(TRAUMA = sum(PrimaryConditionGroup=="TRAUMA"),
            PREGNANCY = sum(PrimaryConditionGroup=="PREGNANCY"),
              SEIZURE = sum(PrimaryConditionGroup=="SEIZURE")),
    by = list(PatientID, Year)]
}

# system.time(.) timed 3 times
#         Method Time_rep1 Time_rep2 Time_rep3
#       dcast_DT     0.393     0.399     0.396
#    reshape2_DT     3.784     3.457     3.605
#         DT_raw     0.647     0.680     0.657

dcast.data.table 比使用常规聚合快约1.6倍 data.table 比8.8倍快 reshape2:::dcast


1
2018-03-13 10:39