2011-08-05 10 views
40

To naprawdę zakwestionowało moją zdolność do debugowania kodu R.Nie znaleziono błędu obiektu z ddply wewnątrz funkcji

Chcę użyć ddply(), aby zastosować te same funkcje do różnych kolumn, które są kolejno nazwane; na przykład. a, b, c. Aby to zrobić, zamierzam wielokrotnie podawać nazwę kolumny jako ciąg znaków i używać funkcji eval(parse(text=ColName)), aby ta funkcja mogła się do niej odwoływać. Chwyciłem tę technikę z innej odpowiedzi.

To działa dobrze, dopóki nie wstawię ddply() do innej funkcji. Oto przykładowy kod:

# Required packages: 
library(plyr) 

myFunction <- function(x, y){ 
    NewColName = "a" 
    z = ddply(x, y, summarize, 
      Ave = mean(eval(parse(text=NewColName)), na.rm=TRUE) 
    ) 
    return(z) 
} 

a = c(1,2,3,4) 
b = c(0,0,1,1) 
c = c(5,6,7,8) 
df = data.frame(a,b,c) 
sv = c("b") 

#This works. 
ColName = "a" 
ddply(df, sv, summarize, 
     Ave = mean(eval(parse(text=ColName)), na.rm=TRUE) 
) 

#This doesn't work 
#Produces error: "Error in parse(text = NewColName) : object 'NewColName' not found" 
myFunction(df,sv) 

#Output in both cases should be 
# b Ave 
#1 0 1.5 
#2 1 3.5 

Jakieś pomysły? NewColName jest nawet zdefiniowane wewnątrz funkcji!

Pomyślałem, że odpowiedź na to pytanie, loops-to-create-new-variables-in-ddply, może mi pomóc, ale zrobiłem wystarczająco dużo headbangingu na dzisiaj i czas podnieść rękę i poprosić o pomoc.

Odpowiedz

14

Można to zrobić za pomocą kombinacji do.call i call do budowy połączenia w środowisku, w którym NewColName jest nadal widoczny:

myFunction <- function(x,y){ 
NewColName <- "a" 
z <- do.call("ddply",list(x, y, summarize, Ave = call("mean",as.symbol(NewColName),na.rm=TRUE))) 
return(z) 
} 

myFunction(d.f,sv) 
    b Ave 
1 0 1.5 
2 1 3.5 
3

Wygląda na to, że masz problem ze środowiskiem. Globalna cesja rozwiązuje problem, ale kosztem czyjejś duszy:

library(plyr) 

a = c(1,2,3,4) 
b = c(0,0,1,1) 
c = c(5,6,7,8) 
d.f = data.frame(a,b,c) 
sv = c("b") 

ColName = "a" 
ddply(d.f, sv, summarize, 
     Ave = mean(eval(parse(text=ColName)), na.rm=TRUE) 
) 

myFunction <- function(x, y){ 
    NewColName <<- "a" 
    z = ddply(x, y, summarize, 
      Ave = mean(eval(parse(text=NewColName)), na.rm=TRUE) 
    ) 
    return(z) 
} 

myFunction(x=d.f,y=sv) 

eval patrzy w parent.frame (1). Więc jeśli zamiast definiować NewColName poza MyFunction powinno działać:

rm(NewColName) 
NewColName <- "a" 
myFunction <- function(x, y){ 

    z = ddply(x, y, summarize, 
      Ave = mean(eval(parse(text=NewColName)), na.rm=TRUE) 
    ) 
    return(z) 
} 
myFunction(x=d.f,y=sv) 

Korzystając get wyciągnąć my.parse z wcześniejszego środowiska, możemy się dużo bliżej, ale wciąż muszą przechodzić curenv jako globalny:

myFunction <- function(x, y){ 
    NewColName <- "a" 
    my.parse <- parse(text=NewColName) 
    print(my.parse) 
    curenv <<- environment() 
    print(curenv) 

    z = ddply(x, y, summarize, 
      Ave = mean(eval(get("my.parse" , envir=curenv)), na.rm=TRUE) 
    ) 
    return(z) 
} 

> myFunction(x=d.f,y=sv) 
expression(a) 
<environment: 0x0275a9b4> 
    b Ave 
1 0 1.5 
2 1 3.5 

podejrzewam, że ddply ocenia w .GlobalEnv już, dlatego wszystkie parent.frame() i sys.frame() strategii Próbowałem nie powiodło się.

+0

Podejrzewam, że rozwiązanie może wymagać funkcji @Hadley :-) –

+0

Wysiłek w próbowaniu tych wszystkich. Grupowe uderzenie głową jest zawsze doceniane ...... Zachowam moją duszę. –

9

ja czasami napotkasz problemy, jak to podczas łączenia ddply z summarize lub transform lub czegoś, a nie jest na tyle silny, aby odgadnąć tajniki nawigacji w różnych środowiskach bywam na bok, krok ten problem, po prostu nie używając summarize i zamiast korzystania własną funkcję anonimową:

myFunction <- function(x, y){ 
    NewColName <- "a" 
    z <- ddply(x, y, .fun = function(xx,col){ 
          c(Ave = mean(xx[,col],na.rm=TRUE))}, 
       NewColName) 
    return(z) 
} 

myFunction(df,sv) 

Oczywiście, nie jest to koszt do robienia tych rzeczy „ręcznie”, ale często unika się bólu głowy radzenia sobie z problemami oceny, które pochodzą z połączenia ddply i summarize. To nie znaczy, oczywiście, że Hadley nie pojawi się roztworem ...

+2

Dopóki nie naprawię błędu, jest to zalecane rozwiązanie. Zauważ, że możesz użyć 'transform' etc wewnątrz twojej anonimowej funkcji. – hadley

+0

@joran wdrożyłem Twoje rozwiązanie i zadziałało to dla mnie. Byłem po prostu ciekawy, dlaczego istnieje ten problem z zakresu w ddply? czy to dlatego, że podsumowanie tworzy nową ramkę danych, która nie ma dostępu do tej nazwy? –

+0

@ user3801801 Ma to związek z niestandardową oceną funkcji argumentów funkcji. Musiałbym przejrzeć kod źródłowy, aby przypomnieć sobie konkretny problem, ale w zasadzie ma to związek z tym, jak R wie gdzie oceniać argumenty (tj. W kontekście obecnego otoczenia, w globalnym środowisku, gdzieś w pomiędzy). – joran

4

Problem leży w kodzie samego pakietu plyr. W funkcji podsumowania istnieje linia eval(substitute(...),.data,parent.frame()). Powszechnie wiadomo, że parent.frame() może robić całkiem fajne i nieoczekiwane rzeczy. T

Rozwiązanie @James jest bardzo miłym obejściem, , ale jeśli dobrze pamiętam, sam @Hadley powiedział wcześniej, że pakiet plyr nie był przeznaczony do użycia w ramach funkcji.

Przepraszam, myliłem się tutaj. Wiadomo jednak, że w tej chwili pakiet plyr stwarza problemy w takich sytuacjach.

Dlatego dam ci rozwiązanie bazowy dla problemu:

myFunction <- function(x, y){ 
    NewColName = "a" 
    z = aggregate(x[NewColName],x[y],mean,na.rm=TRUE) 
    return(z) 
} 
> myFunction(df,sv) 
    b a 
1 0 1.5 
2 1 3.5 
+0

+1 Za przyjęcie mojego rozwiązania "uniknij podsumowanie" i dostarczenie rzeczywistego wyjaśnienia problemu. ;) – joran

+0

+1 zdecydowanie za poświęcenie czasu na wyjaśnienie problemu parent.frame(). Wydaje się dziwne, że funkcja nie może być użyta w innej funkcji, ponieważ zmusza do pisania ciągłego kodu. Może @Hadley może komentować. –

+0

Oczywiście nigdy nie twierdziłem, że plyr nie jest przeznaczony do użycia w ramach funkcji - zawsze mówiłem, że jest to błąd, którego obecnie brakuje mi do zrozumienia :( – hadley

22

Dzisiejsze rozwiązanie na to pytanie jest, aby summarize do here(summarize). na przykład

myFunction <- function(x, y){ 
    NewColName = "a" 
    z = ddply(x, y, here(summarize), 
      Ave = mean(eval(parse(text=NewColName)), na.rm=TRUE) 
    ) 
    return(z) 
} 

here(f), dodany do plyr w grudniu 2012 roku, oddaje aktualny kontekst.

+0

Brilliant! Używając lubridate i plyr razem, upewnij się, że konkretnie odwołujesz się do plyr :: here() (jako, że lubridate niestety redefiniuje tutaj()). –

+0

więcej informacji: https://github.com/hadley/plyr/ problemy/3 – Lennert

Powiązane problemy