2016-01-13 2 views
4

У меня есть этот стиль вектор символов:Эффективное расщепление вектора символов

vec <- c("id a; sex m; age 16; type 1;","id a; sex m; age 16;","id a; sex m; age 16; type 3") 

Каждый элемент vec является «» отделенный список атрибутов, где каждый атрибут имеет формат «значение ключа» (т.е. ";" символ может отображаться только как разделитель).

Таким образом, первый список атрибутов: ID = а SEX = т возраст = 16 тип = 1

Обратите внимание, что различные элементы в vec может иметь несколько различных атрибутов.

Я ищу эффективный способ разделить vec на список списков. Каждый элемент внешнего списка представляет собой список всех значений атрибутов, в которых именами элементов являются ключи атрибутов. Это означает, что длина внешнего списка будет длиной элементов vec, а длина каждого внутреннего списка будет равна длине атрибутов.

Я в настоящее время есть такая реализация, которая поможет понять вывод мне нужно:

attributes.list <- sapply(vec, function(x) strsplit(x, split = "(\\;)(\\s+)?", perl = TRUE)[[1]]) 
attributes.lol <- lapply(attributes.list, function(x) { 
    attribute.mat <- sapply(x, function(y) strsplit(y, split = " ")[[1]]) 
    colnames(attribute.mat) <- NULL 
    attribute.list <- as.list(attribute.mat[2,]) 
    names(attribute.list) <- attribute.mat[1,] 
    return(attribute.list) 
}) 

> attributes.lol[[1]] 
$id 
[1] "a" 

$sex 
[1] "m" 

$age 
[1] "16" 

$type 
[1] "1" 

Длина vec в действительности очень долго (~ млн элементов), поэтому мне было интересно, если есть более эффективный способ для достижения этой цели.

+2

Может ли значение когда-либо * содержат * ';' (например, в качестве части строки или спасшийся)? –

+1

Какая часть кода является узким местом? –

+0

Ответ на вопрос Конрада Рудольфа - нет. «;» может отображаться только как разделитель. – user1701545

ответ

3

В следующей таблице используется только база R. Добавляйте точку с запятой на каждую запись, разделяйте записи в точке с запятой, удаляйте пробелы в начале и конце, заменяете пробел и пробел и читайте с использованием read.dcf. Это дает матрицу m, которую мы конвертируем в кадр данных и используем type.convert для получения правильных типов. (Если матрица достаточно затем опустить вторую линию.)

m <- read.dcf(textConnection(sub(" ",": ",trimws(unlist(strsplit(paste0(vec, ";"),";")))))) 
as.data.frame(lapply(as.data.frame(m, stringsAsFactors = FALSE), type.convert)) 

дает:

id sex age type 
1 a m 16 1 
2 a m 16 NA 
3 a m 16 3 
1

Даже это не принесет вам тот же результат, вы можете попробовать подставляя «;» следующим образом:

require(data.table) 
l <- lapply(vec, function(x){ 
    fread(gsub(";", "\n", x)) 
}) 

, который дает вам список, который затем можно объединить с помощью

rbindlist(l, idcol = TRUE) 

Это приводит к:

.id id a 
1: 1 sex m 
2: 1 age 16 
3: 1 type 1 
4: 2 sex m 
5: 2 age 16 
6: 3 sex m 
7: 3 age 16 
8: 3 type 3 
+0

Вызов 'fread' миллион раз не является обязательно будет быстро, не так ли? – A5C1D2H2I1M1N2O1R2T1

2

Вы можете попробовать этот подход, который в соответствии с предложения @alexis_laz:

Установка:

vec <- c("id a; sex m; age 16; type 1;","id a; sex m; age 16;","id a; sex m; age 16; type 3") 

v <- rep(vec,1e5) 

Код:

z <- strsplit(v, split = "(\\;)(\\s+)?", perl = TRUE) 

out <- lapply(z,function(s) {v <- unlist(strsplit(s,' ')); setNames(as.list(v[c(F,T)]),v[c(T,F)]) }) 
4

Я хотел бы предложить сочетание "iotools" и «данные.стол», что-то вдоль линий этого:

library(iotools) 
library(data.table) 
melt(data.table(ind = seq_along(vec), trimws(mstrsplit(vec, ";"))), 
    "ind", na.rm = TRUE)[ 
     , c("key", "val") := tstrsplit(value, " ", TRUE)][ 
     , c("variable", "value") := NULL][] 

Или, если вы хотите "широкий" форму (как @ ответ GGrothendieck в):

dcast(
    melt(data.table(ind = seq_along(vec), trimws(mstrsplit(vec, ";"))), 
     "ind", na.rm = TRUE)[ 
     , c("key", "val") := tstrsplit(value, " ", TRUE)][ 
      , c("variable", "value") := NULL][], ind ~ key, value.var = "val") 

Я предлагаю выше, потому что вы Обратите внимание на то, что вы хотите эффективный подход. Сравните следующие:

Пример данных длиной 3, приблизительно 100000 и приблизительно 1 млн.

vec <- c("id a; sex m; age 16; type 1;","id a; sex m; age 16;","id a; sex m; age 16; type 3") 
v100k <- rep(vec, ceiling(100000/length(vec))) 
v1M <- rep(vec, ceiling(1000000/length(vec))) 

подходы мы хотим проверить:

library(iotools) 
library(data.table) 

funAM_l <- function(invec) { 
    melt(data.table(ind = seq_along(invec), trimws(mstrsplit(invec, ";"))), "ind", na.rm = TRUE)[ 
    , c("key", "val") := tstrsplit(value, " ", TRUE)][ 
     , c("variable", "value") := NULL][] 
} 

funAM_w <- function(invec) dcast(funAM_l(invec), ind ~ key, value.var = "val") 

funMT <- function(v) { 
    z <- strsplit(v, split = "(\\;)(\\s+)?", perl = TRUE) 
    lapply(z,function(s) {v <- unlist(strsplit(s,' ')); setNames(as.list(v[c(F,T)]),v[c(T,F)]) }) 
} 

funF <- function(invec) rbindlist(lapply(invec, function(x) { fread(gsub(";", "\n", x)) }), idcol = TRUE) 

funGG <- function(invec) read.dcf(textConnection(sub(" ",": ",trimws(unlist(strsplit(paste0(invec, ";"),";")))))) 

Мое предложение не собирается выигрывать гонки с небольшим вектором:

library(microbenchmark) 
microbenchmark(funAM_l(vec), funAM_w(vec), funF(vec), funGG(vec), funMT(vec)) 
# Unit: microseconds 
#   expr  min  lq  mean median  uq  max neval 
# funAM_l(vec) 1474.163 1525.3765 1614.28414 1573.6325 1601.3815 2828.481 100 
# funAM_w(vec) 3293.376 3482.9510 3741.30381 3553.7240 3714.1730 6787.863 100 
#  funF(vec) 690.761 729.4900 830.61645 756.4610 777.6725 4083.904 100 
# funGG(vec) 182.281 209.8405 220.46376 220.8055 232.1820 280.788 100 
# funMT(vec) 57.288 76.5225 84.81496 83.2755 90.3120 166.352 100 

Но посмотрите на то, что происходит, когда мы масштабироваться до векторы:

system.time(funAM_l(v100k)) 
# user system elapsed 
# 0.24 0.00 0.24 
system.time(funAM_w(v100k)) 
# user system elapsed 
# 0.296 0.000 0.296 
system.time(funMT(v100k)) 
# user system elapsed 
# 1.768 0.000 1.768 
system.time(funF(v100k)) 
# user system elapsed 
# 21.960 0.136 22.068 
system.time(funGG(v100k)) 
# user system elapsed 
# 30.968 0.004 30.940 

Вот как это делается на векторе длиной 1 миллион.

system.time(funAM_w(v1M)) 
# user system elapsed 
# 4.316 0.092 4.402 

Мое другое предложение было будет смотреть на cSplit из моего пакета «splitstackshape». Это немного лучше, чем подход Марата.

Здесь на 1 миллион значений:

library(splitstackshape) 
system.time(dcast(
    cSplit(cSplit(data.table(ind = seq_along(v1M), v1M), "v1M", ";", "long"), "v1M", " "), 
    ind ~ v1M_1, value.var = "v1M_2")) 
# user system elapsed 
# 13.744 0.156 13.882