2016-08-03 6 views
1

Я подхожу к неизвестному количеству нескольких периодов даты в пределах основной последовательности периодов. Есть ли более читаемый или более компактный способ сделать это, чем мой путь? Я использую sqldf и data.table здесь, так как я использую обе библиотеки в проекте, но могу сделать это в чистой data.table, если это необходимо. Или наоборот. Другие хорошо развитые библиотеки также в порядке.R - транспонировать и вычислять различия между неизвестными столбцами даты. Более читаемый способ?

Нет hardcoding, я запускаю это с несколькими вариантами и не знаю максимального количества периодов в периоды. Количество в течение периодов не имеет теоретического верхнего (а может быть, 365) предел, но основная проверка здравомыслия должна составлять максимум около 6-15 периодов, в зависимости от спецификации.

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

Хотелось бы верить, что должна быть более короткая/читаемая функция. Есть ли что-нибудь, что мы можем сделать в dcast-части, которая вычисляет различия и применяет функцию sum? Я пробовал, но fun.aggregate кажется ограниченным более простыми операциями.

library("data.table") 
library("sqldf") 

Data <- data.table(
    Fnr  = c(22516, 22516, 22516, 45459, 45459), 
    Vernr = c(1,1,1,1,2), 
    Startdat = c("2010-01-01", "2010-01-01", "2012-01-01", 
       "2013-04-01", "2013-04-01"), 
    Endat = c("2010-12-31", "2010-12-31", "2012-05-19", 
       "2014-03-31", "2014-03-31"), 
    Fromdat = c("2010-02-21", "2010-08-16", NA, "2013-08-31", "2014-01-02"), 
    Tomdat = c("2010-05-16", "2010-09-11", NA, "2013-10-27", "2014-02-13") 
) 

tmp.eval <- "list(Fnr, Vernr, Startdat, Endat)" 

dt_tmp <- Data[, nobs_id := order(Fromdat), 
       by = eval(parse(text = tmp.eval))] 

dt_tmp <- dt_tmp[, c("Fromdat", "Tomdat") := list(as.Date(Fromdat), 
                as.Date(Tomdat))] 

dt_tmp <- dcast(dt_tmp, Fnr + Vernr + ... ~ nobs_id, 
       value.var = c("Fromdat", "Tomdat")) 

dt.colnames <- data.table(colnames(dt_tmp)) 
dt.col1 <- dt.colnames[substr(V1,1,3) == "Fro"][, c("nobs_id", "Fromdat") := 
               list(order(V1),V1)] 
dt.col2 <- dt.colnames[substr(V1,1,3) == "Tom"][, c("nobs_id", "Tomdat") := 
               list(order(V1),V1)] 
dt.set <- merge(dt.col1[,V1 := NULL], dt.col2[,V1 := NULL], 
       by = "nobs_id") 
dt.set <- dt.set[, diff_col := paste(Tomdat,Fromdat, sep = "-")] 
dt.set <- dt.set[, diff_col := paste(diff_col, " diff_",nobs_id, sep = "")] 
dt.set <- dt.set[, diffvar_col := paste("as.numeric(diff_",nobs_id,")", 
             sep = "")] 

str.diff <- paste(dt.set$diff_col, collapse = ",") 
str.diffvar <- paste(dt.set$diffvar_col, collapse = ",") 
str.diffvar <- paste("sum(", str.diffvar, ", na.rm = TRUE)") 

dt_tmp <- sqldf(sprintf("SELECT *, %s FROM dt_tmp", 
         str.diff) 
       ) 

dt_tmp <- setDT(dt_tmp)[Startdat <= Endat, 
         corr_days := eval(parse (text = str.diffvar)),  
         by = list(Fnr, Vernr, Startdat, Endat)] 
+2

Для такого длинного вопроса может быть трудно получить прямой ответ здесь. Больше подходит для проверки кода. 1. Я бы не «dcast», а обрабатывал по группам как данные, которые уже хорошо смоделированы (* tidy *, используя недавно популяризированный термин). Тогда вам не нужно заменять имена столбцов переменными/строками, так как вы сохраняете данные в более общей структуре. 2. Не знаете, почему вы используете 'sqldf' здесь только для простого одиночного преобразования. 3. Я бы избегал 'eval (parse (' on text в пользу 'eval (' на языковых объектах, это хорошая функция R. – jangorecki

+0

@jangorecki thx для подсказки, я обязательно буду думать об этом в будущем Sqldf - это артефакт, я сначала собирался в sqldf (я думал) и не менял его позже, так как планировал перезаписать функцию в любом случае. О части eval, thx, я все еще довольно новичок в использовании из R! – ErrantBard

ответ

3

Я не 100% уверен, что вы пытаетесь сделать, но вот способ получить тот же конечный вывод dt_tmp. Предполагая, что я интерпретирую то, что вы пытаетесь сделать правильно, трюк состоит в том, чтобы сначала выполнить ваши вычисления, затем расплавить это, а затем бросить.

Data[, nobs_id := order(Fromdat), 
    by = list(Fnr, Vernr, Startdat, Endat)] 
Data[,Tomdat:=as.IDate(Tomdat)] 
Data[,Fromdat:=as.IDate(Fromdat)] 
Data[,diff:=as.integer(Tomdat-Fromdat)] 
Data[,corr_days:=sum(diff),by= list(Fnr, Vernr, Startdat, Endat)] 
mytemp<-dcast(melt(Data,id.vars=c('Fnr','Vernr','Startdat','Endat','nobs_id','corr_days')),Fnr+Vernr+Startdat+Endat+corr_days~variable+nobs_id,value.var='value') 

#if you want them in the same order as your dt_tmp 
setcolorder(mytemp, c(names(mytemp)[!names(mytemp)%in%'corr_days'],'corr_days')) 



#if you want the date columns to be type character again 
datecols<-names(mytemp)[grepl('dat',names(mytemp))] 
mytemp[,(datecols):=lapply(.SD,as.character),.SDcols=datecols] 
+0

Хорошо, еще одна второстепенная вещь. Я бы установил даты спереди и подумал о том, чтобы использовать 'as.IDate'. Нет реальной причины держать их в виде строк и форматировать« на лету »(например, при вычислении' diff ') – Frank

+1

@Frank да, я сделал это так, потому что казалось, что может быть какая-то причина (хотя я и не представляю, что это может быть), чтобы она хранилась как персонаж, иначе OP включил бы as.IDate (или as.Date), когда они назначают 'Data'. Очевидно, ваш путь лучше. Думаю, я изменю его на это, и OP может вернуться к символу, если захочет , –

+0

Отличный @DeanMacGregor - это намного короче! И лучше, я думаю, что я был способ установить один путь. Понял, что я пропустил одну (хотя и второстепенную) вещь в моем вопросе. У меня около 30 столбцов в моей реальной таблице данных с различными категориальными значениями. Они исправлены, поэтому я могу их назвать, но если я этого не сделаю, это будет бонус. Но ответ хороший и гораздо легче читать, поэтому я с радостью соглашусь с этим! (Даты персонажей были только результатом моего вопроса, это даты с самого начала в реальном dt. Идентификация была для меня новой, хотя) – ErrantBard