2016-06-14 8 views
1

Ich versuche, einige parametrisierte dplyr Manipulationen zu tun. Das einfachste reproduzierbare Beispiel der Wurzel des Problems zum Ausdruck bringt, ist dies:Wie übergeben '...' Argument in eine interp() Formel innerhalb von Lazyeval

# Data 
test <- data.frame(group = rep(1:5, each = 2), 
        value = as.integer(c(NA, NA, 2, 3, 3, 5, 7, 8, 9, 0))) 

> test 
    group value 
1  1 NA 
2  1 NA 
3  2  2 
4  2  3 
5  3  3 
6  3  5 
7  4  7 
8  4  8 
9  5  9 
10  5  0 

# Summarisation example, this is what I'd like to parametrise 
# so that I can pass in functions and grouping variables dynamically 

test.summary <- test %>% 
       group_by(group) %>% 
       summarise(group.mean = mean(value, na.rm = TRUE)) 

> test.summary 
Source: local data frame [5 x 2] 

    group group.mean 
    <int>  <dbl> 
1  1  NaN 
2  2  2.5 
3  3  4.0 # Correct results 
4  4  7.5 
5  5  4.5 

Dies ist, wie ich allein weit gekommen

# This works fine, but notice there's no 'na.rm = TRUE' passed in 

doSummary <- function(d_in = data, func = 'mean', by = 'group') { 
# d_in: data in 
# func: required function for summarising 
# by: the variable to group by 
# NOTE: the summary is always for the 'value' column in any given dataframe 

    # Operations for summarise_ 
    ops <- interp(~f(value), 
        .values = list(f = as.name(func), 
           value = as.name('value')))   
    d_out <- d_in %>% 
      group_by_(by) %>% 
      summarise_(.dots = setNames(ops, func)) 
} 

> doSummary(test) 
Source: local data frame [5 x 2] 

    group mean(value) 
    <int>  <dbl> 
1  1   NA 
2  2   2.5 
3  3   4.0 
4  4   7.5 
5  5   4.5 

Der Versuch, mit dem ‚na.rm‘ Parameter

# When I try passing in the 'na.rm = T' parameter it breaks 
doSummary.na <- function(d_in = data, func = 'mean', by = 'group') { 
    # Doesn't work 
    ops <- interp(~do.call(f, args), 
        .values = list(f = func, 
           args = list(as.name('value'), na.rm = TRUE))) 

    d_out <- d_in %>% 
      group_by_(by) %>% 
      summarise_(.dots = setNames(ops, func)) 
} 

> doSummary.na(test) 
Error: object 'value' not found 

Vielen Dank für Ihre Hilfe!

+0

Und 'interp' kommt von ...? – nicola

+2

@pfabri Das Schlüssel-Bit der fehlenden Informationen ist, dass 'interp()' aus Paket lazyeval ist, gibt es andere Funktionen mit dem gleichen Namen, zum Beispiel in akima – Miff

+0

@pfabri Ich kann nicht sagen, ob das folgende in Ihrem Fall funktionieren könnte , obwohl es nicht direkt Ihre Frage beantwortet 'interp (~ do.call (f, args), .values ​​= Liste (f =' mean ', args = Liste (na.rm = TRUE)))'. – Miff

Antwort

3

Ihr Titel erwähnt ..., aber Ihre Frage nicht. Wenn wir nicht mit ... befassen müssen, erhält die Antwort viel einfacher, weil wir do.call überhaupt nicht benötigen, können wir die Funktion direkt nennen; einfach Ihre ops Definition mit ersetzen:

ops = interp(~f(value, na.rm = TRUE), 
      f = match.fun(func), value = as.name('value')) 

Bitte beachte, dass ich match.fun hier statt as.name benutzt habe. Dies ist im Allgemeinen eine bessere Idee, da es "genau wie R" für die Funktionssuche funktioniert. Als Folge davon kann man nicht einfach einen Funktionsnamen Charakter als Argument übergeben, sondern auch einen Funktionsnamen oder eine anonyme Funktion:

doSummary.na(test, function (x, ...) mean(x, ...)/sd(x, ...)) # x̂/s?! Whatever. 

Apropos, Ihr Versuch, die Spaltennamen zu setzen auch nicht; Sie müssen ops in eine Liste setzen, dass zu beheben:

d_in %>% 
    group_by_(by) %>% 
    summarise_(.dots = setNames(list(ops), func)) 

... weil .dots eine Liste von Operationen erwartet (und setNames erwartet auch einen Vektor/Liste). Dieser Code funktioniert jedoch nicht mehr, wenn Sie ein func-Objekt an die Funktion übergeben, die kein Zeichenvektor ist. Um dies zu robusteren, so etwas wie folgt verwenden:

fname = if (is.character(func)) { 
     func 
    } else if (is.name(substitute(func))) { 
     as.character(substitute(func)) 
    } else { 
     'func' 
    } 

d_in %>% 
    group_by_(by) %>% 
    summarise_(.dots = setNames(list(ops), fname)) 

Die Dinge werden komplizierter, wenn Sie tatsächlich zulassen wollen ... vorbei, anstelle der bekannten Argumente, weil (soweit ich weiß) gibt es einfach keine direkte Art und Weise zu übergeben ... über interp, und, wie Sie, kann ich nicht die do.call Ansatz zu arbeiten.

Das Paket bietet die sehr nette Funktion make_call, die uns auf dem Weg zu einer Lösung hilft. Das obige könnte auch geschrieben werden als

# Not good. :-(
ops = make_call(as.name(func), list(as.name('value'), na.rm = TRUE)) 

Dies funktioniert. ABER nur, wenn func als Zeichenvektor übergeben wird. Wie oben erläutert, ist dies einfach nicht flexibel.

jedoch hüllt make_call einfach Basis R as.call und das können wir direkt verwenden:

ops = as.call(list(match.fun(func), as.name('value'), na.rm = TRUE)) 

Und jetzt können wir einfach passieren ... auf:

doSummary = function (d_in = data, func = 'mean', by = 'group', ...) { 
    ops = as.call(list(match.fun(func), as.name('value'), ...)) 

    fname = if (is.character(func)) { 
      func 
     } else if (is.name(substitute(func))) { 
      as.character(substitute(func)) 
     } else { 
      'func' 
     } 

    d_in %>% 
     group_by_(by) %>% 
     summarize_(.dots = setNames(list(ops), fname)) 
} 

Um klar zu sein: die gleiche sein könnte erreicht mit interp, aber ich denke, dies würde manuell ein formula Objekt aus einer Liste erstellen, was zu tun, sehr ähnlich wie in meiner Lösung, und dann (redundant) Aufruf interp über das Ergebnis.

Im Allgemeinen finde ich, dass zwar unglaublich elegant ist, aber in einigen Situationen bietet base R einfachere Lösungen. Insbesondere ist interp ein leistungsfähiger substitute Ersatz, aber bquote, eine ziemlich wenig genutzte Basis-R-Funktion, bietet bereits viele der gleichen syntaktischen Vorteile. Der große Vorteil von -Objekten besteht darin, dass sie im Gegensatz zu Basis-R-Ausdrücken ihre Evaluierungsumgebung mit sich herumtragen. Dies wird jedoch nicht immer benötigt.