2014-04-29 9 views
6

Anche se i dettagli di questo sono, ovviamente, specifici per le app, nello spirito SO sto cercando di mantenere questo il più generale possibile! Il problema di base è come unire data.frames per data quando un data.frame ha date specifiche e l'altro ha un intervallo di date. In secondo luogo, la domanda chiede come gestire più osservazioni di una determinata variabile e come includerle in un output finale data.frame. Sono sicuro che alcuni di questi sono standard, ma una ricerca piuttosto completa ha rivelato poco.Range join data.frames - colonna data specifica con intervalli di date/intervalli in R

Gli oggetti mre che sto cercando di unire sono di seguito.

# 'Speeches' data.frame 
structure(list(Name = structure(c(2L, 2L, 2L, 2L, 2L, 2L, 2L, 
2L, 2L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L), .Label = c("BBB", 
"AAA"), class = "factor"), Date = structure(c(12543, 12404, 12404, 
12404, 12373, 12362, 12345, 12320, 12207, 15450, 15449, 15449, 
15449, 15449, 15449, 15449, 15449, 15448, 15448, 15448), class = "Date")), .Names =  c("Name", 
"Date"), row.names = c("1", "1.1", "1.2", "1.3", "1.4", "1.5", 
"1.6", "1.7", "1.8", "2", "2.1", "2.2", "2.3", "2.4", "2.5", 
"2.6", "2.7", "2.8", "2.9", "2.10"), class = "data.frame") 

# 'History' data.frame 
structure(list(Name = structure(c(2L, 2L, 2L, 2L, 2L, 2L, 1L, 
1L, 1L, 1L, 1L, 1L, 1L), .Label = c("BBB", "AAA"), class = "factor"), 
    Role = structure(c(1L, 2L, 3L, 3L, 3L, 4L, 1L, 2L, 3L, 3L, 
3L, 3L, 4L), .Label = c("Political groups", "National parties", 
"Member", "Substitute", "Vice-Chair", "Chair", "Vice-President", 
"Quaestor", "President", "Co-President"), class = "factor"), 
Value = structure(c(10L, 12L, 6L, 3L, 8L, 4L, 9L, 11L, 1L, 
7L, 1L, 2L, 5L), .Label = c("a", "b", "c", "d", "e", "f", 
"g", "h", "i", "j", "k", "l", "m", "n", "o"), class = "factor"), 
Role.Start = structure(c(12149, 12149, 12150, 12150, 12152, 
12150, 14439, 14439, 14441, 14503, 15358, 15411, 14441), class = "Date"), 
Role.End = structure(c(12618, 12618, 12618, 12618, 12538, 
12618, 15507, 15507, 15357, 15507, 15410, 15507, 15357), class = "Date")), .Names = c("Name", 
"Role", "Value", "Role.Start", "Role.End"), row.names = c(NA, 
13L), class = "data.frame") 

Ci sono una serie di difficoltà che sto affrontando.

1) Sebbene siano presenti dati relativi alla data e alla cronologia, nel primo sono presenti date specifiche per ciascuna voce e nella seconda è presente un intervallo di date. Idealmente, mi piacerebbe essere in grado di unire in modo che ogni voce vocale sia abbinata sia al relatore ('Nome') che alla voce della cronologia in cui cade la data del discorso.

2) L'output desiderato deve avere un data.frame o data.table con righe uguali alle osservazioni nei discorsi data.frame e colonne per Nome, Data e ciascuno dei ruoli (che verrà popolato in base al valore). Tuttavia, alcuni ruoli appaiono più volte per un determinato oratore, in una determinata data, e quindi ho bisogno di essere in grado di creare più colonne per queste istanze.

L'oggetto di seguito dà questa uscita, ma è stato costruito utilizzando un orribilmente fragile e molto lento per-ciclo:

structure(list(Name = structure(c(2L, 2L, 2L, 2L, 2L, 2L, 2L, 
2L, 2L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L), .Label = c("BBB", 
"AAA"), class = "factor"), Date = structure(c(12543, 12404, 12404, 
12404, 12373, 12362, 12345, 12320, 12207, 15450, 15449, 15449, 
15449, 15449, 15449, 15449, 15449, 15448, 15448, 15448), class = "Date"), 
`Political groups` = structure(c(2L, 2L, 2L, 2L, 2L, 2L, 
2L, 2L, 2L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L), .Label = c("i", 
"j"), class = "factor"), `National parties` = structure(c(2L, 
2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 
1L, 1L, 1L, 1L), .Label = c("k", "l"), class = "factor"), 
Member.1 = structure(c(1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 
2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L), .Label = c("f", 
"g"), class = "factor"), Member.2 = structure(c(2L, 2L, 2L, 
2L, 2L, 2L, 2L, 2L, 2L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 
1L, 1L), .Label = c("b", "c"), class = "factor"), Member.3 = structure(c(NA, 
1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, NA, NA, NA, NA, NA, NA, NA, 
NA, NA, NA, NA), .Label = "h", class = "factor"), Substitute = structure(c(1L, 
1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, NA, NA, NA, NA, NA, NA, NA, 
NA, NA, NA, NA), .Label = "d", class = "factor")), .Names = c("Name", 
"Date", "Political groups", "National parties", "Member.1", "Member.2", 
"Member.3", "Substitute"), row.names = c("1", "1.1", "1.2", "1.3", 
"1.4", "1.5", "1.6", "1.7", "1.8", "2", "2.1", "2.2", "2.3", 
"2.4", "2.5", "2.6", "2.7", "2.8", "2.9", "2.10"), class = "data.frame") 

qualsiasi aiuto e/o commenti su come migliorare questa domanda sarebbe il benvenuto!

risposta

7

Aggiornamento: In v1.9.3 +, ora sovrapposizione unisce sono implementati. Questo è un caso speciale in cui inizio e fine Date sono identici in Speeches. Possiamo eseguire questa operazione usando foverlaps() come segue:

require(data.table) ## 1.9.3+ 
setDT(Speeches) 
setDT(History) 

Speeches[, `:=`(Date2 = Date, id = .I)] 
setkey(History, Name, Role.Start, Role.End) 

ans = foverlaps(Speeches, History, by.x=c("Name", "Date", "Date2"))[, Date2 := NULL] 
ans = ans[order(id, Value)][, N := 1:.N, by=list(Name, Date, Role, id)] 
ans = dcast.data.table(ans, id+Name+Date ~ Role+N, value.var="Value") 

Questo è un caso per la gamma/intervallo aderire.

Ecco il modo data.table. Utilizza due giunti rotanti.

require(data.table) ## 1.9.2+ 
dt1 = as.data.table(Speeches) 
dt2 = as.data.table(History) 

# first rolling join - to get end indices 
setkey(dt2, Name, Role.Start) 
tmp1 = dt2[dt1, roll=Inf, which=TRUE] 

# second rolling join - to get start indices 
setkey(dt2, Name, Role.End) 
tmp2 = dt2[dt1, roll=-Inf, which=TRUE] 

# generate dt1's and dt2's corresponding row indices 
idx = tmp1-tmp2+1L 
idx1 = rep(seq_len(nrow(dt1)), idx) 
idx2 = data.table:::vecseq(tmp2, idx, sum(idx)) 

dt1[, id := 1:.N] ## needed for casting later 

# subset using idx1 and idx2 and bind them colwise 
ans = cbind(dt1[idx1], dt2[idx2, -1L, with=FALSE]) 

# a little reordering to get the output correctly (factors are a pain!) 
ans = ans[order(id,Value)][, N := 1:.N, by=list(Name, Date, Role, id)] 

# finally cast them. 
f_ans = dcast.data.table(ans, id+Name+Date ~ Role+N, value.var="Value") 

ecco l'output:

id Name  Date Political groups_1 National parties_1 Member_1 Member_2 Member_3 Substitute_1 
1: 1 AAA 2004-05-05     j     l  c  f  NA   d 
2: 2 AAA 2003-12-18     j     l  c  f  h   d 
3: 3 AAA 2003-12-18     j     l  c  f  h   d 
4: 4 AAA 2003-12-18     j     l  c  f  h   d 
5: 5 AAA 2003-11-17     j     l  c  f  h   d 
6: 6 AAA 2003-11-06     j     l  c  f  h   d 
7: 7 AAA 2003-10-20     j     l  c  f  h   d 
8: 8 AAA 2003-09-25     j     l  c  f  h   d 
9: 9 AAA 2003-06-04     j     l  c  f  h   d 
10: 10 BBB 2012-04-20     i     k  b  g  NA   NA 
11: 11 BBB 2012-04-19     i     k  b  g  NA   NA 
12: 12 BBB 2012-04-19     i     k  b  g  NA   NA 
13: 13 BBB 2012-04-19     i     k  b  g  NA   NA 
14: 14 BBB 2012-04-19     i     k  b  g  NA   NA 
15: 15 BBB 2012-04-19     i     k  b  g  NA   NA 
16: 16 BBB 2012-04-19     i     k  b  g  NA   NA 
17: 17 BBB 2012-04-19     i     k  b  g  NA   NA 
18: 18 BBB 2012-04-18     i     k  b  g  NA   NA 
19: 19 BBB 2012-04-18     i     k  b  g  NA   NA 
20: 20 BBB 2012-04-18     i     k  b  g  NA   NA 

In alternativa è anche possibile eseguire questa operazione utilizzando GenomicRanges pacchetto da Bioconductor, che si occupa di Campi abbastanza bene, soprattutto quando si richiede una colonna aggiuntiva di unirsi per (Name) oltre agli intervalli. È possibile installarlo da here.

require(GenomicRanges) 
require(data.table) 
dt1 <- as.data.table(Speeches) 
dt2 <- as.data.table(History) 
gr1 = GRanges(Rle(dt1$Name), IRanges(as.numeric(dt1$Date), as.numeric(dt1$Date))) 
gr2 = GRanges(Rle(dt2$Name), IRanges(as.numeric(dt2$Role.Start), as.numeric(dt2$Role.End))) 

olaps = findOverlaps(gr1, gr2, type="within") 
idx1 = queryHits(olaps) 
idx2 = subjectHits(olaps) 

# from here, you can do exactly as above 
dt1[, id := 1:.N] 
... 
... 
dcast.data.table(ans, id+Name+Date ~ Role+N, value.var="Value") 

Fornisce lo stesso risultato di cui sopra.

+1

Questo approccio 'data.table' (dopo alcuni test) potrebbe essere racchiuso in una bella piccola funzione (range-join e/o interval-join) per l'uso diretto. Sarebbe molto utile, penso. – Arun

+0

Questi sono entrambi fantastici. Il GenomicRanges ha funzionato meglio per il mio particolare scopo, ma sono d'accordo sul fatto che alcune funzioni data.table potrebbero essere un grande contributo generale. @jlhoward fornisce un'altra buona alternativa di seguito, che funziona anche bene. – user2728808

2

Ecco un approccio utilizzando sqldf(...) dal pacchetto sqldf. Questo produce il risultato, con le seguenti eccezioni:

  1. I Member.n colonne contengono valori in ordine alfabetico, anziché nell'ordine in cui appaiono nel frame di dati History. Quindi Member.1 conterrebbe c e Member.2 conterrà f, anziché il contrario.
  2. Il set di risultati contiene tutte le colonne relative ai ruoli come fattori, mentre questo set di risultati li ha come caratteri. Se è importante, può essere facilmente modificato.

noti che Speeches e History sono utilizzati per i frame di dati in ingresso, e io uso il vostro Output dataframe per ottenere solo ordine delle colonne.

library(sqldf) # for sqldf(...) 
library(reshape2) # for dcast(...) 

colnames(History)[4:5] <- c("Start","End") # sqldf doesn't like "." in colnames 
Speeches$id <- rownames(Speeches)   # need unique id column 
result <- sqldf("select a.id, a.Name, a.Date, b.Role, b.Value 
       from Speeches a, History b 
       where a.Name=b.Name and a.Date between b.Start and b.End") 
Roles <- aggregate(Role~Name+Date+id,result,function(x) 
    ifelse(x=="Member",paste(x,1:length(x),sep="."),as.character(x)))$Role 
result$Roles <- unlist(Roles) 
result <- dcast(result,Name+Date+id~Roles,value.var="Value") 
result <- result[order(result$id),] # re-order the rows 
result <- result[,colnames(Output)] # re-order the columns 

Spiegazione

  • Innanzitutto, occorre una colonna id Speeches per differenziare tra le colonne replicate nel risultato. Quindi usiamo i nomi delle righe per quello.
  • In secondo luogo, viene utilizzato sqldf(...) per unire le tabelle Speeches e History in base ai propri criteri. Perché vuoi che le date corrispondano in base a un intervallo, questo potrebbe essere l'approccio migliore.
  • In terzo luogo, dobbiamo convertire più istanze di "Membro" in "Membro.1", "Membro.2", ecc. Lo facciamo utilizzando aggregate(...) e paste(...).
  • In quarto luogo, dobbiamo convertire il risultato di sql, che è in formato "lungo" (tutti i valori in una colonna, distinti da una seconda colonna Ruoli), in formato "ampio", valori per ciascun ruolo in colonne diverse . Lo facciamo usando dcast(...).
  • Infine, riordina righe e colonne in modo che siano coerenti con il risultato.
+0

Anche questa è un'ottima risposta. Preferisco marginalmente la soluzione di @ Arun perché non richiede l'uso di sqldf. Grazie molto. – user2728808