Envoi de données "rbind" et "cbind" pour a".cadre`
Background
le mécanisme d'expédition du R
fonctions rbind()
et cbind()
n'est pas standard. J'ai exploré certaines possibilités de l'écriture rbind.myclass()
ou cbind.myclass()
fonctions lorsque l'un des arguments est un data.frame
, mais jusqu'à présent, je n'ai pas une approche satisfaisante. Ce post se concentre sur rbind
, mais il en va de même pour cbind
.
Problème
Laissez-nous créer un rbind.myclass()
fonction qui fait simplement écho quand il a été appelé.
rbind.myclass <- function(...) "hello from rbind.myclass"
nous créons un objet de classe myclass
, et les appels suivants à rbind
tous les
envoyer correctement à rbind.myclass()
a <- "abc"
class(a) <- "myclass"
rbind(a, a)
rbind(a, "d")
rbind(a, 1)
rbind(a, list())
rbind(a, matrix())
Toutefois, lorsque l'un des arguments (ce qui n'a pas besoin d'être le premier), rbind()
appel base::rbind.data.frame()
au lieu de:
rbind(a, data.frame())
Ce comportement est un peu surprenant, mais c'est bien documentée dans la
dispatch
article de rbind()
. Les conseils donnés est le suivant:
Si vous voulez combiner d'autres objets avec des trames de données, il peut être nécessaire de les contraindre d'abord à des cadres de données.
dans la pratique, ce conseil peut être difficile à mettre en oeuvre. La Conversion en base de données peut supprimer des informations essentielles sur la classe. En outre, l'utilisateur qui pourrait ne pas être au courant du Conseil peut être bloqué avec une erreur ou un résultat inattendu après avoir émis la commande rbind(a, x)
.
Approches
Avertir l'utilisateur
Une première possibilité est de pour avertir l'utilisateur que l'appel à rbind(a, x)
ne doit pas être fait quand x
est une base de données. Au lieu de cela, l'utilisateur de paquet mypackage
devrait faire un appel explicite à une fonction cachée:
mypackage:::rbind.myclass(a, x)
cela peut être fait, mais l'utilisateur doit se rappeler de faire l'appel explicite lorsque nécessaire. L'appel de la fonction cachée est quelque chose de dernier recours et ne devrait pas être régulier.
Intercepter rbind
Sinon, j'ai essayé de protéger l'utilisateur par l'interception de l'expédition. Mon premier essai était de fournir une définition locale de base::rbind.data.frame()
:
rbind.data.frame <- function(...) "hello from my rbind.data.frame"
rbind(a, data.frame())
rm(rbind.data.frame)
Cela échoue car rbind()
n'est pas trompé en appelant rbind.data.frame
.GlobalEnv
, et appelle le base
version comme d'habitude.
une autre stratégie consiste à outrepasser rbind()
par une fonction locale, ce qui a été suggéré dans S3 envoi de "rbind" et " cbind`.
rbind <- function (...) {
if (attr(list(...)[[1]], "class") == "myclass") return(rbind.myclass(...))
else return(base::rbind(...))
}
cela fonctionne parfaitement pour l'expédition à rbind.myclass()
, donc l'utilisateur peut maintenant type rbind(a, x)
pour tout type d'objet x
.
rbind(a, data.frame())
L'inconvénient, c'est qu'après library(mypackage)
nous obtenons le message The following objects are masked from ‘package:base’: rbind
.
bien que techniquement tout fonctionne comme prévu, il devrait y avoir de meilleurs moyens qu'un base
la fonction remplacer.
Conclusion
aucune des solutions de rechange susmentionnées n'est satisfaisante. J'ai lu à propos des alternatives utilisant l'expédition S4, mais jusqu'à présent je n'ai trouvé aucune implémentation de l'idée. Toute aide ou des pointeurs?
3 réponses
comme vous le mentionnez vous-même, l'utilisation de S4 serait une bonne solution qui fonctionne bien. Je n'ai pas cherché récemment, avec des bases de données, car je m'intéresse beaucoup plus à d'autres matrices généralisées, à la fois dans ma"matrice" de mes paquets CRAN de longue date (="recommandé", c'est-à-dire une partie de chaque distribution R) et dans "Rmpfr".
en fait, même de deux façons différentes:
1)Rmpfr
utilise la nouvelle façon de définir des méthodes pour le"..."dans rbind()/cbind().
cela est bien documenté dans ?dotsMethods
(mnémonique: '...'=dots) et mis en œuvre dans le rmpfr/R/array.R ligne 511 ff (p. ex. https://r-forge.r-project.org/scm/viewvc.php/pkg/R/array.R?view=annotate&root=rmpfr)
2)Matrix
utilise l'approche plus ancienne en définissant les méthodes (S4) pour rbind2 () et cbind2 (): si vous lisez ?rbind
il mentionne cela et quand rbind2/cbind2 sont utilisés. L'idée ici:" 2 " signifie que vous définissez les méthodes S4 avec une signature pour deux ("2") objets de type matrice et que rbind / cbind les utilise deux de ses arguments potentiellement nombreux récursivement.
Je ne pense pas que vous allez être en mesure de trouver quelque chose de complètement satisfaisant. Le mieux que vous puissiez faire est d'exporter rbind.myclass
pour que les utilisateurs puissent l'appeler directement sans faire mypackage:::rbind.myclass
. Vous pouvez l'appeler autrement si vous voulez (dplyr
appelle sa version bind_rows
), mais si vous choisissez de le faire, j'utiliserais un nom qui évoque rbind
, comme rbind_myclass
.
Même si vous pouvez obtenir r-core d'accord pour modifier l'expédition comportement, de sorte que rbind
expéditions sur son premier argument, il y a encore des cas où les utilisateurs voudront rbind
plusieurs objets avec un myclass
objet quelque part autre que la première. Comment les utilisateurs peuvent-ils envoyer rbind.myclass(df, df, myclass)
?
data.table
solution semble dangereuse; Je ne serais pas surpris si les responsables du CRAN mettaient un chèque et le refusaient à un moment donné.
dotsMethod
l'approche a été suggérée par Martin Maechler et mise en oeuvre dans le Rmpfr
package. Nous devons définir un nouveau générique, classe et une méthode utilisant S4.
setGeneric("rbind", signature = "...")
mychar <- setClass("myclass", slots = c(x = "character"))
b <- mychar(x = "b")
rbind.myclass <- function(...) "hello from rbind.myclass"
setMethod("rbind", "myclass",
function(..., deparse.level = 1) {
args <- list(...)
if(all(vapply(args, is.atomic, NA)))
return( base::cbind(..., deparse.level = deparse.level) )
else
return( rbind.myclass(..., deparse.level = deparse.level))
})
# these work as expected
rbind(b, "d")
rbind(b, b)
rbind(b, matrix())
# this fails in R 3.4.3
rbind(b, data.frame())
Error in rbind2(..1, r) :
no method for coercing this S4 class to a vector
Je n'ai pas été capable de résoudre l'erreur. Voir R: Les méthodes génériques ne devraient-elles pas fonctionner à l'intérieur d'un paquet sans qu'elles soient attachées? pour un problème connexe.
comme cette approche l'emporte sur rbind()
, nous obtenons l'avertissement The following objects are masked from 'package:base': rbind
.