2016-05-27 15 views
0

Ich habe ein Datenformat von Zeitstempeln, die einen kategorischen Status angeben. Der Status ist bis zum nächsten Zeitstempel gültig. Zu diesem Zeitpunkt kann sich die Kategorie ändern.Zeitreihen von kategorischen Daten - wie prozentualer Anteil jeder Kategorie über Zeitspannen berechnet werden kann?

Ich würde gern in der Lage sein, den prozentualen Anteil der Zeit zu bestimmen, die in jeder Kategorie über regelmäßige Zeiträume verbracht wird, wie monatlich, vierteljährlich oder jährlich.

Dies scheint wie ein allgemein genug Problem, aber ich konnte nicht eine elegante Lösung oder Bibliothek finden, um es zu lösen.

Zum Beispiel mit dem folgenden Beispieldatenrahmen:

  date status 
2016-02-20 09:11:00  a 
2016-03-06 02:38:00  c 
2016-03-10 15:20:00  b 
2016-03-10 21:20:00  a 
2016-03-11 11:51:00  b 
2016-03-12 01:19:00  c 
2016-03-22 14:39:00  c 
2016-03-23 11:37:00  b 
2016-03-25 17:38:00  c 
2016-03-26 01:24:00  c 
2016-03-26 12:40:00  a 
2016-04-12 10:28:00  c 

... Ich möchte vielleicht wöchentlich berichten von 3/1-3/7, 3/8-3/14, 3/15- 3/21, die prozentuale Zeit in jeder Woche von 'a', 'b' und 'c' Status.

Ich begann brachiale Kraft eine Lösung zu kodieren (es ist hässlich ...), als ich entschied, vielleicht sollte ich hier fragen, ob es eine elegantere Art ist, es zu tun.


======== Herausgegeben eine unelegant Brute-Force-Lösung unter ========

time_analysis <- function(df, starttime, endtime) { 
    # - assumes sorted by date 

    startindex <- sum(df$date <= starttime) # find the index of the entry which contains the start time 
    endindex <- sum(df$date <= endtime) + 1 # find the index of the entry which contains the end time 

    if ((startindex == 0) || (endindex > nrow(df))) { 
    print("Date outside of available data") 
    return(NULL) 
    } 

    df2 <- df[ startindex:endindex, ] # subset the dataframe to include the range, but still need to trim ends 

    df2$date[1] <- starttime # trim to the start time 
    df2$date[nrow(df2)] <- endtime # trim back the end time 
    df2$status[nrow(df2)] <- df2$status[nrow(df2)-1] # status hasn't changed yet, so still the previous status 

    duration <- diff(df2$date) # vector of the time within each segment, 1 fewer elements than the dataframe 
    units(duration) <- 'days' 
    duration <- as.numeric(duration) # need to convert to numeric, or else can't divide by total duration 

    df2 <- df2[ -nrow(df2), ] # remove the last row, to make length same as the duration vector 
    df2$duration <- duration # add the duration column 

    total <- sum(df2$duration) # to allow calculations within the ddply 
    return(ddply(df2[, c('status','duration')], 'status', function(x) { # calculate by each status category 
    return(c(
     date = starttime, 
     totaldays = round(sum(x$duration), 2), 
     fraction = round(sum(x$duration)/total, 3))) 
    })) 
} 

Und unten wäre eine Probe Verwendung hinzuzufügen, das würde die Berichterstattung in etwa zwei Wochen teilen. Ich hasse die Verwendung der manuellen Datumscodierung und die Verwendung einer Schleife in R, bin aber zu unerfahren, um einen besseren Weg zu kennen.

times <- c("2016-03-01","2016-03-15","2016-04-01","2016-04-15","2016-05-01","2016-05-15") 
result <- data.frame() 
for (i in 1:(length(times) - 1)) { 
    result <- rbind(result, time_analysis(d, times[i], times[i+1])) 
} 
print(result, row.names = FALSE) 

Nachgeben (ausgenommen einige Fehler für Termine außerhalb des Bereichs):

status  date totaldays fraction 
    a 2016-03-01  5.71 0.409 
    b 2016-03-01  0.81 0.058 
    c 2016-03-01  7.43 0.532 
    a 2016-03-15  5.47 0.322 
    b 2016-03-15  2.25 0.132 
    c 2016-03-15  9.28 0.546 

===== Und nach der Einlieferung, fand eine viel schönere Art und Weise, die Zeiten zu generieren:

times <- as.character(seq(as.Date("2016-03-01"), as.Date("2016-05-15"), by = '2 weeks')) 
+0

Es würde helfen, wenn Sie die gewünschte Ausgabe für Ihre Beispieleingabe geben, damit die Antworten verifiziert werden können. Es scheint, als ob Sie einfach das 'diff()' Ihrer Datums-/Zeitspalte nehmen und das mit dem entsprechenden Status aggregieren könnten (den letzten Status ignorierend, für den Sie keine Endzeit haben). Es ist wichtig anzugeben, wie Sie Ihre Intervallpausen auswählen und was Sie für Zeiten tun möchten, die diese Intervalle umfassen. – MrFlick

+0

Etwas wie 'do.call (rbind, tapply (df $ status, Monate (df $ date), Funktion (x) {prop.table (Tabelle (x)) * 100}))', vielleicht – alistaire

+0

@alistaire Das wird nicht funktionieren, wenn der Datensatz mehrere Jahre umfasst, da der gleiche Monat in verschiedenen Jahren zusammen aggregiert werden würde. Der Aufruf 'monates()' kann durch 'format()' ersetzt werden, um sowohl das Jahr als auch den Monat, z.B. 'format (df $ datum, '% Y-% m')'. – bgoldst

Antwort

0

Hier ist ein Ansatz, der die cut.POSIXt() S3 spezifische mit einer verschachtelten data.table Aggregation kombiniert.

## define data 
library(data.table); 
dt <- data.table(date=as.POSIXct(c('2016-02-20 09:11:00','2016-03-06 02:38:00','2016-03-10 15:20:00','2016-03-10 21:20:00','2016-03-11 11:51:00','2016-03-12 01:19:00','2016-03-22 14:39:00','2016-03-23 11:37:00','2016-03-25 17:38:00','2016-03-26 01:24:00','2016-03-26 12:40:00','2016-04-12 10:28:00')),status=c('a','c','b','a','b','c','c','b','c','c','a','c')); 

## solution 
dt[,{ n1 <- .N; .SD[,.(pct=.N/n1*100),.(status)]; },.(month=cut(df$date,'month'))]; 
##   month status pct 
## 1: 2016-02-01  a 100 
## 2: 2016-03-01  c 50 
## 3: 2016-03-01  b 30 
## 4: 2016-03-01  a 20 
## 5: 2016-04-01  c 100