Fonctions de niveau supérieur dans R - Y a-t-il un opérateur de composition officiel ou une fonction de curry?
-
19-09-2019 - |
Question
Je peux créer un opérateur de composition dans R:
`%c%` = function(x,y)function(...)x(y(...))
À utiliser comme ceci:
> numericNull = is.null %c% numeric
> numericNull(myVec)
[2] TRUE FALSE
Mais je voudrais savoir s'il existe un ensemble officiel de fonctions pour faire ce genre de chose et d'autres opérations telles que le curry dans R. Il s'agit en grande partie de réduire le nombre de supports, de mots clés de fonction, etc. dans mon code.
Ma fonction de curry:
> curry=function(...){
z1=z0=substitute(...);z1[1]=call("list");
function(...){do.call(as.character(z0[[1]]),
as.list(c(eval(z1),list(...))))}}
> p = curry(paste(collapse=""))
> p(letters[1:10])
[1] "abcdefghij"
C'est particulièrement sympa pour EG agrégé:
> df = data.frame(l=sample(1:3,10,rep=TRUE), t=letters[1:10])
> aggregate(df$t,df["l"],curry(paste(collapse="")) %c% toupper)
l x
1 1 ADG
2 2 BCH
3 3 EFIJ
Que je trouve beaucoup plus élégant et modifiable que:
> aggregate(df$t, df["l"], function(x)paste(collapse="",toupper(x)))
l x
1 1 ADG
2 2 BCH
3 3 EFIJ
Fondamentalement, je veux savoir - cela a-t-il déjà été fait pour R?
La solution
Le lieu standard pour la programmation fonctionnelle dans R est maintenant le functional
bibliothèque.
De la bibliothèque:
fonctionnel: curry, composer et autres fonctions d'ordre supérieur
Exemple:
library(functional)
newfunc <- Curry(oldfunc,x=5)
CRAN:https://cran.r-project.org/web/packages/fonctional/index.html
PS: cette bibliothèque substitue le ROxigen
bibliothèque.
Autres conseils
Ces deux fonctions existent réellement dans la roxygen
forfait (Voir le code source ici) de Peter Danenberg (était à l'origine basé sur Solution de Byron Ellis sur R-Help):
Curry <- function(FUN,...) {
.orig = list(...);
function(...) do.call(FUN,c(.orig,list(...)))
}
Compose <- function(...) {
fs <- list(...)
function(...) Reduce(function(x, f) f(x),
fs,
...)
}
Notez l'utilisation du Reduce
fonction, qui peut être très utile lorsque vous essayez de faire une programmation fonctionnelle dans R. Voir? Réduisez pour plus de détails (qui couvre également d'autres fonctions telles que Map
et Filter
).
Et votre exemple de curry (légèrement différent dans cette utilisation):
> library(roxygen)
> p <- Curry(paste, collapse="")
> p(letters[1:10])
[1] "abcdefghij"
Voici un exemple pour montrer l'utilité de Compose
(Application de trois fonctions différentes aux lettres):
> Compose(function(x) x[length(x):1], Curry(paste, collapse=""), toupper)(letters)
[1] "ZYXWVUTSRQPONMLKJIHGFEDCBA"
Et votre dernier exemple fonctionnerait comme ceci:
> aggregate(df[,"t"], df["l"], Compose(Curry(paste, collapse=""), toupper))
l x
1 1 ABG
2 2 DEFH
3 3 CIJ
Enfin, voici une façon de faire la même chose avec plyr
(pourrait aussi facilement être fait avec by
ou aggregate
comme déjà montré):
> library(plyr)
> ddply(df, .(l), function(df) paste(toupper(df[,"t"]), collapse=""))
l V1
1 1 ABG
2 2 DEFH
3 3 CIJ
Il y a une fonction appelée curry dans le roxygène forfait.
Trouvé via cette conversation sur les archives du courrier R.
Une approche plus complexe est requise si vous souhaitez que les «noms» des variables passent avec précision.
Par exemple, si vous le faites plot(rnorm(1000),rnorm(1000))
Ensuite, vous obtiendrez de belles étiquettes sur vos axes X et Y. Un autre exemple de ceci est data.frame
> data.frame( rnorm(5), rnorm(5), first=rpois(5,1), second=rbinom(5,1,0.5) )
rnorm.5. rnorm.5..1 first second
1 0.1964190 -0.2949770 0 0
2 0.4750665 0.8849750 1 0
3 -0.7829424 0.4174636 2 0
4 1.6551403 1.3547863 0 1
5 1.4044107 -0.4216046 0 0
Non pas que les données. Frame a attribué des noms utiles aux colonnes.
Certaines implémentations de Curry peuvent ne pas le faire correctement, conduisant à des noms de colonne et à des étiquettes de tracé illisibles. Au lieu de cela, j'utilise maintenant quelque chose comme ceci:
Curry <- function(FUN, ...) {
.orig = match.call()
.orig[[1]] <- NULL # Remove first item, which matches Curry
.orig[[1]] <- NULL # Remove another item, which matches FUN
function(...) {
.inner = match.call()
.inner[[1]] <- NULL # Remove first item, which matches Curry
do.call(FUN, c(.orig, .inner), envir=parent.frame())
}
}
C'est assez complexe, mais je pense que c'est correct. match.call
Attrapera tous les args, se souvenant pleinement de ce que les expressions ont défini les args (cela est nécessaire pour de belles étiquettes). Le problème est qu'il attrape trop d'args - pas seulement le ...
mais aussi le FUN
. Il se souvient également du nom de la fonction qui est appelée (Curry
).
Par conséquent, nous voulons supprimer ces deux premières entrées dans .orig
pour que .orig
correspond vraiment au ...
arguments. C'est pourquoi nous faisons .orig[[1]]<-NULL
Deux fois - à chaque fois, supprime une entrée et déplace tout le reste vers la gauche.
Cela complète la définition et nous pouvons maintenant faire ce qui suit pour obtenir exactement le même que ci-dessus
Curry(data.frame, rnorm(5), rnorm(5) )( first=rpois(5,1) , second=rbinom(5,1,0.5) )
Une dernière note sur envir=parent.frame()
. J'ai utilisé cela pour m'assurer qu'il n'y aura pas de problème si vous avez des variables externes appelées «.inner» ou «.orig». Maintenant, toutes les variables sont évaluées à l'endroit où le curry est appelé.