2014-07-16 3 views
6

Meine Gruppe schreibt eine Menge Code mit data.table und wir gelegentlich von der 'Invalid .internal.selfref erkannt und durch eine Kopie von der ganze Tisch ... warnte. Dieses Verhalten kann unseren Code beschädigen, wenn eine Datentabelle durch Verweis auf eine Funktion übergeben wird, und ich versuche herauszufinden, wie ich sie umgehen kann.Umgang mit ungültigen Eigenreferenz in R data.table bei der Übergabe durch Verweis auf eine Funktion

Angenommen, ich habe eine Funktion, die als Nebeneffekt eine Spalte zu einer data.table hinzufügt - beachten Sie, dass die ursprüngliche data.table nicht zurückgegeben wird.

foo <- function(mydt){ 
    mydt[, c := c("a", "b")] 
    return(123) 
) 

> x<- data.table(a=c(1,2), b=c(3,4)) 
> foo(x) 
[1] 123 
> x 
    a b c 
1: 1 3 a 
2: 2 4 b 

x wurde mit der neuen Spalte aktualisiert. Dies ist das gewünschte Verhalten.

Nehmen wir nun an etwas passiert, dass die interne Selbst ref in x bricht:

> x<- data.table(a=c(1,2), b=c(3,4)) 
> x[["a"]] <- c(7,8) 
> foo(x) 
[1] 123 
Warning message: 
In `[.data.table`(mydt, , `:=`(c, c("a", "b"))) : 
Invalid .internal.selfref detected and fixed by taking a copy ... 

> x 
    a b 
1: 7 3 
2: 8 4 

Ich verstehe, was (fast) passiert ist. Die Konstruktion [["a"]] ist nicht data.table friendly; x wurde in einen Datenrahmen und dann zurück in eine Datentabelle konvertiert, was die internen Abläufe irgendwie durcheinander brachte. Dann innerhalb von foo(), während der Referenzoperation des Hinzufügens einer Spalte wurde dieses Problem erkannt, und eine Kopie von mydt wurde erstellt; die neue Spalte 'c' wurde zu mydt hinzugefügt. Durch diese Kopieroperation wurde jedoch die Beziehung zwischen übergebenem Verweis zwischen x und mydt getrennt, sodass die zusätzlichen Spalten nicht zu x gehören.

Die Funktion foo() wird von verschiedenen Personen verwendet und es wird schwierig sein, sich vor ungültigen internen Selfref-Situationen zu schützen. Jemand da draußen könnte leicht etwas wie x [["a"]] machen, was zu einer ungültigen Eingabe führen würde. Ich versuche herauszufinden, wie ich damit umgehen kann.

Bisher habe ich diese Idee, am Anfang von foo():

if(!data.table:::selfrefok(mydt)) stop("mydt is corrupt.") 

Das gibt uns zumindest eine Chance, das Problem Spek, aber es ist nicht sehr freundlich für die Nutzer von foo() , weil die Art und Weise, in der diese Eingänge beschädigt werden können, ziemlich undurchsichtig sein kann. Idealerweise möchte ich in der Lage sein, beschädigte Eingaben zu korrigieren und die gewünschte Funktionalität von foo() beizubehalten. Aber ich kann nicht sehen, wie, wenn ich meinen Code nicht so umstrukturiere, dass foo mydt zurückgibt und es x im aufrufenden Bereich zuweist, was möglich aber nicht ideal ist. Irgendwelche Ideen?

Antwort

3

Sie sollen das Ganze der Warnung lesen ....

Dann würden Sie

zu einem frühen Zeitpunkt bemerken, diese data.table wurde von R kopiert (oder manuell angelegt wurde Struktur() oder ähnlich). Vermeiden Sie Schlüssel < -, Namen < - und attr < - die in R derzeit (und seltsamerweise) kann die gesamte data.table kopieren.

[[<- ist ähnlich names<- und attr<-, dass sie eine Kopie erstellen.

können Sie sicherstellen, dass das Nebenreferenzverhalten ist das Gespräch mit Ersatz und dann bewerten in dem übergeordneten Rahmen

foo <- function(x) { 
    l <- substitute(x[,c := 'a'], as.list(match.call())['x']); 
    eval.parent(l) 
    return(123)} 

xx<- data.table(a=c(1,2), b=c(3,4)) 
xx[["a"]] <- c(7,8) 
foo(xx) 
# [1] 123 
# Warning message: ..... 

# but it now works! 
xx 
# a b c 
# 1: 7 3 a 
# 2: 8 4 a 

Die Warnung bleibt zu konstruieren, aber die Funktion wie gewünscht funktioniert.

+0

Ja, ich weiß, es wird eine Kopie erstellen. Die Herausforderung besteht darin, dass ich nicht garantieren kann, dass Benutzer meines Codes nicht [[<-] verwenden und somit fehlerhafte Datentabellen übergeben. Also muss ich diese Tabellen mit fehlerhaften Datentypen irgendwie so reparieren, dass das Verhalten der einzelnen Funktionen in Bezug auf die Referenz beibehalten wird, oder das Nebenreferenzmuster in foo() vermeiden. Ich fange an zu denken, dass letzteres die einzige Option ist. – pteehan

+0

@pteehan - siehe meine Bearbeitung. – mnel

2

@pteehan, große Frage!Meiner Meinung nach wäre eine viel sauberere Lösung, die Überbelegung während des Zuweisungsschritts selbst wiederherzustellen, mit einer Warnung, die im Grunde sagt "tun Sie es nicht!".

Der Weg dazu wäre durch [[<-.data.table Methode, die derzeit nicht existiert. Wenn ich etwas nicht vermisse, wäre es eine gute Ergänzung, deren Zweck ist nicht zu ermutigen, es zu verwenden, sondern Fälle wie diese zu fangen und Menschen auf die richtige Verwendung (mit einer Warnung) und gleichzeitig zu lenken Zeit, um die Überbelegung wiederherzustellen.

Grob:

`[[<-.data.table` <- function(x, i, j, value) { 
    warning("Don't do this. Use := instead.") 
    call = sys.call() 
    call[[1L]] = `[[<-.data.frame` 
    ans = copy(eval(call, envir=parent.frame())) 
} 

foo <- function(mydt) { 
    mydt[, c := c("a", "b")] 
    return(123) 
} 
x <- data.table(a = c(1,2), b = c(3,4)) 

x[["a"]] <- c(7,8) 
# Warning message: 
# In `[[<-.data.table`(`*tmp*`, "a", value = c(7, 8)) : 
# Don't do this. Use := instead. 

data.table:::selfrefok(x) 
# [1] 1 

foo(x) 
# [1] 123 

x 
# a b c 
# 1: 7 3 a 
# 2: 8 4 b 

Etwas in dieser Richtung sollte eine saubere Lösung bieten, glaube ich. Vielleicht sollte dies umgesetzt werden.

PS: This post erklärt im Detail, warum die Warnung in Ihrer Frage auftritt.

Verwandte Themen