эквивалент melt + reshape, который разделяется на имена столбцов - PullRequest
2 голосов
/ 03 июля 2019

Точка : если вы собираетесь голосовать, чтобы закрыть, это плохая форма, чтобы не объяснять причину. Если его можно улучшить, не требуя закрытия, потратьте 10 секунд, чтобы написать краткий комментарий.

Вопрос:
Как мне сделать следующее «частичное плавление» так, чтобы память могла поддерживать?

подробности:
У меня есть несколько миллионов строк и около 1000 столбцов. Имена столбцов содержат 2 фрагмента информации.

Обычно я сливался бы с фреймом данных (или таблицей), состоящим из пары столбцов, затем разделял бы имя переменной, чтобы создать два новых столбца, а затем приводил бы одно из новых разбиений для новых имен столбцов. и один для имен строк.

Это не работает. Мой миллиард строк данных заставляет дополнительные столбцы переполнять мою память.

За пределами "итеративной силы" (в отличие от грубой силы) цикла for, существует ли чистый и эффективный способ сделать это?

Мысли:

  • это немного похоже на литье в расплавленном состоянии
  • библиотеками, общими для этого, кажутся "dplyr", "tidyr", "reshape2" и "data.table".
  • тидирная сборка + разделение + разворот выглядит хорошо, но не нравится отсутствие уникального идентификатора строки
  • Ресурс Reshape2 (я ищу 2d выход) хочет агрегировать
  • Грубая сила теряет метки. Под грубой силой я подразумеваю df <- rbind (df [, block1], ...), где block - первые 200 индексов столбцов, block2 - второй, и так далее. </li>

Обновление (фиктивный код):

#libraries
library(stringr)

#reproducibility
set.seed(56873504)

#geometry
Ncol <- 2e3
Nrow <- 1e6

#column names
namelist <- numeric(length=Ncol)
for(i in 1:(Ncol/200)){
  col_idx <- 1:200+200*(i-1)
  if(i<26){
  namelist[col_idx] <- paste0(intToUtf8(64+i),str_pad(string=1:200,width=3,pad="0"))
  } else {
    namelist[col_idx] <- paste0(intToUtf8(96+i),str_pad(string=1:200,width=3,pad="0"))
  }
}

#random data
df <- as.data.frame(matrix(runif(n=Nrow*Ncol,min=0, max=16384),nrow=Nrow,ncol=Ncol))
names(df) <- namelist

Вывод, который я бы искал, имел бы столбец с первым символом текущего имени (одиночный символ алфавита), а имена столбцов были бы от 1 до 200. Он был бы намного менее широким, чем "df", но не полностью расплавленным , Это также не убило бы мой процессор или память.

(Уродливый / Ручной) Версия с перебором:

(работает над этим ...)

1 Ответ

1 голос
/ 04 июля 2019

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

Если вы знаете, что каждая строка столбца всегда имеет 200 (или n) полей, связанных с ней (т. Е. A001 - A200), вы можете использовать melt() и составить список переменных измерения.

melt(dt
     , measure.vars = lapply(seq_len(Ncol_p_grp), seq.int, to = Ncol_p_grp * n_grp, by = Ncol_p_grp)
     , value.name = as.character(seq_len(Ncol_p_grp))
)[, variable := rep(namelist_letters, each = Nrow)][]

#this data set used Ncol_p_grp <- 5 to help condense the data. 
        variable         1          2         3          4          5
     1:        A 0.2655087 0.06471249 0.2106027 0.41530902 0.59303088
     2:        A 0.3721239 0.67661240 0.1147864 0.14097138 0.55288322
     3:        A 0.5728534 0.73537169 0.1453641 0.45750426 0.59670404
     4:        A 0.9082078 0.11129967 0.3099322 0.80301300 0.39263068
     5:        A 0.2016819 0.04665462 0.1502421 0.32111280 0.26037592
    ---                                                              
259996:        Z 0.5215874 0.78318812 0.7857528 0.61409610 0.67813484
259997:        Z 0.6841282 0.99271480 0.7106837 0.82174887 0.92676493
259998:        Z 0.1698301 0.70759513 0.5345685 0.09007727 0.77255570
259999:        Z 0.2190295 0.14661878 0.1041779 0.96782695 0.99447460
260000:        Z 0.4364768 0.06679642 0.6148842 0.91976255 0.08949571

В качестве альтернативы, мы можем использовать rbindlist(lapply(...)) для просмотра набора данных и поднабора его на основе буквы в столбцах.

rbindlist(
  lapply(namelist_letters,
       function(x) setnames(
         dt[, grep(x, names(dt), value = T), with = F]
         , as.character(seq_len(Ncol_p_grp)))
  )
  , idcol = 'ID'
, use.names = F)[, ID := rep(namelist_letters, each = Nrow)][]

С 78 миллионами элементов в этом наборе данных это занимает около четверти секунды. Я попытался увеличить его до 780 миллионов, но у меня просто нет оперативной памяти для быстрого создания данных.

#78 million elements - 10,000 rows * 26 grps * 200 cols_per_group
Unit: milliseconds
             expr      min       lq     mean   median       uq      max neval
      melt_option 134.0395 135.5959 137.3480 137.1523 139.0022 140.8521     3
 rbindlist_option 290.2455 323.4414 350.1658 356.6373 380.1260 403.6147     3

Данные: Запустите это, прежде чем все выше:

#packages ----
library(data.table)
library(stringr)

#data info
Nrow <- 10000
Ncol_p_grp <- 200
n_grp <- 26

#generate data
set.seed(1)
dt <- data.table(replicate(Ncol_p_grp * n_grp, runif(n = Nrow)))

names(dt) <- paste0(rep(LETTERS[1:n_grp], each = Ncol_p_grp)
                    , str_pad(rep(seq_len(Ncol_p_grp), n_grp), width = 3, pad = '0'))

#first letter
namelist_letters <- unique(substr(names(dt), 1, 1))
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...