2015-10-13 10 views
7

Я работаю над данными HCUP, и это имеет диапазон значений в одном столбце, который необходимо разбить на несколько столбцов. Ниже фрейм данных HCUP для справки:Разделение одного столбца на несколько наблюдений с использованием R

code   label 
61000-61003  excision of CNS 
0169T-0169T  ventricular shunt 

Желательный результат должен быть:

code   label 
61000   excision of CNS 
61001   excision of CNS 
61002   excision of CNS 
61003   excision of CNS 
0169T   ventricular shunt 

Мой подход к этой проблеме с использованием пакета splitstackshape и используя этот код

library(data.table) 
library(splitstackshape) 

cSplit(hcup, "code", "-")[, list(code = code_1:code_2, by = label)] 

Этот подход приводит к проблемам с памятью. Есть ли лучший подход к этой проблеме?

Некоторые комментарии:

  • данные имеет много писем кроме "Т".
  • Буква может быть либо спереди, либо в самом конце, но не между двумя номерами.
  • Там нет никаких изменений письма от «Т» к «U» в одном диапазоне
+0

Хммм Я не очень разбираюсь в data.table, но я не вижу, как ваш экзамен может работать - 'Code_1' (не должно быть' code_1'?), А 'code_2' должен быть числовым, если вы хотите построить последовательность, например 'hcup <- read.table (header = T, strAsAsFactors = F, text =" метка кода \ n61000-61003 excision_of_CNS \ n0169T-0169T ventricular_shunt "); cSplit (hcup, "code", "-") [, list (Code = seq (as.integer (gsub ("\\ D", "", code_1)), as.integer (gsub ("\\ D" , "", code_2)))), by = label] '. – lukeA

+0

Спасибо. Я принял изменения. Я не особо отношусь к «splitstackshape» как таковой. Есть ли возможность написать функцию, которая может справиться с этой проблемой? – x1carbon

+0

Это может быть полезно из документации 'splitstackshape': если вы знаете, что все значения в столбце будут иметь одинаковое количество значений для каждой строки после разделения, вместо этого вы должны использовать функцию' cSplit_f', которая использует 'fread' вместо 'strsplit' и, как правило, быстрее. –

ответ

7

Вот решение, использующее dplyr и all.is.numeric из Hmisc:

library(dplyr) 
library(Hmisc) 
library(tidyr) 
dat %>% separate(code, into=c("code1", "code2")) %>% 
     rowwise %>% 
     mutate(lists = ifelse(all.is.numeric(c(code1, code2)), 
         list(as.character(seq(from = as.numeric(code1), to = as.numeric(code2)))), 
         list(code1))) %>% 
     unnest(lists) %>% 
     select(code = lists, label) 

Source: local data frame [5 x 2] 

    code    label 
    (chr)   (fctr) 
1 61000 excision of CNS 
2 61001 excision of CNS 
3 61002 excision of CNS 
4 61003 excision of CNS 
5 0169T ventricular shunt 

правку исправить диапазоны с символьные значения. Обрушивает простоту немного:

dff %>% mutate(row = row_number()) %>% 
     separate(code, into=c("code1", "code2")) %>% 
     group_by(row) %>% 
     summarise(lists = if(all.is.numeric(c(code1, code2))) 
           {list(str_pad(as.character(
            seq(from = as.numeric(code1), to = as.numeric(code2))), 
             nchar(code1), pad="0"))} 
          else if(grepl("^[0-9]", code1)) 
           {list(str_pad(paste0(as.character(
            seq(from = extract_numeric(code1), to = extract_numeric(code2))), 
             strsplit(code1, "[0-9]+")[[1]][2]), 
             nchar(code1), pad = "0"))} 
          else 
           {list(paste0(
             strsplit(code1, "[0-9]+")[[1]], 
             str_pad(as.character(
            seq(from = extract_numeric(code1), to = extract_numeric(code2))), 
             nchar(gsub("[^0-9]", "", code1)), pad="0")))}, 
        label = first(label)) %>% 
     unnest(lists) %>% 
     select(-row) 
Source: local data frame [15 x 2] 

       label lists 
       (chr) (chr) 
1 excision of CNS 61000 
2 excision of CNS 61001 
3 excision of CNS 61002 
4 ventricular shunt 0169T 
5 ventricular shunt 0170T 
6 ventricular shunt 0171T 
7 excision of CNS 01000 
8 excision of CNS 01001 
9 excision of CNS 01002 
10 some procedure A2543 
11 some procedure A2544 
12 some procedure A2545 
13 some procedure A0543 
14 some procedure A0544 
15 some procedure A0545 

данные:

dff <- structure(list(code = c("61000-61002", "0169T-0171T", "01000-01002", 
"A2543-A2545", "A0543-A0545"), label = c("excision of CNS", "ventricular shunt", 
"excision of CNS", "some procedure", "some procedure")), .Names = c("code", 
"label"), row.names = c(NA, 5L), class = "data.frame") 
+0

Это выглядит хорошо. Но в конечном выпуске он пропускает коды, такие как «0169T». – x1carbon

+0

Это решение очень близко, но все еще пропускает тот код, где письмо на первом месте.Например, код «A4245» не добавляется в конечную базу данных. – x1carbon

+0

см. Редактирование, исправлено. – jeremycg

1

Менее элегантный способ сделать это:

# the data 
hcup <- data.frame(code=c("61000-61003", "0169T-0169T"), 
        label=c("excision of CNS", "ventricular shunt"), stringsAsFactors = F) 
hcup 
>   code    label 
>1 61000-61003 excision of CNS 
>2 0169T-0169T ventricular shunt 

# reshaping 
# split the code ranges into separate columns 
seq.ends <- cbind(do.call(rbind.data.frame, strsplit(hcup$code, "-")), hcup$label) 
# create a list with a data.frame for each original line 
new.list <- apply(seq.ends, 1, FUN=function(x){data.frame(code=if(grepl("\\d{5}", x[1])){ 
        z<-x[1]:x[2]}else{z<-x[1]}, label=rep(x[3], length(z)), 
        stringsAsFactors = F)}) 
# collapse the list into a df 
new.df <- do.call(rbind, lapply(new.list, data.frame, stringsAsFactors=F)) 

new.df 
>  code    label 
>1.1 61000 excision of CNS 
>1.2 61001 excision of CNS 
>1.3 61002 excision of CNS 
>1.4 61003 excision of CNS 
>2 0169T ventricular shunt 
6

Оригинал Ответ: Смотрите ниже обновления.

Во-первых, я сделал ваши данные примера более сложными, добавив первую строку в нижнюю.

dff <- structure(list(code = c("61000-61003", "0169T-0169T", "61000-61003" 
), label = c("excision of CNS", "ventricular shunt", "excision of CNS" 
)), .Names = c("code", "label"), row.names = c(NA, 3L), class = "data.frame") 

dff 
#   code    label 
# 1 61000-61003 excision of CNS 
# 2 0169T-0169T ventricular shunt 
# 3 61000-61003 excision of CNS 

Мы можем использовать оператор последовательности :, чтобы получить последовательности для code колонны, обертывание tryCatch() таким образом мы можем избежать ошибки на, и сохранить значения, которые не могут быть виртуализированных. Сначала разделим значения на метку -, затем запустите ее через lapply().

xx <- lapply(
    strsplit(dff$code, "-", fixed = TRUE), 
    function(x) tryCatch(x[1]:x[2], warning = function(w) x) 
) 
data.frame(code = unlist(xx), label = rep(dff$label, lengths(xx))) 
#  code    label 
# 1 61000 excision of CNS 
# 2 61001 excision of CNS 
# 3 61002 excision of CNS 
# 4 61003 excision of CNS 
# 5 0169T ventricular shunt 
# 6 0169T ventricular shunt 
# 7 61000 excision of CNS 
# 8 61001 excision of CNS 
# 9 61002 excision of CNS 
# 10 61003 excision of CNS 

Мы пытаемся применить оператор последовательности : к каждому элементу из strsplit(), и если принимать x[1]:x[2] не представляется возможным, то это возвращает только значения для этих элементов и протекает с последовательностью x[1]:x[2] иначе. Затем мы просто повторяем значения столбца label на основе результирующих длин в xx, чтобы получить новый столбец label.


Update: Вот что я придумал в ответ на ваши изменения. Заменить xx выше

xx <- lapply(strsplit(dff$code, "-", TRUE), function(x) { 
    s <- stringi::stri_locate_first_regex(x, "[A-Z]") 
    nc <- nchar(x)[1L] 
    fmt <- function(n) paste0("%0", n, "d") 
    if(!all(is.na(s))) { 
     ss <- s[1,1] 
     fmt <- fmt(nc-1) 
     if(ss == 1L) { 
      xx <- substr(x, 2, nc) 
      paste0(substr(x, 1, 1), sprintf(fmt, xx[1]:xx[2])) 
     } else { 
      xx <- substr(x, 1, ss-1) 
      paste0(sprintf(fmt, xx[1]:xx[2]), substr(x, nc, nc)) 
     } 
    } else { 
     sprintf(fmt(nc), x[1]:x[2]) 
    } 
}) 

Да, это сложно. Теперь, если мы возьмем следующий кадр df2 данных в качестве тестового примера

df2 <- structure(list(code = c("61000-61003", "0169T-0174T", "61000-61003", 
"T0169-T0174"), label = c("excision of CNS", "ventricular shunt", 
"excision of CNS", "ventricular shunt")), .Names = c("code", 
"label"), row.names = c(NA, 4L), class = "data.frame") 

и запустить xx код сверху на него, мы можем получить следующий результат.

data.frame(code = unlist(xx), label = rep(df2$label, lengths(xx))) 
#  code    label 
# 1 61000 excision of CNS 
# 2 61001 excision of CNS 
# 3 61002 excision of CNS 
# 4 61003 excision of CNS 
# 5 0169T ventricular shunt 
# 6 0170T ventricular shunt 
# 7 0171T ventricular shunt 
# 8 0172T ventricular shunt 
# 9 0173T ventricular shunt 
# 10 0174T ventricular shunt 
# 11 61000 excision of CNS 
# 12 61001 excision of CNS 
# 13 61002 excision of CNS 
# 14 61003 excision of CNS 
# 15 T0169 ventricular shunt 
# 16 T0170 ventricular shunt 
# 17 T0171 ventricular shunt 
# 18 T0172 ventricular shunt 
# 19 T0173 ventricular shunt 
# 20 T0174 ventricular shunt 
+0

Это отлично работает. Но входные данные имеют такие коды, как «0005T-0006T». В этом случае только 0005T помещается в конечный результат, но код 0006T отсутствует. – x1carbon

+0

Приносим извинения, набор данных был большим, и я пропустил его. Да, я хотел бы иметь оба кода в конечном выпуске. – x1carbon

+0

Да, @jeremycg прав. – x1carbon

3

Создать правило секвенирования для таких кодов:

seq_code <- function(from,to){ 

    ext = function(x, part) gsub("([^0-9]?)([0-9]*)([^0-9]?)", paste0("\\",part), x) 

    pre = unique(sapply(list(from,to), ext, part = 1)) 
    suf = unique(sapply(list(from,to), ext, part = 3)) 

    if (length(pre) > 1 | length(suf) > 1){ 
     return("NO!") 
    } 

    num = do.call(seq, lapply(list(from,to), function(x) as.integer(ext(x, part = 2)))) 
    len = nchar(from)-nchar(pre)-nchar(suf) 

    paste0(pre, sprintf(paste0("%0",len,"d"), num), suf) 

} 

С @ jeremycg пример М.:

setDT(dff)[,.(
    label = label[1], 
    code = do.call(seq_code, tstrsplit(code,'-')) 
), by=.(row=seq(nrow(dff)))] 

, который дает

row    label code 
1: 1 excision of CNS 61000 
2: 1 excision of CNS 61001 
3: 1 excision of CNS 61002 
4: 2 ventricular shunt 0169T 
5: 2 ventricular shunt 0170T 
6: 2 ventricular shunt 0171T 
7: 3 excision of CNS 01000 
8: 3 excision of CNS 01001 
9: 3 excision of CNS 01002 
10: 4 some procedure A2543 
11: 4 some procedure A2544 
12: 4 some procedure A2545 
13: 5 some procedure A0543 
14: 5 some procedure A0544 
15: 5 some procedure A0545 

данные, скопированные из @ jeremycg отвечают:

dff <- structure(list(code = c("61000-61002", "0169T-0171T", "01000-01002", 
"A2543-A2545", "A0543-A0545"), label = c("excision of CNS", "ventricular shunt", 
"excision of CNS", "some procedure", "some procedure")), .Names = c("code", 
"label"), row.names = c(NA, 5L), class = "data.frame") 
3

Если вы достаточно терпеливы, вы, вероятно, разобрать строки на отдельные куски вместо Eval/синтаксического анализа трюк, увы, я не так:

fancy.seq = function(x) eval(parse(text=sub(', \\)', ')', sub('\\(, ', '(', 
       sub('.*?([0-9]+)(.*)-(.*?)([1-9][0-9]*).*', 
        'paste0("\\3", 
          formatC(\\1:\\4, width=log10(\\4)+1, format="d", flag="0"), 
          "\\2")', 
        x))))) 
# using example from jeremycg's answer 
dt[, .(fancy.seq(code), label), by = 1:nrow(dt)] 
# nrow V1    label 
# 1: 1 61000 excision of CNS 
# 2: 1 61001 excision of CNS 
# 3: 1 61002 excision of CNS 
# 4: 2 0169T ventricular shunt 
# 5: 2 0170T ventricular shunt 
# 6: 2 0171T ventricular shunt 
# 7: 3 01000 excision of CNS 
# 8: 3 01001 excision of CNS 
# 9: 3 01002 excision of CNS 
#10: 4 A2543 some procedure 
#11: 4 A2544 some procedure 
#12: 4 A2545 some procedure 
#13: 5 A0543 some procedure 
#14: 5 A0544 some procedure 
#15: 5 A0545 some procedure 

Если неясно, что делает вышеизложенное, просто запустите команды sub по одному в одной из строк кода.

+1

'\\ 2: \\ 4' блестящий! – Arun

+1

@Frank fixed;) – eddi