Перестановка нескольких строк фрейма данных в больших данных в R

Я относительно новичок в R. У меня есть кадр данных test, который выглядит так:

PMID    # id
LID
STAT
MH
RN
OT
PST     # cue
LID
STAT
MH
PMID    # id
OT
PST     # cue
LID
DEP
RN
PMID    # id
PST     # cue

и я хочу, чтобы это выглядело так:

PMID    # id
LID
STAT
MH
RN
OT
PST     # cue
PMID    # id
LID
STAT
MH
OT
PST     # cue
PMID    # id
LID
DEP
RN
PST     # cue

По сути, я хочу, чтобы записи, следующие за PMID, относились к этому конкретному PMID, как в случае с первым PMID. Однако после первого PMID этот PMID размещается случайным образом между своими записями. Однако каждый PMID заканчивается PST, поэтому я хочу, чтобы последующие PMID после первого были перемещены в место после предыдущего местоположения PST. У меня есть два фрейма данных, которые содержат расположение индекса каждого PMID и PST. Так, например, для PMID df a_new содержит

1 
11
17

а для PST df b содержит

7
13
18

Это то, что я пробовал, но поскольку у меня более 24 миллионов строк, он не закончил работу через несколько часов, и когда я остановил его, мой фрейм данных не изменился:

for (i in 1:nrow(test))
{    
  if (i %in% a_new$X1) # if it's a PMID
  {
    entry <- match(i, a_new$X1) # find entry index of PMID
    if (entry != 1) # as long as not first row from a_new (that's corrected)
    {
      r <- b[i, 1] # row of PST
      test <- rbind(test[1:r, ], test[entry, 1], test[-(1:r), ])
      test <- test[-c(i+1), ] # remove duplicate PMID
    }
  }
}

Как видите, rbind в этой ситуации будет крайне неэффективным. Пожалуйста, порекомендуйте.


person sweetmusicality    schedule 06.07.2017    source источник
comment
test не похож на data.frame: у него нет имени столбца и номера строки.   -  person HubertL    schedule 06.07.2017
comment
это 24 миллиона наблюдений/строк и 1 столбец   -  person sweetmusicality    schedule 06.07.2017
comment
Я не знаю, как добавить номера столбцов и строк в stackoverflow (без ручного управления)   -  person sweetmusicality    schedule 06.07.2017


Ответы (2)


Вот ответ с использованием data.table.

library(data.table)

dat <- fread("Origcol
             PMID
             LID
             STAT
             MH
             RN
             OT
             PST     
             LID
             STAT
             MH
             PMID    
             OT
             PST     
             LID
             DEP
             RN
             PMID   
             PST")

dat[, old_order := 1:.N]
pst_index <- c(0, which(dat$Origcol == "PST"))
dat[, grp := unlist(lapply(1:(length(pst_index)-1), 
                           function(x) rep(x, 
                                           times = (pst_index[x+1] - pst_index[x]))))]
dat[, Origcol := factor(Origcol, levels = c("PMID", "LID", "STAT", 
                                            "MH", "RN", "OT", 
                                            "DEP", "PST"))]
dat[order(grp, Origcol)]

Результат:

    Origcol old_order grp
 1:    PMID         1   1
 2:     LID         2   1
 3:    STAT         3   1
 4:      MH         4   1
 5:      RN         5   1
 6:      OT         6   1
 7:     PST         7   1
 8:    PMID        11   2
 9:     LID         8   2
10:    STAT         9   2
11:      MH        10   2
12:      OT        12   2
13:     PST        13   2
14:    PMID        17   3
15:     LID        14   3
16:      RN        16   3
17:     DEP        15   3
18:     PST        18   3

Преимущество этого заключается в том, что data.table выполняет множество операций по ссылке и должно быть быстрым, как только вы увеличите размер. Вы сказали, что у вас есть 14 миллионов строк, давайте попробуем. Создайте некоторые синтетические данные такого размера:

dat_big <- data.table(Origcol = c("PMID", "LID", "STAT", "MH", "RN", "OT", "PST"))
dat_big_add <- rbindlist(lapply(1:10000, 
                                function(x) data.table(Origcol = c(sample(c("PMID", "LID", "STAT", 
                                                                            "MH", "RN", "OT")), 
                                                                   "PST"))))
dat_big <- rbindlist(list(dat_big, 
                          dat_big_add, dat_big_add, dat_big_add, dat_big_add, dat_big_add, 
                          dat_big_add, dat_big_add, dat_big_add, dat_big_add, dat_big_add, 
                          dat_big_add, dat_big_add, dat_big_add, dat_big_add, dat_big_add, 
                          dat_big_add, dat_big_add, dat_big_add, dat_big_add, dat_big_add))

dat <- rbindlist(list(dat_big, dat_big, dat_big, dat_big, dat_big,
                      dat_big, dat_big, dat_big, dat_big, dat_big))

Теперь у нас есть:

          Origcol
       1:    PMID
       2:     LID
       3:    STAT
       4:      MH
       5:      RN
      ---        
14000066:    STAT
14000067:      MH
14000068:      OT
14000069:    PMID
14000070:     PST

Примените тот же код, что и выше:

dat[, old_order := 1:.N]
pst_index <- c(0, which(dat$Origcol == "PST"))
dat[, grp := unlist(lapply(1:(length(pst_index)-1), 
                           function(x) rep(x, 
                                           times = (pst_index[x+1] - pst_index[x]))))]
dat[, Origcol := factor(Origcol, levels = c("PMID", "LID", "STAT", 
                                            "MH", "RN", "OT", 
                                            "DEP", "PST"))]
dat[order(grp, Origcol)]

Теперь мы получаем:

          Origcol old_order     grp
       1:    PMID         1       1
       2:     LID         2       1
       3:    STAT         3       1
       4:      MH         4       1
       5:      RN         5       1
      ---                          
14000066:    STAT  14000066 2000010
14000067:      MH  14000067 2000010
14000068:      RN  14000064 2000010
14000069:      OT  14000068 2000010
14000070:     PST  14000070 2000010

Сколько времени это занимает?

library(microbenchmark)
microbenchmark(
  "data.table" = {
    dat[, old_order := 1:.N]
    pst_index <- c(0, which(dat$Origcol == "PST"))
    dat[, grp := unlist(lapply(1:(length(pst_index)-1), 
                               function(x) rep(x, 
                                               times = (pst_index[x+1] - pst_index[x]))))]
    dat[, Origcol := factor(Origcol, levels = c("PMID", "LID", "STAT", 
                                                "MH", "RN", "OT", 
                                                "DEP", "PST"))]
    dat[order(grp, Origcol)]
  }, 
  times = 10)

И это занимает:

Unit: seconds
       expr      min       lq     mean  median       uq      max neval
 data.table 5.755276 5.813267 6.059665 5.87151 6.034506 7.310169    10

Менее 10 секунд для 14 миллионов строк. Создание тестовых данных заняло намного больше времени.

person Eric Watt    schedule 06.07.2017
comment
вау, спасибо за такой развернутый ответ! ваше объяснение выглядит очень многообещающе. однако при запуске строки test[, grp:= unlist.... я столкнулся с этой ошибкой: Error in rep(x, time = (pst_index[x + 1] - pst_index[x])) : invalid 'times' argument - person sweetmusicality; 07.07.2017
comment
Есть ли что-то другое в вашем наборе данных test? На вашем компьютере не получается скопировать мой код как есть? - person Eric Watt; 07.07.2017
comment
о, ваш код отлично работает при копировании как есть. мой фактический набор данных имеет несколько строк, которые начинаются одинаково (вы увидите, что я имею в виду в следующем предложении) и следуют друг за другом. а также каждая строка не просто одно слово - например, PMID - 234254 или MH - human, но я не знаю, почему это может повлиять на ошибку. Я изменил свой фрейм данных на data.table, используя setDT(df), увидев ваш код... это подходящий ответ? - person sweetmusicality; 07.07.2017
comment
о, я неправильно изменил имя переменной (где вы использовали Origcol) ... позвольте мне попробовать изменить это (обновление: еще не сработало) - person sweetmusicality; 07.07.2017
comment
мой pst_index по какой-то причине равен 0... о, я вижу... мне нужно будет выполнить частичный поиск только для PST, а не точный поиск, поскольку эта строка PST - ppublish или что-то еще... - person sweetmusicality; 07.07.2017
comment
Какая ваша строка эквивалентна pst_index <- c(0, which(dat$Origcol == "PST"))? Вам нужно будет изменить dat на имя вашей таблицы данных и Origcol на имя вашего столбца. - person Eric Watt; 07.07.2017
comment
pst_index должен в конечном итоге быть таким же, как ваш df b, за исключением того, что он имеет 0 в начале (чтобы упростить индексацию на следующем шаге). - person Eric Watt; 07.07.2017
comment
хорошо, код работает без ошибок. однако окончательный кадр данных/таблица данных содержит все NA, в которых раньше были данные (в первом столбце). последние два столбца выглядят нормально, и это потому, что строки моего фрейма данных - это все символы, за которыми, как уже упоминалось, на самом деле следуют другие символы. поэтому без запуска последней строки все столбцы присутствуют. но для той цели, для которой мне это нужно, я думаю, что смогу справиться с этого шага (неупорядоченный, но связанный с grp... Мне нужно в конечном итоге преобразовать в длинные данные, чтобы это могло работать). спасибо! - person sweetmusicality; 07.07.2017

Вот метод индексации с использованием which.

# get positions of PST, the final value
endSpot <- which(temp == "PST")
# increment to get the desired positions of the PMID
# (dropping final value as we don't need to change it)
startSpot <- head(endSpot + 1, -1)
# get the current positions of the PMID, except the first one
PMIDSpot <- tail(which(temp == "PMID"), -1)

Теперь используйте эти индексы для замены строк

temp[c(startSpot, PMIDSpot), ] <- temp[c(PMIDSpot, startSpot), ]

Это возвращает (я добавил переменную позиции строки, называемую count, чтобы отслеживать).

temp
     V1 count
1  PMID     1
2   LID     2
3  STAT     3
4    MH     4
5    RN     5
6    OT     6
7   PST     7
8  PMID    11
9  STAT     9
10   MH    10
11  LID     8
12   OT    12
13  PST    13
14 PMID    17
15  DEP    15
16   RN    16
17  LID    14
18  PST    18

данные

temp <-
structure(list(V1 = c("PMID", "LID", "STAT", "MH", "RN", "OT", 
"PST", "LID", "STAT", "MH", "PMID", "OT", "PST", "LID", "DEP", 
"RN", "PMID", "PST"), count = 1:18), .Names = c("V1", "count"
), row.names = c(NA, -18L), class = "data.frame")
person lmo    schedule 06.07.2017