2016-01-11 4 views
1

Я часто есть таблицы, где одна ячейка может содержать несколько значений (разделить на некоторый характер сепаратора), и мне нужно разделить такие записи, например:сплит столбец в data.table к нескольким строкам

dt1 <- fread("V1 V2 V3 
      x b;c;d 1 
      y d;ef 2 
      z d;ef 3") 

должны дать что-то вроде этого:

# V1 V2 V3 
# 1: x b 1 
# 2: x c 1 
# 3: x d 1 
# 4: y d 2 
# 5: y ef 2 
# 6: z d 3 
# 7: z ef 3 

до сих пор я сделал следующие функции:

# I omit all error-checking code here and assume that 
# dtInput is a valid data.table and 
# col2split is a name of existing column 
splitcol2rows <- function(dtInput, col2split, sep){ 
    ori.names <- names(dtInput); # save original order of columns 
    ori.keys <- key(dtInput); # save original keys 

    # create new table with 2 columns: 
    # one is original "un-splitted" column (will be later used as a key) 
    # and second one is result of strsplit: 
    dt.split <- dtInput[, 
        .(tmp.add.col=rep(unlist(strsplit(get(col2split),sep,T)), .N)), 
        by=col2split] 
    dt.split <- unique(dt.split, by=NULL); 

    # now use that column as a key: 
    setkeyv(dt.split, col2split) 
    setkeyv(dtInput, col2split) 
    dtInput <- dt.split[dtInput, allow.cartesian=TRUE]; 

    # leave only 'splitted' column 
    dtInput[, c(col2split):=NULL]; 
    setnames(dtInput, 'tmp.add.col', col2split); 

    # restore original columns order and keys 
    setcolorder(dtInput, ori.names); 
    setkeyv(dtInput, ori.keys); 

    return(dtInput); 
} 

я t работает нормально (проверьте пример вывода как splitcol2rows(dt1, 'V2', ';')[]), но я уверен, что это решение далека от оптимального и было бы благодарно за любые советы. Например, я просмотрел решение, предложенное Мэттом в ответе на вопрос «Applying a function to each row of a data.table», и мне нравится, что он управляет без создания промежуточной таблицы (мой dt.split), но в моем случае мне нужно сохранить все остальные столбцы и не делать посмотрите, как это сделать в противном случае.


UPD. Во-первых, глядя из раствора, предложенного @RichardScriven, я пришел к переписыванию свою функцию так, она стала намного короче и легче читать:

splitcol2rows_mget <- function(dtInput, col2split, sep){ 
    dtInput <- dtInput[, .(tmp.add.col = unlist(strsplit(get(col2split),sep,T))), by=names(dtInput)] 

    dtInput[, c(col2split):=NULL]; 
    setnames(dtInput, 'tmp.add.col', col2split); 
    return(dtInput); 
} 

Он все еще имеет некоторые уродливые куски, как промежуточный «tmp.add. col ", который может вызвать конфликт, если такие столбцы уже существуют в исходной таблице. Кроме того, это более короткое решение оказалось медленнее, чем мой первый код. И оба они медленнее, чем cSplit() из splitstackshape пакета:

require('microbenchmark') 
require('splitstackshape') 

splitMy1 <- function(input){return(splitcol2rows(input, col2split = 'V2', sep = ';'))} 
splitMy2 <- function(input){return(splitcol2rows_mget(input, col2split = 'V2', sep = ';'))} 
splitSH <- function(input){return(cSplit(input, splitCols = 'V2', sep = ';', direction = 'long'))} 

# Smaller table, 100 repeats: 
set.seed(1) 
num.rows <- 1e4; 
dt1 <- data.table(V1=seq_len(num.rows), 
        V2=replicate(num.rows,paste0(sample(letters, runif(1,1,6), T), collapse = ";")), 
        V3=rnorm(num.rows)) 
print(microbenchmark(splitMy1(dt1), splitMy2(dt1), splitSH(dt1), times=100L)) 
#Unit: milliseconds 
#   expr  min  lq  mean median  uq  max neval 
# splitMy1(dt1) 56.34475 58.53842 68.11128 62.51419 79.79727 98.96797 100 
# splitMy2(dt1) 61.84215 64.59619 76.41503 69.02970 88.49229 132.43679 100 
# splitSH(dt1) 31.29671 33.14389 38.28108 34.91696 39.31291 83.58625 100  

# Bigger table, 1 repeat: 
set.seed(1) 
num.rows <- 5e5; 
dt1 <- data.table(V1=seq_len(num.rows), 
        V2=replicate(num.rows,paste0(sample(letters, runif(1,1,6), T), collapse = ";")), 
        V3=rnorm(num.rows)) 
print(microbenchmark(splitMy1(dt1), splitMy2(dt1), splitSH(dt1), times=1L)) 

#Unit: seconds 
#   expr  min  lq  mean median  uq  max neval 
# splitMy1(dt1) 2.955825 2.955825 2.955825 2.955825 2.955825 2.955825  1 
# splitMy2(dt1) 3.693612 3.693612 3.693612 3.693612 3.693612 3.693612  1 
# splitSH(dt1) 1.990201 1.990201 1.990201 1.990201 1.990201 1.990201  1 
+3

Ответ на [вопрос я спросил] (http://stackoverflow.com/questions/33288695/how-to-use-tidyrseparate-when-the-number-of-needed-variables-is- неизвестно) некоторое время назад может дать вам ответ, который вы ищете. – brittenb

+0

@brittenb, спасибо большое, 'cSplit()' действительно делает работу! возможно, я просто сделаю небольшой бенчмаркинг, чтобы проверить, не слишком ли быстрый код для больших таблиц) –

+0

Ну, вы можете получить все столбцы с помощью 'dt1 [, c (. (V2 = scan (text = V2, sep ="; " , what = "")), mget (names (dt1) [- (1: 2)])), by = V1] ', но в этот момент я бы просто пошел с' cSplit() ';-) –

ответ

3

Там есть функция в пакете splitstackshape называется cSplit, который идеально подходит для этой задачи. Просто передайте «;» как разделитель и «длинный», как направление, чтобы получить то, что нам нужно.

> library(splitstackshape) 
> dat <- data.frame(V1 = c("x", "y", "z"), V2 = c("b;c;d", "d;ef", "d;ef"), V3 = 1:3, stringsAsFactors = FALSE) 
> cSplit(dat, "V2", sep = ";", direction = "long") 
# V1 V2 V3 
# 1: x b 1 
# 2: x c 1 
# 3: x d 1 
# 4: y d 2 
# 5: y ef 2 
# 6: z d 3 
# 7: z ef 3 
Смежные вопросы