Эффективно создавать фрейм данных из строк, содержащих пары ключ-значение
Я хотел бы попросить вас дать рекомендации по эффективности для конкретной проблемы с кодированием в R. У меня есть вектор строки в следующем стиле:
[1] "HGVSc=ENST00000495576.1:n.820-1G>A;INTRON=1/1;CANONICAL=YES"
[2] "DISTANCE=2179"
[3] "HGVSc=ENST00000466430.1:n.911C>T;EXON=4/4;CANONICAL=YES"
[4] "DISTANCE=27;CANONICAL=YES;common"
В каждом элементе вектора отдельные записи разделяются символом ;
, а MOST для одиночных записей имеет формат KEY=VALUE
. Однако есть также некоторые записи, которые имеют только формат KEY
(см. "Общий" в [4]). В этом примере есть 15 разных ключей, и не каждый ключ появляется в каждом элементе вектора. 15 различных клавиш:
names <- c('ENSP','HGVS','DOMAINS','EXON','INTRON', 'HGVSp', 'HGVSc','CANONICAL','GMAF','DISTANCE', 'HGNC', 'CCDS', 'SIFT', 'PolyPhen', 'common')
Из этого вектора я хотел бы создать dataframe, который выглядит так:
ENSP HGVS DOMAINS EXON INTRON HGVSp HGVSc CANONICAL
1 - - - - 1/1 - ENST00000495576.1:n.820-1G>A YES
2 - - - - - - - -
3 - - - 4/4 - - ENST00000466430.1:n.911C>T YES
4 - - - - - - - YES
GMAF DISTANCE HGNC CCDS SIFT PolyPhen common
1 - - - - - - -
2 - 2179 - - - - -
3 - - - - - - -
4 - 27 - - - - YES
Я написал эту функцию для решения проблемы:
unlist.info <- function(names, column){
info.mat <- matrix(rep('-', length(column)*length(names)), nrow=length(column), ncol=length(names), dimnames=list(c(), names))
info.mat <- as.data.frame(info.mat, stringsAsFactors=F)
for (i in 1:length(column)){
info <- unlist(strsplit(column[i], "\\;"))
for (e in info){
e <- unlist(strsplit(e, "\\="))
j <- which(names == e[1])
if (length(e) > 1){
# KEY=VALUE. The value might contain a = as well
value <- paste(e[2:length(e)], collapse='=')
info.mat[i,j] <- value
}else{
# only KEY
info.mat[i,j] <- 'YES'
}
}
}
return(info.mat)
}
И затем я вызываю:
mat <- unlist.info(names, vector)
Несмотря на то, что это работает, это очень медленно. Также я обрабатываю векторы с более чем 100 000 записей. Теперь я понимаю, что петля неэффективна и неэффективна в R, и я знаком с концепцией применения функций к кадрам данных. Однако, поскольку каждая запись вектора содержит другое подмножество записей KEY=VALUE
или KEY
, я не мог придумать более эффективную функцию.
Ответы
Ответ 1
Здесь вы идете:
Восстановить данные:
x <- c(
"HGVSc=ENST00000495576.1:n.820-1G>A;INTRON=1//1;CANONICAL=YES",
"DISTANCE=2179",
"HGVSc=ENST00000466430.1:n.911C>T;EXON=4//4;CANONICAL=YES",
"DISTANCE=27;CANONICAL=YES;common"
)
Создайте именованный вектор с вашими желаемыми именами. Это используется для быстрого поиска позже:
names <- setNames(1:15, c('ENSP','HGVS','DOMAINS','EXON','INTRON', 'HGVSp', 'HGVSc','CANONICAL','GMAF','DISTANCE', 'HGNC', 'CCDS', 'SIFT', 'PolyPhen', 'common'))
Создайте вспомогательную функцию, которая присваивает каждой переменной правильное положение в матрице. Затем используйте lapply
и strsplit
:
assign <- function(x, names){
xx <- sapply(x, function(i)if(length(i)==2L) i else c(i, "YES"))
z <- rep(NA, length(names))
z[names[xx[1, ]]] <- xx[2, ]
z
}
sx <- lapply(strsplit(x, ";"), strsplit, "=")
ret <- t(sapply(sx, assign, names))
colnames(ret) <- names(names)
ret
Результаты:
ENSP HGVS DOMAINS EXON INTRON HGVSp HGVSc CANONICAL GMAF DISTANCE HGNC
[1,] NA NA NA NA "1//1" NA "ENST00000495576.1:n.820-1G>A" "YES" NA NA NA
[2,] NA NA NA NA NA NA NA NA NA "2179" NA
[3,] NA NA NA "4//4" NA NA "ENST00000466430.1:n.911C>T" "YES" NA NA NA
[4,] NA NA NA NA NA NA NA "YES" NA "27" NA
CCDS SIFT PolyPhen common
[1,] NA NA NA NA
[2,] NA NA NA NA
[3,] NA NA NA NA
[4,] NA NA NA "YES"
Ответ 2
Здесь другое, более быстрое решение, использующее исходные пары...
## test elapsed replications relative average
## 2 thell_solution(x) 0.37 1000 1.000 0.00037
## 3 andrie_solution(x) 1.04 1000 2.811 0.00104
## 1 original_solution(x) 2.61 1000 7.054 0.00261
Так как спаривание [1] всегда получает назначенное спаривание [2], за исключением последнего bool (... не то, что я понимаю, почему этот один флаг обрабатывается по-разному в исходном векторе строки...), мы можем воспользоваться последовательностью и фактом что вектор присваивает NA, когда имя задается без значения (т.е.: x [5] == NA), и нам также не нужно много раз называть имена. И поскольку strsplit использует регулярное выражение, мы можем сделать чередование.
# Let `x` be as @Andrie made it in his answer. Let `names` be as you had
# in the original question.
# A pre-built dummy record and empty list.
na.record <- setNames(rep(NA, time = length(names)), names)
y <- list()
do.call(rbind, lapply(strsplit(x, "(;|=)"), FUN = function(x) {
x_seq <- seq.int(to = length(x), by = 2)
y[x[x_seq]] <- x[x_seq + 1]
y[is.na(y)] <- "YES"
na.record[x[x_seq]] <- y
na.record
}))
## ENSP HGVS DOMAINS EXON INTRON HGVSp HGVSc
## [1,] NA NA NA NA "1//1" NA "ENST00000495576.1:n.820-1G>A"
## [2,] NA NA NA NA NA NA NA
## [3,] NA NA NA "4//4" NA NA "ENST00000466430.1:n.911C>T"
## [4,] NA NA NA NA NA NA NA
## CANONICAL GMAF DISTANCE HGNC CCDS SIFT PolyPhen common
## [1,] "YES" NA NA NA NA NA NA NA
## [2,] NA NA "2179" NA NA NA NA NA
## [3,] "YES" NA NA NA NA NA NA NA
## [4,] "YES" NA "27" NA NA NA NA "YES"