Comment organiser de grands programmes R?


161

Lorsque j'entreprends un projet R de toute complexité, mes scripts deviennent rapidement longs et déroutants.

Quelles pratiques puis-je adopter pour que mon code soit toujours un plaisir de travailler? Je pense à des choses comme

  • Placement des fonctions dans les fichiers source
  • Quand diviser quelque chose dans un autre fichier source
  • Que doit contenir le fichier maître
  • Utiliser des fonctions comme unités organisationnelles (si cela en vaut la peine étant donné que R rend difficile l'accès à l'état global)
  • Pratiques d'indentation / saut de ligne.
    • Traiter (comme {?
    • Mettez des choses comme)} sur 1 ou 2 lignes?

En gros, quelles sont vos règles empiriques pour organiser de gros scripts R?


12
devrait être wiki communautaire
SilentGhost

Vous pouvez également consulter le ProjectTemplatepackage.
ctbrown

Réponses:


71

La réponse standard est d'utiliser des packages - voir le manuel Writing R Extensions ainsi que différents didacticiels sur le Web.

Ça te donne

  • une manière quasi-automatique d'organiser votre code par thème
  • vous encourage fortement à rédiger un fichier d'aide, vous faisant réfléchir à l'interface
  • beaucoup de contrôles de santé via R CMD check
  • une chance d'ajouter des tests de régression
  • ainsi qu'un moyen pour les espaces de noms.

Le simple fait d'exécuter du source()code fonctionne pour des extraits de code très courts. Tout le reste doit être dans un package - même si vous ne prévoyez pas de le publier car vous pouvez écrire des packages internes pour les référentiels internes.

En ce qui concerne la partie «comment éditer», le manuel de R Internals a d'excellents standards de codage R dans la section 6. Sinon, j'ai tendance à utiliser les valeurs par défaut en mode ESS d'Emacs .

Mise à jour 2008-Août-13: David Smith vient blogué sur le Google R Guide de style .


8
Si vous développez votre arbre source / analyse «organiquement», ne trouvez-vous pas cela difficile / encombrant? Si vous remarquez une erreur dans votre code (courante lors de l'exploration d'un nouvel espace de problème), vous devez (i) corriger la source; (ii) réinstaller le package; (iii) le recharger dans votre espace de travail? Existe-t-il un moyen d'appeler la bibliothèque (...) afin de recharger un paquet qui est déjà chargé (étape iii ci-dessus)? N'avez-vous pas besoin de tuer votre espace de travail, de redémarrer R puis de recharger votre bibliothèque / package pour voir si c'est vrai?
Steve Lianoglou

1
Essayer de googler le style de codage R.
hadley

3
@SteveLianoglou Je sais que c'est assez ancien, mais le paquet devtools de Hadley rend le rechargement de tout votre code très facile.
Dason

1
Ce billet de blog donne (à mon avis) un très bon tutoriel rapide pour construire un premier paquet bare bones: hilaryparker.com/2014/04/29/writing-an-r-package-from-scratch
panterasBox

1
Voici le lien de travail vers Google R Style Guide
Denis Rasulev

51

J'aime mettre différentes fonctionnalités dans leurs propres fichiers.

Mais je n'aime pas le système de paquets de R. C'est assez difficile à utiliser.

Je préfère une alternative légère, pour placer les fonctions d'un fichier dans un environnement (ce que toutes les autres langues appellent un «espace de noms») et l'attacher. Par exemple, j'ai créé un groupe de fonctions 'util' comme ceci:

util = new.env()

util$bgrep = function [...]

util$timeit = function [...]

while("util" %in% search())
  detach("util")
attach(util)

Tout cela est dans un fichier util.R . Lorsque vous le sourcez, vous obtenez l'environnement «util» afin que vous puissiez appeler util$bgrep()et autres; mais de plus, l' attach()appel le rend si juste bgrep()et un tel travail directement. Si vous ne mettez pas toutes ces fonctions dans leur propre environnement, elles pollueraient l'espace de noms de premier niveau de l'interpréteur (celui qui ls()s'affiche).

J'essayais de simuler le système de Python, où chaque fichier est un module. Ce serait mieux d'avoir, mais cela semble correct.


Merci, Brendan. C'est très utile. Que se passe-t-il avec la boucle while? Quel est le problème avec if (! ("Util"% in% search ())) attach (util)
Dan Goldstein

2
donc vous pouvez faire source ("util.R") encore et encore si vous voulez le peaufiner et autres.
Brendan OConnor

vous n'avez pas vraiment besoin d'une boucle while - tout ce dont vous avez besoin est détachée (util). Je ne me souviens pas si cela donne une erreur ou non s'il n'est pas déjà chargé, mais c'est le plus sûr et fonctionne. les suggestions sont les bienvenues.
Brendan OConnor

1
Créer des environnements spécifiques aux fonctions et les attacher est la voie à suivre pour moi. Une autre méthode pour obtenir la même chose d'une manière différente (avec plus de modularité) consiste à utiliser sys.source: MyEnv <- attach(NULL, name=s_env); sys.source(file, MyEnv). Je déclare même (dans son propre environnement!) Au démarrage une fonction sys.source2qui cherchera si un environnement du même nom est déjà là et alimente celui-ci au lieu d'en créer un nouveau. Cela rend l'ajout de fonctions personnelles rapide, facile et assez organisé :-)
Antoine Lizée

5
Je viens de voir cela aujourd'hui et cela s'applique à une alternative au package: github.com/klmr/modules
ctbrown

34

Cela peut sembler un peu évident, surtout si vous êtes un programmeur, mais voici comment je pense aux unités logiques et physiques de code.

Je ne sais pas si c'est votre cas, mais quand je travaille en R, je commence rarement avec un grand programme complexe en tête. Je commence généralement par un script et je sépare le code en unités logiquement séparables, souvent en utilisant des fonctions. La manipulation des données et le code de visualisation sont placés dans leurs propres fonctions, etc. Et ces fonctions sont regroupées dans une section du fichier (manipulation des données en haut, puis visualisation, etc.). En fin de compte, vous voulez réfléchir à la manière de faciliter la maintenance de votre script et de réduire le taux de défauts.

Le niveau de grain fin / grossier de vos fonctions variera et il existe diverses règles empiriques: par exemple, 15 lignes de code, ou "une fonction doit être chargée d'effectuer une tâche identifiée par son nom", etc. Votre kilométrage variera . Étant donné que R ne prend pas en charge l'appel par référence, je suis généralement différent de rendre mes fonctions trop fines lorsqu'il s'agit de passer des trames de données ou des structures similaires. Mais cela peut être une surcompensation pour certaines erreurs de performance stupides lorsque j'ai commencé avec R.

Quand extraire les unités logiques dans leurs propres unités physiques (comme les fichiers source et les groupes plus importants comme les packages)? J'ai deux cas. Premièrement, si le fichier devient trop volumineux et que le défilement parmi des unités logiquement non liées est une gêne. Deuxièmement, si j'ai des fonctions qui peuvent être réutilisées par d'autres programmes. Je commence généralement par placer une unité groupée, disons des fonctions de manipulation de données, dans un fichier séparé. Je peux ensuite me procurer ce fichier à partir de n'importe quel autre script.

Si vous allez déployer vos fonctions, vous devez commencer à penser aux packages. Je ne déploie pas de code R en production ou pour une réutilisation par d'autres pour diverses raisons (brièvement: la culture de l'organisation préfère d'autres langages, des préoccupations concernant les performances, la GPL, etc.). De plus, j'ai tendance à constamment affiner et ajouter à mes collections de fichiers source, et je préfère ne pas traiter les packages lorsque j'apporte un changement. Vous devriez donc consulter les autres réponses liées aux packages, comme celles de Dirk, pour plus de détails à ce sujet.

Enfin, je pense que votre question n'est pas nécessairement particulière à R. Je recommanderais vraiment de lire Code Complete de Steve McConnell qui contient beaucoup de sagesse sur ces problèmes et les pratiques de codage en général.


3
Commentaire très utile, ars, merci. Je suis programmeur, mais il est bon de vérifier avec les autres. Quand vous dites "Puisque R ne prend pas en charge l'appel par référence, je me méfie généralement de rendre mes fonctions trop fines", je vous entends. J'ai l'habitude d'écrire des fonctions comme ReadData (); CleanData (); Analyser les données(); GraphData (); et R rend cela fastidieux. Je me réveille à l'idée que je dois utiliser "source" comme j'utilise des fonctions dans d'autres langues.
Dan Goldstein

2
Vous avez raison, Dan. Je me retrouve à utiliser "source" de cette façon pour les tâches de préparation des ensembles de données, donc je peux simplement utiliser un data.frame préparé à travers d'autres scripts où l'analyse réelle est effectuée. Je n'étais jamais sûr que c'était une bonne pratique parce que cela semble bizarre par rapport à d'autres langages - plus comme un script shell vraiment. C'est bien de comparer les notes. :)
ars

Bonne réponse et commentaires. Je trouve cela particulièrement ennuyeux dans R, car vous travaillez souvent avec des données dans un format particulier, pour lesquelles il est vraiment difficile d'écrire des fonctions réutilisables. Alors, écrivez-vous un bon code fonctionnel, même si vous savez qu'il ne sera jamais réutilisé, ou écrivez-vous simplement du code procédural rapide et désagréable juste pour obtenir un peu plus d'efficacité avec de gros objets? Un peu de dilemme .. R serait absolument génial avec l'appel par référence ..
rien101

Eh bien, cela peut être fait, en quelque sorte , mais il n'y a pas de gain d'efficacité ...
rien101

19

Je suis d'accord avec les conseils de Dirk! À mon humble avis, organiser vos programmes depuis de simples scripts jusqu'aux packages documentés est, pour la programmation en R, comme passer de Word à TeX / LaTeX pour l'écriture. Je recommande de jeter un œil au très utile Créer des packages R: un tutoriel de Friedrich Leisch.


6
Les packages semblent convaincants. Cependant, je craignais qu'ils ne soient exagérés. Je n'écris pas de code à usage général. La plupart de ce que je fais est de tester cette hypothèse, de tester cette hypothèse, de tracer ceci, d'ajuster les paramètres de tracé, de tracer cela, de remodeler les données, de tracer cela. Je fais des choses qui, une fois terminées, ne seront probablement jamais réexécutées.
Dan Goldstein

1
Dans ce cas, vous devriez jeter un œil à Sweave. Il combine le code R avec LaTeX. Vous avez donc l'analyse et la source du rapport ensemble.
Thierry

15

Ma réponse concise:

  1. Écrivez soigneusement vos fonctions, identifiez les sorties et les entrées assez générales;
  2. Limitez l'utilisation de variables globales;
  3. Utiliser des objets S3 et, le cas échéant, des objets S4;
  4. Mettez les fonctions dans des packages, en particulier lorsque vos fonctions appellent C / Fortran.

Je pense que R est de plus en plus utilisé en production, donc le besoin de code réutilisable est plus grand qu'auparavant. Je trouve l'interprète beaucoup plus robuste qu'avant. Il ne fait aucun doute que R est 100-300x plus lent que C, mais généralement le goulot d'étranglement est concentré autour de quelques lignes de code, qui peuvent être déléguées à C / C ++. Je pense que ce serait une erreur de déléguer les forces de R dans la manipulation des données et l'analyse statistique à un autre langage. Dans ces cas, la pénalité de performance est faible et, en tout cas, vaut largement les économies réalisées sur les efforts de développement. Si le temps d'exécution seul était le problème, nous serions tous assembleurs d'écriture.


11

J'avais l'intention de comprendre comment écrire des paquets mais je n'ai pas investi de temps. Pour chacun de mes mini-projets, je garde toutes mes fonctions de bas niveau dans un dossier appelé «functions /», et je les source dans un espace de noms séparé que je crée explicitement.

Les lignes de code suivantes créeront un environnement nommé "myfuncs" sur le chemin de recherche s'il n'existe pas déjà (en utilisant attach), et le rempliront avec les fonctions contenues dans les fichiers .r dans mon répertoire 'functions /' (en utilisant sys.source). Je mets généralement ces lignes en haut de mon script principal destiné à "l'interface utilisateur" à partir de laquelle les fonctions de haut niveau (invoquant les fonctions de bas niveau) sont appelées.

if( length(grep("^myfuncs$",search()))==0 )
  attach("myfuncs",pos=2)
for( f in list.files("functions","\\.r$",full=TRUE) )
  sys.source(f,pos.to.env(grep("^myfuncs$",search())))

Lorsque vous apportez des modifications, vous pouvez toujours le ressourcer avec les mêmes lignes, ou utiliser quelque chose comme

evalq(f <- function(x) x * 2, pos.to.env(grep("^myfuncs$",search())))

pour évaluer les ajouts / modifications dans l'environnement que vous avez créé.

C'est kludgey je sais, mais évite d'avoir à être trop formel à ce sujet (mais si vous en avez l'occasion, j'encourage le système de paquets - j'espère que je migrerai de cette façon à l'avenir).

En ce qui concerne les conventions de codage, c'est la seule chose que j'ai vue concernant l'esthétique (je les aime et je les suis vaguement mais je n'utilise pas trop d'accolades dans R):

http://www1.maths.lth.se/help/R/RCC/

Il existe d'autres "conventions" concernant l'utilisation de [, drop = FALSE] et <- comme opérateur d'affectation suggéré dans diverses présentations (généralement keynote) à useR! conférences, mais je ne pense pas qu’aucune d’entre elles soit stricte (bien que [, drop = FALSE] soit utile pour les programmes dans lesquels vous n’êtes pas sûr de l’entrée que vous attendez).


6

Comptez-moi comme une autre personne en faveur des colis. J'admets être assez médiocre sur l'écriture de pages de manuel et de vignettes jusqu'à ce que je le doive (c'est-à-dire être publié), mais c'est un moyen très pratique de regrouper les sources. De plus, si vous prenez au sérieux la maintenance de votre code, les points soulevés par Dirk entrent tous en jeu.


4

Je suis également d’accord. Utilisez la fonction package.skeleton () pour commencer. Même si vous pensez que votre code ne sera peut-être plus jamais exécuté, cela peut vous motiver à créer un code plus général qui pourrait vous faire gagner du temps plus tard.

Quant à accéder à l'environnement global, c'est facile avec l'opérateur << -, bien que déconseillé.


3

N'ayant pas encore appris à écrire des packages, je me suis toujours organisé en sourcing de sous-scripts. C'est similaire aux classes d'écriture mais pas aussi impliquées. Ce n'est pas un programme élégant mais je trouve que je construis des analyses au fil du temps. Une fois que j'ai une grande section qui fonctionne, je la déplace souvent vers un script différent et je la recherche simplement car elle utilisera les objets de l'espace de travail. Peut-être ai-je besoin d'importer des données de plusieurs sources, de les trier toutes et de trouver les intersections. Je pourrais mettre cette section dans un script supplémentaire. Cependant, si vous souhaitez distribuer votre «application» à d'autres personnes, ou si elle utilise une entrée interactive, un package est probablement une bonne voie. En tant que chercheur, j'ai rarement besoin de distribuer mon code d'analyse mais j'ai SOUVENT besoin de l'augmenter ou de le peaufiner.


J'ai utilisé cette méthode mais j'ai depuis réalisé que les fonctions et les packages sont meilleurs que source ("next_script.R"). J'ai écrit à ce sujet ici: stackoverflow.com/questions/25273166/…
Arthur Yip

1

J'ai également cherché le Saint Graal du bon flux de travail pour monter un grand projet R. J'ai trouvé l'année dernière ce package appelé rsuite , et c'était certainement ce que je cherchais. Ce package R a été explicitement développé pour le déploiement de grands projets R, mais j'ai trouvé qu'il pouvait être utilisé pour des projets R de petite, moyenne et grande taille. Je vais donner des liens vers des exemples du monde réel dans une minute (ci-dessous), mais je veux d'abord expliquer le nouveau paradigme de la construction de projets R avec rsuite.

Remarque. Je ne suis ni le créateur ni le développeur de rsuite.

  1. Nous avons mal fait des projets avec RStudio; l'objectif ne doit pas être la création d'un projet ou d'un package mais d'une plus grande portée. Dans rsuite, vous créez un super-projet ou un projet maître, qui contient les projets R standard et les packages R, dans toutes les combinaisons possibles.

  2. En ayant un super-projet R, vous n'avez plus besoin d'Unix makepour gérer les niveaux inférieurs des projets R en dessous; vous utilisez des scripts R en haut. Laisse moi te montrer. Lorsque vous créez un projet maître rsuite, vous obtenez cette structure de dossiers:

entrez la description de l'image ici

  1. Le dossier Rest l'endroit où vous placez vos scripts de gestion de projet, ceux qui les remplaceront make.

  2. Le dossier packagesest le dossier rsuitecontenant tous les packages qui composent le super-projet. Vous pouvez également copier-coller un package qui n'est pas accessible depuis Internet, et rsuite le construira également.

  3. le dossier deploymentest l'endroit où rsuiteécriront tous les binaires de package qui ont été indiqués dans les DESCRIPTIONfichiers de packages . Ainsi, cela rend, en soi, vous projetez totalement reproductible dans le temps.

  4. rsuiteest livré avec un client pour tous les systèmes d'exploitation. Je les ai tous testés. Mais vous pouvez également l'installer en tant que addinpour RStudio.

  5. rsuitevous permet également de créer une condainstallation isolée dans son propre dossier conda. Ce n'est pas un environnement mais une installation physique Python dérivée d'Anaconda dans votre machine. Cela fonctionne avec les R SystemRequirements, à partir desquels vous pouvez installer tous les packages Python de votre choix, à partir de n'importe quel canal conda de votre choix.

  6. Vous pouvez également créer des référentiels locaux pour extraire les packages R lorsque vous êtes hors ligne, ou souhaitez créer le tout plus rapidement.

  7. Si vous le souhaitez, vous pouvez également créer le projet R sous forme de fichier zip et le partager avec des collègues. Il fonctionnera, à condition que vos collègues aient la même version R installée.

  8. Une autre option consiste à créer un conteneur de l'ensemble du projet dans Ubuntu, Debian ou CentOS. Ainsi, au lieu de partager un fichier zip avec la génération de votre projet, vous partagez le Dockerconteneur entier avec votre projet prêt à être exécuté.

J'ai beaucoup expérimenté rsuitepour rechercher une reproductibilité totale, et éviter de dépendre des paquets que l'on installe dans l'environnement global. C'est faux car dès que vous installez une mise à jour de package, le projet, le plus souvent, cesse de fonctionner, en particulier les packages avec des appels très spécifiques à une fonction avec certains paramètres.

La première chose que j'ai commencé à expérimenter était avec les bookdownebooks. Je n'ai jamais eu la chance d'avoir un livre pour survivre à l'épreuve du temps plus de six mois. Donc, ce que j'ai fait, c'est convertir le projet de livre original pour suivre le rsuitecadre. Maintenant, je n'ai pas à m'inquiéter de la mise à jour de mon environnement R global, car le projet a son propre ensemble de packages dans le deploymentdossier.

La prochaine chose que j'ai faite a été de créer des projets d'apprentissage automatique, mais de la rsuitemanière. Un projet maître, orchestrant au sommet, et tous les sous-projets et packages doivent être sous le contrôle du maître. Cela change vraiment la façon dont vous codez avec R, vous rendant plus productif.

Après cela, j'ai commencé à travailler dans un nouveau package appelé rTorch. Cela a été possible, en grande partie, grâce à rsuite; cela vous permet de penser et de faire les choses en grand.

Un conseil cependant. L'apprentissage rsuiten'est pas facile. Parce que cela présente une nouvelle façon de créer des projets R, cela semble difficile. Ne vous effrayez pas aux premières tentatives, continuez à gravir la pente jusqu'à ce que vous y arriviez. Cela nécessite une connaissance approfondie de votre système d'exploitation et de votre système de fichiers.

J'espère qu'un jour RStudionous permettra de générer des projets orchestrants comme le rsuitefait depuis le menu. Ça serait génial.

Liens:

Repo RSuite GitHUb

bookdown r4ds

keras et tutoriel brillant

moderndive-book-rsuite

interprétable_ml-rsuite

IntroMachineLearningWithR-rsuite

clark-intro_ml-rsuite

hyndman-bookdown-rsuite

statistique_rethinking-rsuite

fread-benchmarks-rsuite

dataviz-rsuite

tutoriel-segmentation-de-détail-h2o

tutoriel-de-désabonnement-client-telco

sclerotinia_rsuite


-7

R est OK pour une utilisation interactive et de petits scripts, mais je ne l'utiliserais pas pour un grand programme. J'utiliserais un langage grand public pour la plupart de la programmation et l'envelopperais dans une interface R.


1
Il existe des packages (c'est-à-dire des programmes) très volumineux. Proposez-vous sérieusement qu'ils devraient être réécrits dans une autre langue? Pourquoi???
Eduardo Leoni

4
Une considération est l'efficacité. J'ai souvent réécrit le code R en code C ++ et je l'ai rendu 100 fois plus rapide. Un autre est le support des outils. R n'a rien de comparable aux IDE comme Eclipse ou Visual Studio. Enfin, si un programme est très volumineux, il est probable qu'il exécute des tâches non statistiques auxquelles R n'est pas bien adapté.
John

2
Il existe un plugin (Stat-ET) disponible qui permet à Eclipse d'interagir avec R. Je suis d'accord que C ++ peut fonctionner beaucoup plus rapidement que R. Mais combien de temps avez-vous besoin pour recoder les éléments R en C ++? À moins que vous ne puissiez réutiliser le code fréquemment, l'avantage du code plus rapide ne vaut pas grand-chose par rapport à l'effort de le recoder en C ++.
Thierry

2
Oui, il y a un compromis (productivité vs performances). Et pour l'analyse de données / travail statistique purement, R gagne souvent. Mais pour écrire d'autres tâches, par exemple GUI, Web, etc., je ne suis pas sûr que ce soit le cas. Nous prototypons et travaillons souvent en R mais déployons du code de production en Python / C ++. Avec ce dernier, vous obtenez des performances et des bibliothèques / frameworks très matures et réutilisables pour diverses tâches. Mais c'est une situation fluide et l'écosystème R est en constante évolution.
ars

L'utilisation du Rcpppackage, y compris du code C ++ dans les programmes R devient assez simple. Ainsi, la réécriture de certaines parties du code R peut être intégrée assez facilement dans R. De plus, l'avènement de RStudio a introduit un IDE pour R, bien que peut-être pas encore aussi puissant que Visual Studio.
Paul Hiemstra
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.