2016-03-20 9 views
9

Ho dati in cui la combinazione di due variabili ("ManufactererId" e "ProductId") costituisce chiavi/identificatori univoci. I dati si presenta così:Come evitare che le righe con indici/chiavi duplicati vengano aggiunte ad un data.frame?

my.data <- data.frame(ManufactererId = c(1, 1, 2, 2), 
         ProductId = c(1, 2, 1, 7), 
         Price = c(12.99, 149.00, 0.99, 3.99)) 
my.data 
# ManufactererId ProductId Price 
# 1    1   1 12.99 
# 2    1   2 149.00 
# 3    2   1 0.99 
# 4    2   7 3.99 

voglio assicurare che non posso aggiungere accidentalmente un'altra riga con un paio di ManufactererId - ProductID uguale a ciò che è già presente nella tabella (come il vincolo univoco su una tabella di database) .

Cioè, se provo ad aggiungere una riga con ManufactererId = 2 e ProductId = 7 al mio frame di dati:

my.data <- rbind(my.data, data.frame(ManufactererId = 2, ProductId = 7, Price = 120.00)) 

... dovrebbe fallire con un errore. Come può essere realizzato?

Oppure devo usare un tipo di dati diverso?

risposta

7

1) zoo Se questo è conveniente o meno dipende da quali operazioni si desidera eseguire ma gli oggetti zoo hanno indici univoci. Possiamo costruire un indice di testo incollando insieme le due colonne Id.

library(zoo) 
z <- with(my.data, zoo(Price, paste(ManufactererId, ProductId))) 

z <- c(z, zoo(90, "1 1")) # Error, not appended 
z <- c(z, zoo(90, "1 3")) # OK 

noti che la parte di dati di un oggetto zoo può essere un vettore come illustrato sopra o una matrice in caso di avere più di prezzo nei dati.

2) SQLite Questo potrebbe essere fatto con uno qualsiasi di un certo numero di database ma useremo SQLite qui. Per prima cosa creiamo una tabella con un indice univoco in un database SQLite e quindi inseriamo le righe.

library(RSQLite) 

con <- dbConnect(SQLite()) 
dbWriteTable(con, "my", my.data, row.names = FALSE) 
dbGetQuery(con, "create unique index ix on my(ManufactererId, ProductId)") 

dbGetQuery(con, sprintf("insert into my values(%d, %d, %d)", 1, 1, 99)) # error 
dbGetQuery(con, sprintf("insert into my values(%d, %d, %d)", 1, 13, 90)) # OK 
+0

Aggiunto secondo approccio. –

6

si può fare qualcosa di simile: Dove keys è la vostra unica-chiave

append_save <- function(DF, to_be_appended, keys=c("ManufactererId", "ProductId")){ 
    if(ncol(DF) != ncol(to_be_appended) || !all(names(DF) %in% names(to_be_appended))){ 
    stop("must have the same columns") 
    } 
    if(nrow(merge(DF, to_be_appended, by=keys))==0){ 
    rbind(DF, to_be_appended) 
    } else { 
    stop("Trying to append douplicated indices") 
    } 
} 

prova è:

to_be_appended = data.frame(ManufactererId=2,ProductId=17,Price=3.99) 
append_save(my.data, to_be_appended) # works 
to_be_appended_err = data.frame(ManufactererId=2,ProductId=7,Price=3.99) 
append_save(my.data, to_be_appended_err) # error 

Se aggiungete i dati solo in base a chiave colonne si potrebbe utilizzare data.table come segue:

append_save <- function(DF, to_be_appended, keys=c("ManufactererId", "ProductId")){ 
    if(!all(keys %in% names(to_be_appended))){ 
    stop("key-columns must be present") 
    } 
    if(nrow(data.table::merge(DF, to_be_appended, on=keys))==0){ 
    data.table::setDF(data.table::rbindlist(list(DF, to_be_appended), fill = TRUE))[] 
    } else { 
    stop("Trying to append douplicated indices") 
    } 
} 
1

Un modo per fare ciò nella base R è usare un environment come un dizionario o come oggetto hash-map. my.dict < - nuovo.ENV()

primo luogo, scrivere alcune funzioni helper

make_key <- function(ManufactererId, ProductId) 
    paste(ManufactererId, ProductId) 

set_value <- function(key, value, dict){ 
     ## checking here assures desired behavior 
     if(any(key %in% names(dict))) 
      stop("This key has been used") 
     assign(key, value, envir=dict) 
} 

allora, è possibile generare chiavi come

keys <- make_key(my.data[[1]], my.data[[2]]) 

per impostare i valori, è necessario essere un po 'più attenti

# don't just do this as the first element is used by assign 
# set_value(keys, my.data[[3]], dict=my.dict) 

mapply(set_value, keys, my.data[[3]], MoreArgs = list(dict=my.dict)) 
ls.str(my.dict) # better than str for environments 
# 1 1 : num 13 
# 1 2 : num 149 
# 2 1 : num 0.99 
# 2 7 : num 3.99 

set_value("1 1", 4, my.dict) 
# Error in set_value("1 1", 4, my.dict) : This key has been used 
0

Un modo semplice per creare nuovi dati escludendo i duplicati:

library(data.table) 
my.data = data.table(ManufactererId = c(1, 1, 2, 2), 
        ProductId = c(1, 2, 1, 7), 
        Price = c(12.99, 149.00, 0.99, 3.99), 
        key = c("ManufactererId","ProductId")) 
x = my.data # my data will be called 'x' 
y = data.table(ManufactererId = 2, ProductId = 7, Price = 120.00) 
rbind(x, y[!x, on=key(x)]) 
# ManufactererId ProductId Price 
#1:    1   1 12.99 
#2:    1   2 149.00 
#3:    2   1 0.99 
#4:    2   7 3.99 

Anche se non è necessario impostare una chiave, ma solo fornire carattere vettoriale di on argomento direttamente. Penso che valga la pena usare una chiave che rifletta semplicemente le nostre aspettative di business riguardo alla struttura dei dati.


Se si vuole generare un errore in questo caso è possibile utilizzare il seguente:

unique.rbind = function(x, y, by=key(x)) { 
    if (nrow(x[y, nomatch=0L, on=by])) stop("duplicates in 'y'") 
    rbind(x, y) 
} 
unique.rbind(x, y) 
# Error in unique.rbind(x, y) : duplicates in 'y' 

Nessuno di y file viene inserito in caso di errore.

Problemi correlati