2015-02-17 2 views
5

EDIT: При создании простого образца data.frame я использовал те же даты для двух столбцов Date, но это не так, что усложняет эту проблему.R Reshape Performance

Вместо этого dataframe:

ID  Date   Balance Date2  Balance2 
1  01-01-2014  10000  01-02-2014 5000 
2  01-01-2014  50000  01-02-2014 30000 
3  01-01-2014  30000  01-02-2014 15000 
4  01-01-2014  5000  01-02-2014 3500 

У меня есть этот dataframe вместо:

ID  Date   Balance Date2  Balance2 
1  01-01-2014  10000  01-02-2017 5000 
2  01-01-2015  50000  01-02-2016 30000 
3  01-08-2014  30000  01-02-2015 15000 
4  01-02-2016  5000  01-02-2018 3500 

который я хотел бы, чтобы изменить к следующему:

ID  Date   Balance 
1  01-01-2014  10000  
1  02-02-2017  5000 
2  01-01-2015  50000  
2  01-02-2016  30000  
3  ...   ...  And so on... 

У меня есть следующие в данный момент.

Dates = a character containing all the columns with Dates (Date, Date2, Date3...) 
Balances = a character containing all the columns with Balances (Balance1, Balance2...) 

df <- reshape(df, 
       varying = Balances, 
       v.names = "Balance" 
       timevar = "Date" 
       times = Dates, 
       direction = "long") 

Результаты с отлично предлагаемых ваши методы не получить мне результаты, когда я изменил образец data.frame/data.table.

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

Мне нужен способ, в котором R понимает, что ему нужно взять столбец «Дата» и столбец «Баланс», поместить его в новый DF, затем взять Date2 и Balance2, перенести их с помощью первого DF, затем Date3, Balance3 и т. Д. , пока я не получу свои переменные 700ish.

Я подумываю написать цикл, любые мысли? Ниже приведены примеры данных.

Спасибо заранее,

Роберт

df <- data.frame(ID=seq(1:4), 
       Date= c("01-01-2014","01-01-2015","01-08-2014","01-02-2016"), 
       Balance = c(10000,50000,30000,5000), 
       Date2= c("01-02-2017","01-02-2016","01-02-2015","01-02-2018"), 
      Balance2 = c(5000,30000,15000,3500)) 
+0

могу Не комментируйте вашу конкретную проблему, но я призываю вас перейти на 'reshape2', который поставляется с значительно улучшенным API и реализован на C++, который имеет потенциально значительные улучшения производительности. –

+0

@RobertLuyt, см. Мое редактирование. Дайте мне знать, если это сработает для вас. – bgoldst

ответ

2

Не самое простое решение построить новый data.frame путем объединения двух наборов столбцов? Это может быть сделано без reshape:

r> x <- data.frame(ID=1:4, Date=as.POSIXct(c('2014-01-01','2014-01-01','2014-01-01','2014-01-01')), Balance=c(10000,50000,30000,5000), Date2=as.POSIXct(c('2014-01-02','2014-01-02','2014-01-02','2014-01-02')), Balance2=c(5000,30000,15000,3500)); 
r> y <- data.frame(ID=c(x$ID,x$ID), Date=c(x$Date,x$Date2), Balance=c(x$Balance,x$Balance2)); 
r> y; 
    ID  Date Balance 
1 1 2014-01-01 10000 
2 2 2014-01-01 50000 
3 3 2014-01-01 30000 
4 4 2014-01-01 5000 
5 1 2014-01-02 5000 
6 2 2014-01-02 30000 
7 3 2014-01-02 15000 
8 4 2014-01-02 3500 

Можете ли вы дайте мне знать, если это хорошо работает для ваших данных?

Для сортировки:

r> z <- y[order(y$ID,y$Date),]; rownames(z) <- 1:nrow(z); 
r> z; 
    ID  Date Balance 
1 1 2014-01-01 10000 
2 1 2014-01-02 5000 
3 2 2014-01-01 50000 
4 2 2014-01-02 30000 
5 3 2014-01-01 30000 
6 3 2014-01-02 15000 
7 4 2014-01-01 5000 
8 4 2014-01-02 3500 

Edit: Учитывая, что у вас так много столбцов, вручную вызова c() на каждой соответствующей дате и столбце Остаток не практично. Однако, немного поиграв, я понял, что вы можете комбинировать функции names(), grep(), do.call() и c(), чтобы автоматически извлекать и комбинировать данные так, как вы хотите. Вам также понадобится unname() для удаления нежелательных имен элементов и replicate() для повторной тиражирования столбца ID достаточное количество раз.

Во-первых, я выяснил способ создания рандомизированных входных данных.рамка для тестирования:

r> randDate <- function() as.Date('2014-01-01')+as.integer(runif(1,max=30)); 
r> randBalance <- function() 5000+as.integer(runif(1,max=18))*5000; 
r> n <- 700; 
r> x <- setNames(do.call(data.frame, c(list(1:4), replicate(n, list(do.call(c, replicate(4, randDate(), simplify=F)), do.call(c, replicate(4, randBalance(), simplify=F))), simplify=F))), c('ID', sapply(1:n, function(x) c(paste0('Date',x), paste0('Balance',x))))); 
r> x; 
    ID  Date1 Balance1  Date2 Balance2  Date3 Balance3 ... Balance698 Date699 Balance699 Date700 Balance700 
1 1 2014-01-29 10000 2014-01-08 50000 2014-01-05 40000 ...  30000 2014-01-23  35000 2014-01-08  45000 
2 2 2014-01-30 65000 2014-01-15 10000 2014-01-11 45000 ...  75000 2014-01-29  25000 2014-01-04  50000 
3 3 2014-01-11 75000 2014-01-14 70000 2014-01-24 45000 ...  50000 2014-01-02  10000 2014-01-01  50000 
4 4 2014-01-11 25000 2014-01-11 20000 2014-01-24 20000 ...  50000 2014-01-08  70000 2014-01-11  75000 

Теперь вы можете достичь желаемого перепрофилирование, используя следующие:

r> y <- data.frame(ID=do.call(c, replicate((ncol(x)-1)/2, x$ID, simplify=F)), Date=unname(do.call(c, x[,grep('^Date[0-9]+$', names(x))])), Balance=unname(do.call(c, x[,grep('^Balance[0-9]+$', names(x))]))); 
r> y; 
    ID  Date Balance 
1  1 2014-01-29 10000 
2  2 2014-01-30 65000 
3  3 2014-01-11 75000 
4  4 2014-01-11 25000 
5  1 2014-01-08 50000 
6  2 2014-01-15 10000 
... 
2795 3 2014-01-02 10000 
2796 4 2014-01-08 70000 
2797 1 2014-01-08 45000 
2798 2 2014-01-04 50000 
2799 3 2014-01-01 50000 
2800 4 2014-01-11 75000 

И для заказа:

r> z <- y[order(y$ID,y$Date),]; rownames(z) <- 1:nrow(z); 
r> z; 
    ID  Date Balance 
1  1 2014-01-01 55000 
2  1 2014-01-01 20000 
3  1 2014-01-01 15000 
4  1 2014-01-01 75000 
5  1 2014-01-01 40000 
6  1 2014-01-01 85000 
... 
2795 4 2014-01-30 15000 
2796 4 2014-01-30 65000 
2797 4 2014-01-30 5000 
2798 4 2014-01-30 70000 
2799 4 2014-01-30 35000 
2800 4 2014-01-30 30000 

Этот код работает в основном мгновенно. Ключом к скорости является то, что он извлекает каждый столбец ввода для целевого выходного столбца одновременно, подписывая data.frame (например, x[,grep('^Date[0-9]+$', names(x))] для всех столбцов Date) и запускает все из них одним вызовом до c() посредством одного вызова до do.call, который игнорирует класс data.frame аргумента и просто рассматривает его как базовый список, который он есть. Конечным результатом является то, что вы получаете векторный столбец c() в форме, которая составляет , почти, готовый к подключению к выходному data.frame (вам просто нужно удалить ненужные имена элементов, используя unname()). Вам нужно сделать это для столбцов Date и столбцов Balance независимо (столбцы баланса, индексированные через x[,grep('^Balance[0-9]+$', names(x))]) и упаковать их вместе в новом вызове построения data.frame. Единственный другой фрагмент головоломки состоит в том, чтобы воспроизвести столбец ввода ID достаточное количество раз ((ncol(x)-1)/2), чтобы создать правильный столбец идентификатора вывода, который соответствует выходным векторам даты и баланса.

Это решение полностью векторизовано, без явных или скрытых петель. Кроме того, он использует только встроенную функциональность R; он не требует зависимости от каких-либо дополнительных пакетов. Я всегда стараюсь избегать использования дополнительных пакетов, которые, как правило, затрудняют сложность и трудности в обслуживании, поскольку расширяется объем знаний, необходимых для понимания кода.

+0

И затем вы получаете то, что запросил у вас запрос, вы можете заказать по ID. Этот порядок может быть немного медленным. – John

+0

или 'rbind (DF [, 1: 3], DF [, c (1, 4: 5)])' – Roland

+0

@Roland Мне нравится краткое изложение вашего предложения, но когда я его запускаю, я получаю 'Ошибка в матче .names (clabs, names (xi)): имена не соответствуют предыдущим именам', а 'rbind()', похоже, не предоставляет никакого способа указывать имена. Есть ли способ заставить 'rbind()' работать? – bgoldst

2

Если вам небезразличен порядок, чем, вероятно, самый быстрый способ исходит от data.table ответов. Но если вы этого не сделаете, вы можете просто привязать строки первых трех столбцов к первому и последнему двум, используя rbind. Это будет очень быстро и просто, но у вас нет порядка. Вы можете изменить порядок с функцией order на ID.

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

dateMat <- as.matrix(df[, c(2, 4)]) 
balMat <- as.matrix(df[, c(3, 5)]) 
dates <- as.vector(t(dateMat)) 
balances <- as.vector(t(balMat)) 
dfl <- data.frame(ID = rep(df$ID, each = 2), Date = dates, Balance = balances) 

Вы можете протестировать две версии ВНЕ для скорости на большом data.frame.

2

Другим вариантом может быть следующее. Используя select в dplyr, вы можете одновременно выбирать столбцы и изменять имена столбцов. В конце вы связываете два набора данных с bind_rows.

DATA & КОД

mydf <- structure(list(ID = 1:4, Date = structure(c(1L, 1L, 1L, 1L), .Label = "01-01-2014", class = "factor"), 
Balance = c(10000L, 50000L, 30000L, 5000L), Date2 = structure(c(1L, 
1L, 1L, 1L), .Label = "01-02-2014", class = "factor"), Balance2 = c(5000L, 
30000L, 15000L, 3500L)), .Names = c("ID", "Date", "Balance", 
"Date2", "Balance2"), class = "data.frame", row.names = c(NA, 
-4L)) 

# Convert factor to date object 
mutate_each(mydf, funs(as.Date(., format = "%m-%d-%Y")), Date, Date2) -> mydf 

bind_rows(select(mydf, 1:3), select(mydf, 1, Date = Date2, Balance = Balance2)) %>% 
arrange(ID, Date) 

# ID  Date Balance 
#1 1 2014-01-01 10000 
#2 1 2014-01-02 5000 
#3 2 2014-01-01 50000 
#4 2 2014-01-02 30000 
#5 3 2014-01-01 30000 
#6 3 2014-01-02 15000 
#7 4 2014-01-01 5000 
#8 4 2014-01-02 3500 
+0

Я буду держать вас в курсе, это кажется многообещающим. –

+0

@RobertLuyt У вас здесь есть всевозможные отличные подходы. Попробуй их всех и посмотри, что работает для тебя лучше всего. :) – jazzurro

3

Если ваши столбцы названы как вы ввели в вашем примере, вы можете попробовать merged.stack из моего пакета «splitstackshape». Обратите внимание, что значения в столбце «Идентификатор» должны быть уникальными, чтобы работать корректно, хотя (как и в ваших образцовых данных).

Использование прост: укажите «заглушки» переменных (здесь «Дата» и «Баланс»). Установка sep = "var.stubs" просто удаляет остальную часть имени столбца.[, .time_1 := NULL] - это просто удалить столбец времени, который был создан в процессе перестройки.

library(splitstackshape) 
merged.stack(mydf, var.stubs = c("Date", "Balance"), 
      sep = "var.stubs")[, .time_1 := NULL][] 
# ID  Date Balance 
# 1: 1 01-01-2014 10000 
# 2: 1 01-02-2014 5000 
# 3: 2 01-01-2014 50000 
# 4: 2 01-02-2014 30000 
# 5: 3 01-01-2014 30000 
# 6: 3 01-02-2014 15000 
# 7: 4 01-01-2014 5000 
# 8: 4 01-02-2014 3500 

Скоро (версия 1.9.8 от «data.table») melt будет иметь возможность обрабатывать преобразования в полудлинной форме, как вы пытаетесь получить здесь. Это будет быстрее, чем merged.stack на данный момент, но merged.stack уже должен иметь возможность обрабатывать ваш нынешний сценарий.

+0

Hi Ananda - У вас есть ссылка, в которой упоминается/описывается, что предстоящее улучшение 'data.table :: melt()'? Поцарапайте это: [нашло это] (https://github.com/Rdatatable/data.table/issues/828)! Спасибо за головы. –

+0

@ JoshO'Brien, Просто получил ответ на этот комментарий. Странно ... Рад, что вы нашли ссылку тем временем :-) Возможно, немного неловко это сказать, но я взволнован этим развитием :-) – A5C1D2H2I1M1N2O1R2T1

+0

Hah! Это значит, что по крайней мере двое из нас ...;) –

2

Адрес data.table. Я все еще пытаюсь думать, как удалить вызовы на первую таблицу данных.

dt <- structure(list(ID = 1:4, Date = structure(c(1L, 1L, 1L, 1L), .Label = "01-01-2014", class = "factor"), 
Balance = c(10000L, 50000L, 30000L, 5000L), Date2 = structure(c(1L, 
1L, 1L, 1L), .Label = "01-02-2014", class = "factor"), Balance2 = c(5000L, 
30000L, 15000L, 3500L)), .Names = c("ID", "Date", "Balance", 
"Date2", "Balance2"), class = "data.table", row.names = c(NA, 
-4L)) 

dt1 <- melt(dt,id="ID",measure=c("Balance","Balance2"))[, variable := c(as.character(dt$Date), as.character(dt$Date2))] 
dt1 
1

Как уже упоминалось in this answer, версия 1.9.6 из data.table (на CRAN 19 сен 2015) представил возможность расплавиться на несколько колонок:

library(data.table) 
melt(setDT(df), measure.vars = patterns("Date", "Balance"), 
    value.name = c("Date", "Balance")) 
ID variable  Date Balance 
1: 1  1 01-01-2014 10000 
2: 2  1 01-01-2015 50000 
3: 3  1 01-08-2014 30000 
4: 4  1 01-02-2016 5000 
5: 1  2 01-02-2017 5000 
6: 2  2 01-02-2016 30000 
7: 3  2 01-02-2015 15000 
8: 4  2 01-02-2018 3500