лучший способ манипулировать строками в больших данных. таблица

У меня есть строка data.table размером 67 мм с именами и фамилиями людей, разделенными пробелами. Мне просто нужно создать новый столбец для каждого слова.

Вот небольшое подмножество данных:

n <- structure(list(Subscription_Id = c("13.855.231.846.091.000", 
"11.156.048.529.090.800", "24.940.584.090.830", "242.753.039.111.124", 
"27.843.782.090.830", "13.773.513.145.090.800", "25.691.374.090.830", 
"12.236.174.155.090.900", "252.027.904.121.210", "11.136.991.054.110.100"
), Account_Desc = c("AGUAYO CARLA", "LEIVA LILIANA", "FULLANA MARIA LAURA", 
"PETREL SERGIO", "IPTICKET SRL", "LEDESMA ORLANDO", "CATTANEO LUIS RAUL", 
"CABRAL CARMEN ESTELA", "ITURGOYEN HECTOR", "CASA CASILDO"), 
    V1 = c("AGUAYO", "LEIVA", "FULLANA", "PETREL", "IPTICKET", 
    "LEDESMA", "CATTANEO", "CABRAL", "ITURGOYEN", "CASA"), V2 = c("CARLA", 
    "LILIANA", "MARIA", "SERGIO", "SRL", "ORLANDO", "LUIS", "CARMEN", 
    "HECTOR", "CASILDO"), V3 = c(NA, NA, "LAURA", NA, NA, NA, 
    "RAUL", "ESTELA", NA, NA), `NA` = c(NA_character_, NA_character_, 
    NA_character_, NA_character_, NA_character_, NA_character_, 
    NA_character_, NA_character_, NA_character_, NA_character_
    )), .Names = c("Subscription_Id", "Account_Desc", "V1", "V2", 
"V3", NA), class = c("data.table", "data.frame"), row.names = c(NA, 
-10L), .internal.selfref = <pointer: 0x0000000000200788>)


require("data.table")
n <- data.table(n)

Ожидаемый результат

#           Subscription_Id         Account_Desc        V1      V2     V3 NA
# 1: 13.855.231.846.091.000         AGUAYO CARLA    AGUAYO   CARLA     NA NA
# 2: 11.156.048.529.090.800        LEIVA LILIANA     LEIVA LILIANA     NA NA
# 3:     24.940.584.090.830  FULLANA MARIA LAURA   FULLANA   MARIA  LAURA NA

1-я попытка

Как заставить это работать будет первым вопросом

library(stringr)
# This separates the strings, but i loose the Subscription_Id variable.
n[, str_split_fixed(Account_Desc, "[ +]", 4)]

# This doesn't work.
n[, paste0("V",1:4) := str_split_fixed(Account_Desc, "[ +]", 4)]

2-я попытка

Это работает, но я, кажется, делаю расчет 3 раза. Не уверен, что это самый эффективный способ

cols = paste0("V",1:3)
for(j in 1:3){
  set(n,i=NULL,j=cols[j],value = sapply(strsplit(as.character(n$Account_Desc),"[ +]"), "[", j))
}

Давайте используем big_n для сравнения

big_n <- data.table(Subscription_Id = rep(n[,Subscription_Id],1e7),
                    Account_Desc = rep(n[,Account_Desc],1e7)
                    )

person marbel    schedule 12.02.2014    source источник
comment
Подождать, пока sep2 будет реализовано в fread? ;)   -  person Roland    schedule 12.02.2014
comment
@ Роланд, я вижу, как ты подмигиваешь, но, если я правильно помню, fread не получит аргумент типа fill (как присутствует в read.table) в ближайшее время, а это означает, что ему будет тяжело с такими несбалансированными строками.   -  person A5C1D2H2I1M1N2O1R2T1    schedule 12.02.2014
comment
@AnandaMahto Ах, я не видел людей с несколькими именами. Я обычно забываю об этом, так как я один из бедных парней, у которых есть только один.   -  person Roland    schedule 12.02.2014
comment
read.csv может занять годы   -  person Baumann    schedule 12.02.2014
comment
есть 2 FR (уже готовые), которые действительно могут вам здесь помочь: у одного есть rbindlist работа со списками векторов, а у другого есть fill аргумент для этого; пока они не будут реализованы, я думаю, что ваше второе решение (возможно, с некоторыми незначительными корректировками) - это путь.   -  person eddi    schedule 12.02.2014
comment
... и почему-то никто не указал, что вы ошиблись во времени. Вы разбиваете всего 10 строк, а не 1e7 :-)   -  person A5C1D2H2I1M1N2O1R2T1    schedule 12.02.2014
comment
@AnandaMahto Я представляю десять строк в 10e7 в big_n (всего в big_n 100 ММ строк). Не уверен, что это эквивалентно, но это работает для воспроизводимости.   -  person marbel    schedule 12.02.2014
comment
@MartínBel, посмотрите, что у вас есть в вашей функции set_method. У вас жестко запрограммировано n$Account_Desc в строке strsplit.   -  person A5C1D2H2I1M1N2O1R2T1    schedule 12.02.2014
comment
Следование рекомендации @Arun о перемещении strsplit из цикла имеет большое значение, как и использование strsplit(input, " ", fixed = TRUE). Включение обоих этих предложений сократило время, которое я получал на 1e5 строк, с ~ 25 секунд до ~ 9 секунд.   -  person A5C1D2H2I1M1N2O1R2T1    schedule 12.02.2014


Ответы (2)


Я не работаю с наборами данных в таком масштабе, поэтому понятия не имею, пригодится это или нет. Одна вещь, которая приходит на ум, - это использовать matrix и матричную индексацию.

Поскольку я нетерпелив, я пробовал это только на 1e5 строках на своей медленной системе :-)

Создайте образец данных

big_n <- data.table(Subscription_Id = rep(n[,Subscription_Id],1e5),
                    Account_Desc = rep(n[,Account_Desc],1e5))

Напишите функцию для создания вашей матрицы

StringMat <- function(input) {
  Temp <- strsplit(input, " ", fixed = TRUE)
  Lens <- vapply(Temp, length, 1L)
  A <- unlist(Temp, use.names = FALSE)
  Rows <- rep(sequence(length(Temp)), Lens)
  Cols <- sequence(Lens)
  m <- matrix(NA, nrow = length(Temp), ncol = max(Lens),
              dimnames = list(NULL, paste0("V", sequence(max(Lens)))))
  m[cbind(Rows, Cols)] <- A
  m
}

Засеките время и просмотрите результат

system.time(outB1 <- cbind(big_n, StringMat(big_n$Account_Desc)))
#    user  system elapsed 
#   4.524   0.000   4.533 
outB1
#                 Subscription_Id         Account_Desc        V1      V2     V3
#       1: 13.855.231.846.091.000         AGUAYO CARLA    AGUAYO   CARLA     NA
#       2: 11.156.048.529.090.800        LEIVA LILIANA     LEIVA LILIANA     NA
#       3:     24.940.584.090.830  FULLANA MARIA LAURA   FULLANA   MARIA  LAURA
#       4:    242.753.039.111.124        PETREL SERGIO    PETREL  SERGIO     NA
#       5:     27.843.782.090.830         IPTICKET SRL  IPTICKET     SRL     NA
#      ---                                                                     
#  999996: 13.773.513.145.090.800      LEDESMA ORLANDO   LEDESMA ORLANDO     NA
#  999997:     25.691.374.090.830   CATTANEO LUIS RAUL  CATTANEO    LUIS   RAUL
#  999998: 12.236.174.155.090.900 CABRAL CARMEN ESTELA    CABRAL  CARMEN ESTELA
#  999999:    252.027.904.121.210     ITURGOYEN HECTOR ITURGOYEN  HECTOR     NA
# 1000000: 11.136.991.054.110.100         CASA CASILDO      CASA CASILDO     NA

Исправьте функцию set_method и сравните тайминги

set_method <- function(DT){
  cols = paste0("V",1:3)
  for(j in 1:3){
    set(DT,i=NULL,j=cols[j],
        value = sapply(strsplit(as.character(DT[, Account_Desc, with = TRUE]),
                                "[ +]"), "[", j))
  }
}

system.time(set_method(big_n))
#    user  system elapsed 
#  25.319   0.022  25.586 

Сбросьте набор данных "big_n" и попробуйте str_split_fixed (ой!)

big_n[, c("V1", "V2", "V3") := NULL]

library(stringr)
system.time(outBrodie <- cbind(big_n, as.data.table(str_split_fixed(
  big_n$Account_Desc, "[ +]", 4))))
#    user  system elapsed 
# 204.966   0.514 206.910 
person A5C1D2H2I1M1N2O1R2T1    schedule 12.02.2014
comment
+1 Мне нравятся оптимизации. Однако вы можете сэкономить дополнительное время, создав матрицу символов m вместо логической матрицы. Я рекомендую заменить NA (логический) на NA_character_. Это позволяет избежать преобразования типов при добавлении строк в матрицу. - person Sven Hohenstein; 16.02.2014

РЕДАКТИРОВАТЬ 3: Кража крови и пота Аруна:

cbind(n, as.data.table(str_split_fixed(n$Account_Desc, "[ +]", 4)))

Это позволяет избежать потенциально дорогостоящего by и дает тот же результат (плюс исходный столбец имени).

EDIT2: согласно комментарию Аруна, возможно:

n.2[, c(paste0("V", 1:4)):=as.list(str_split_fixed(Account_Desc, "[ +]", 4)), by=Subscription_Id]

Но у вас все еще есть by. Старый способ:

n[, as.list(str_split_fixed(Account_Desc, "[ +]", 4)), by=Subscription_Id]                        

производит:

  #            Subscription_Id        V1      V2     V3 V4
  #  1: 13.855.231.846.091.000    AGUAYO   CARLA          
  #  2: 11.156.048.529.090.800     LEIVA LILIANA          
  #  3:     24.940.584.090.830   FULLANA   MARIA  LAURA   
  #  4:    242.753.039.111.124    PETREL  SERGIO          
  #  5:     27.843.782.090.830  IPTICKET     SRL          
  #  6: 13.773.513.145.090.800   LEDESMA ORLANDO          
  #  7:     25.691.374.090.830  CATTANEO    LUIS   RAUL   
  #  8: 12.236.174.155.090.900    CABRAL  CARMEN ESTELA   
  #  9:    252.027.904.121.210 ITURGOYEN  HECTOR          
  # 10: 11.136.991.054.110.100      CASA CASILDO      

РЕДАКТИРОВАТЬ: слово предупреждения, некоторые функции stringr могут быть медленными (не уверен, что это так). Если это все еще медленно для вашего процесса, вы можете написать свою собственную функцию, используя strsplit и что-то, чтобы дополнить ее до соответствующей длины.

person BrodieG    schedule 12.02.2014
comment
@ Арун Я рад украсть твой ответ, но тебе, вероятно, следует опубликовать его как более быструю альтернативу. В этом случае это довольно отличается от настройки. - person BrodieG; 12.02.2014
comment
Я думаю, что это намного медленнее, чем вторая реализация OP. - person eddi; 12.02.2014
comment
@Arun все попытки в этом ответе намного медленнее - person eddi; 12.02.2014
comment
чем вторая реализация OP; и это потому, что функция stringr очень медленная - person eddi; 12.02.2014
comment
но это не так :) обычный strsplit на порядки быстрее; узкое место не расщепляется - оно объединяется - person eddi; 12.02.2014
comment
Второе решение OP выполняет привязку, и это снова на несколько порядков быстрее, чем то, что делает stringr. - person eddi; 12.02.2014
comment
как вы упоминали ранее, Subscription_Id уникален. - person marbel; 12.02.2014
comment
@AnandaMahto, я действительно помню вопрос, в котором вы отметили это несколько недель назад, и поэтому я вставил свое первое редактирование в самый низ, прежде чем этот разговор взорвался; в любом случае, даже исправление этого выглядит не лучшим способом. - person BrodieG; 12.02.2014
comment
Большинство функций stringr просто обертывают базовые функции регулярных выражений R. str_split_fixed() делает немного больше, чем базовые функции, поэтому немного медленнее. - person hadley; 13.02.2014