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?

14
demandé sur Stef van Buuren 2017-12-25 12:12:35

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.

5
répondu Martin Mächler 2017-12-27 13:27:07

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é.

1
répondu Patrick Perry 2017-12-26 22:04:44

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.

1
répondu Stef van Buuren 2017-12-28 07:45:14