data.table:如何根据包含列名的分组唯一行值更改列值
data.table: How to change column values based on grouped unique row values that contain column names
我有一个包含 ~18^6 行的 data.table,我需要通过 ID 获取 CLASS 的唯一值,并将它们各自的列设置为 1,如下图所示宝贝示例
DT <- data.table::data.table(ID=c("1","1","1","2","2"),
CLASS=c("a","a","b","c","b"),
a=c(0,0,0,0,0),
b=c(0,0,0,0,0),
c=c(0,0,0,0,0))
### Start with this
ID CLASS a b c
1 a 0 0 0
1 a 0 0 0
1 b 0 0 0
2 c 0 0 0
2 b 0 0 0
### Want this
ID CLASS a b c
1 a 1 1 0
1 a 1 1 0
1 b 1 1 0
2 c 0 1 1
2 b 0 1 1
我的第一直觉是尝试下面的代码,但发现它会将所有列设置为 1,因为 unique(DT$CLASS) 固有地包含所有 ID 的所有唯一值,并且不会通过 "grouping" 参数参数这么说。
### Tried this
DT[,unique(DT$CLASS):=1,by=ID]
### Got this
ID CLASS a b c
1 a 1 1 1
1 a 1 1 1
1 b 1 1 1
2 c 1 1 1
2 b 1 1 1
我一直在努力充分利用 data.table 的全部潜力和速度,并且想仅使用 data.table 个参数中的命令来创建所需的输出。
谁能帮我写出正确的代码,只使用 data.table commands/arguments,让我的第 j 个索引只包含唯一值,按 ID,并将适当的列设置为 1?
Follow-up Question:
假设每一行也有一个关联的日期 RXDATE,我想创建所有 class 值的相应列名,这些值按 ID CLASS 保存最小 RXDATE .我也可以为此求助于 dcast 吗?
### Start with this
ID CLASS a b c RXDATE
1 a 1 1 0 1-1-99
1 a 1 1 0 1-2-99
1 b 1 1 0 1-3-99
2 c 0 1 1 5-4-00
2 b 0 1 1 6-5-01
### Want this
ID CLASS a b c RXDATE a_DT b_DT c_DT
1 a 1 1 0 1-1-99 1-1-99 1-3-99 NA
1 a 1 1 0 1-2-99 1-1-99 1-3-99 NA
1 b 1 1 0 1-3-99 1-1-99 1-3-99 NA
2 c 0 1 1 5-4-00 NA 6-5-01 5-4-00
2 b 0 1 1 6-5-01 NA 6-5-01 5-4-00
这是一种选择。
unique_wide <- dcast(DT[, unique(CLASS), by = ID], ID ~ V1, value.var = "V1")
classes <- setdiff(names(unique_wide), "ID")
unique_wide[, (classes) := lapply(.SD, function(col) { ifelse(is.na(col), 0L, 1L) }),
.SDcols = classes]
DT[, (classes) := unique_wide[.SD, classes, on = "ID", with = FALSE]]
DT[]
ID CLASS a b c
1: 1 a 1 1 0
2: 1 a 1 1 0
3: 1 b 1 1 0
4: 2 c 0 1 1
5: 2 b 0 1 1
我们首先用DT[, unique(CLASS), by = ID]
得到可能的唯一值。
请注意,您可以直接引用 j
中的列,而无需 $
。
我们可以将其重塑为宽格式以获得如下内容:
ID a b c
1: 1 a b <NA>
2: 2 <NA> b c
接下来的两行只是将值转换为整数,
将 NA
设置为 0,否则设置为 1。
> unique_wide
ID a b c
1: 1 1 1 0
2: 2 0 1 1
之后应用,
这就像一个嵌套连接。
在这种情况下,连接是基于 ID
完成的,
因此它在 DT
和 unique_wide
之间匹配的所有行都将非 ID
列的值更新为 unique_wide
中存在的值。
另请注意,with = FALSE
对基于包含字符向量的变量的 select 列很有用。
顺便说一句,请注意,您甚至不需要最初的 0 个非 ID
列,
如果您将 table 声明为
DT <- data.table::data.table(ID=c("1","1","1","2","2"),
CLASS=c("a","a","b","c","b"))
上面的代码仍然有效。
使用 dcast
和 merge
你还可以:
DT <- data.table::data.table(ID=c("1","1","1","2","2"),
CLASS=c("a","a","b","c","b"),
a=c(0,0,0,0,0),
b=c(0,0,0,0,0),
c=c(0,0,0,0,0))
# dcast to convert to wide
DT_dcast <- dcast(DT[, .(ID, CLASS)], ID ~ CLASS, fun.aggregate = function(x) length(unique(x)), value.var = "CLASS")
DT_dcast
ID a b c
1: 1 1 1 0
2: 2 0 1 1
# Then merge with the original data.table
DT_m <- merge(DT[, .(ID, CLASS)], DT_dcast, by = "ID")
DT_m
ID CLASS a b c
1: 1 a 1 1 0
2: 1 a 1 1 0
3: 1 b 1 1 0
4: 2 c 0 1 1
5: 2 b 0 1 1
编辑
您仍然可以对 dcast
和 merge
.
使用相同的方法
我从你的 'start with this' 数据中注意到第 2 行有不同的 RX 日期,从 'want this' 数据中你只为此保留了 '1-1-99'。
DT2 <- data.table::data.table(ID=c("1","1","1","2","2"),
CLASS=c("a","a","b","c","b"),
a=c(0,0,0,0,0),
b=c(0,0,0,0,0),
c=c(0,0,0,0,0),
RXDate = c("1-1-99", "1-2-99", "1-3-99", "5-4-00", "6-5-01"))
# 2nd row from the data provided has different RXDate under same ID and Class.
# Use x[1] to pick first
DT_dcast <- dcast(DT2[, .(ID, CLASS, RXDate)], ID ~ CLASS,
fun.aggregate = function(x) x[1],
value.var = c("CLASS", "RXDate"))
DT_dcast
ID CLASS.1_a CLASS.1_b CLASS.1_c RXDate_a RXDate_b RXDate_c
1: 1 a b <NA> 1-1-99 1-3-99 <NA>
2: 2 <NA> b c <NA> 6-5-01 5-4-00
# Convert 1 or 0 under CLASS
class_cols <- names(DT_dcast)[grepl("CLASS", names(DT_dcast))]
for (col in class_cols) set(DT_dcast, j = col, value = ifelse(is.na(DT_dcast[[col]]), 0, 1))
DT_dcast
ID CLASS.1_a CLASS.1_b CLASS.1_c RXDate_a RXDate_b RXDate_c
1: 1 1 1 0 1-1-99 1-3-99 <NA>
2: 2 0 1 1 <NA> 6-5-01 5-4-00
# Then merge with the original data.table
DT_m <- merge(DT2[, .(ID, CLASS, RXDate)], DT_dcast, by = "ID")
DT_m
ID CLASS RXDate CLASS.1_a CLASS.1_b CLASS.1_c RXDate_a RXDate_b RXDate_c
1: 1 a 1-1-99 1 1 0 1-1-99 1-3-99 <NA>
2: 1 a 1-2-99 1 1 0 1-1-99 1-3-99 <NA>
3: 1 b 1-3-99 1 1 0 1-1-99 1-3-99 <NA>
4: 2 c 5-4-00 0 1 1 <NA> 6-5-01 5-4-00
5: 2 b 6-5-01 0 1 1 <NA> 6-5-01 5-4-00
如果要重命名列,可以使用 setnames
另一种可能的方法:
idx <- DT3[, CJ(I=.I, J=match(unique(CLASS), names(DT))), by=ID]
setDF(DT3)
DT3[as.matrix(idx[, .(I, J)])] <- 1L
setDT(DT3)[]
我有一个包含 ~18^6 行的 data.table,我需要通过 ID 获取 CLASS 的唯一值,并将它们各自的列设置为 1,如下图所示宝贝示例
DT <- data.table::data.table(ID=c("1","1","1","2","2"),
CLASS=c("a","a","b","c","b"),
a=c(0,0,0,0,0),
b=c(0,0,0,0,0),
c=c(0,0,0,0,0))
### Start with this
ID CLASS a b c
1 a 0 0 0
1 a 0 0 0
1 b 0 0 0
2 c 0 0 0
2 b 0 0 0
### Want this
ID CLASS a b c
1 a 1 1 0
1 a 1 1 0
1 b 1 1 0
2 c 0 1 1
2 b 0 1 1
我的第一直觉是尝试下面的代码,但发现它会将所有列设置为 1,因为 unique(DT$CLASS) 固有地包含所有 ID 的所有唯一值,并且不会通过 "grouping" 参数参数这么说。
### Tried this
DT[,unique(DT$CLASS):=1,by=ID]
### Got this
ID CLASS a b c
1 a 1 1 1
1 a 1 1 1
1 b 1 1 1
2 c 1 1 1
2 b 1 1 1
我一直在努力充分利用 data.table 的全部潜力和速度,并且想仅使用 data.table 个参数中的命令来创建所需的输出。
谁能帮我写出正确的代码,只使用 data.table commands/arguments,让我的第 j 个索引只包含唯一值,按 ID,并将适当的列设置为 1?
Follow-up Question:
假设每一行也有一个关联的日期 RXDATE,我想创建所有 class 值的相应列名,这些值按 ID CLASS 保存最小 RXDATE .我也可以为此求助于 dcast 吗?
### Start with this
ID CLASS a b c RXDATE
1 a 1 1 0 1-1-99
1 a 1 1 0 1-2-99
1 b 1 1 0 1-3-99
2 c 0 1 1 5-4-00
2 b 0 1 1 6-5-01
### Want this
ID CLASS a b c RXDATE a_DT b_DT c_DT
1 a 1 1 0 1-1-99 1-1-99 1-3-99 NA
1 a 1 1 0 1-2-99 1-1-99 1-3-99 NA
1 b 1 1 0 1-3-99 1-1-99 1-3-99 NA
2 c 0 1 1 5-4-00 NA 6-5-01 5-4-00
2 b 0 1 1 6-5-01 NA 6-5-01 5-4-00
这是一种选择。
unique_wide <- dcast(DT[, unique(CLASS), by = ID], ID ~ V1, value.var = "V1")
classes <- setdiff(names(unique_wide), "ID")
unique_wide[, (classes) := lapply(.SD, function(col) { ifelse(is.na(col), 0L, 1L) }),
.SDcols = classes]
DT[, (classes) := unique_wide[.SD, classes, on = "ID", with = FALSE]]
DT[]
ID CLASS a b c
1: 1 a 1 1 0
2: 1 a 1 1 0
3: 1 b 1 1 0
4: 2 c 0 1 1
5: 2 b 0 1 1
我们首先用DT[, unique(CLASS), by = ID]
得到可能的唯一值。
请注意,您可以直接引用 j
中的列,而无需 $
。
我们可以将其重塑为宽格式以获得如下内容:
ID a b c
1: 1 a b <NA>
2: 2 <NA> b c
接下来的两行只是将值转换为整数,
将 NA
设置为 0,否则设置为 1。
> unique_wide
ID a b c
1: 1 1 1 0
2: 2 0 1 1
之后应用ID
完成的,
因此它在 DT
和 unique_wide
之间匹配的所有行都将非 ID
列的值更新为 unique_wide
中存在的值。
另请注意,with = FALSE
对基于包含字符向量的变量的 select 列很有用。
顺便说一句,请注意,您甚至不需要最初的 0 个非 ID
列,
如果您将 table 声明为
DT <- data.table::data.table(ID=c("1","1","1","2","2"),
CLASS=c("a","a","b","c","b"))
上面的代码仍然有效。
使用 dcast
和 merge
你还可以:
DT <- data.table::data.table(ID=c("1","1","1","2","2"),
CLASS=c("a","a","b","c","b"),
a=c(0,0,0,0,0),
b=c(0,0,0,0,0),
c=c(0,0,0,0,0))
# dcast to convert to wide
DT_dcast <- dcast(DT[, .(ID, CLASS)], ID ~ CLASS, fun.aggregate = function(x) length(unique(x)), value.var = "CLASS")
DT_dcast
ID a b c
1: 1 1 1 0
2: 2 0 1 1
# Then merge with the original data.table
DT_m <- merge(DT[, .(ID, CLASS)], DT_dcast, by = "ID")
DT_m
ID CLASS a b c
1: 1 a 1 1 0
2: 1 a 1 1 0
3: 1 b 1 1 0
4: 2 c 0 1 1
5: 2 b 0 1 1
编辑
您仍然可以对 dcast
和 merge
.
我从你的 'start with this' 数据中注意到第 2 行有不同的 RX 日期,从 'want this' 数据中你只为此保留了 '1-1-99'。
DT2 <- data.table::data.table(ID=c("1","1","1","2","2"),
CLASS=c("a","a","b","c","b"),
a=c(0,0,0,0,0),
b=c(0,0,0,0,0),
c=c(0,0,0,0,0),
RXDate = c("1-1-99", "1-2-99", "1-3-99", "5-4-00", "6-5-01"))
# 2nd row from the data provided has different RXDate under same ID and Class.
# Use x[1] to pick first
DT_dcast <- dcast(DT2[, .(ID, CLASS, RXDate)], ID ~ CLASS,
fun.aggregate = function(x) x[1],
value.var = c("CLASS", "RXDate"))
DT_dcast
ID CLASS.1_a CLASS.1_b CLASS.1_c RXDate_a RXDate_b RXDate_c
1: 1 a b <NA> 1-1-99 1-3-99 <NA>
2: 2 <NA> b c <NA> 6-5-01 5-4-00
# Convert 1 or 0 under CLASS
class_cols <- names(DT_dcast)[grepl("CLASS", names(DT_dcast))]
for (col in class_cols) set(DT_dcast, j = col, value = ifelse(is.na(DT_dcast[[col]]), 0, 1))
DT_dcast
ID CLASS.1_a CLASS.1_b CLASS.1_c RXDate_a RXDate_b RXDate_c
1: 1 1 1 0 1-1-99 1-3-99 <NA>
2: 2 0 1 1 <NA> 6-5-01 5-4-00
# Then merge with the original data.table
DT_m <- merge(DT2[, .(ID, CLASS, RXDate)], DT_dcast, by = "ID")
DT_m
ID CLASS RXDate CLASS.1_a CLASS.1_b CLASS.1_c RXDate_a RXDate_b RXDate_c
1: 1 a 1-1-99 1 1 0 1-1-99 1-3-99 <NA>
2: 1 a 1-2-99 1 1 0 1-1-99 1-3-99 <NA>
3: 1 b 1-3-99 1 1 0 1-1-99 1-3-99 <NA>
4: 2 c 5-4-00 0 1 1 <NA> 6-5-01 5-4-00
5: 2 b 6-5-01 0 1 1 <NA> 6-5-01 5-4-00
如果要重命名列,可以使用 setnames
另一种可能的方法:
idx <- DT3[, CJ(I=.I, J=match(unique(CLASS), names(DT))), by=ID]
setDF(DT3)
DT3[as.matrix(idx[, .(I, J)])] <- 1L
setDT(DT3)[]