Lorsque j'entreprends un projet R de toute complexité, mes scripts deviennent rapidement longs et déroutants.
Quelles sont les pratiques que je peux adopter pour que mon code soit toujours un plaisir de travailler avec? Je pense à des choses comme
En gros, quelles sont vos règles d'or pour organiser de gros scripts R?
La réponse standard est d'utiliser des packages - voir le manuel Writing R Extensions ainsi que différents tutoriels sur le web.
Ça te donne
R CMD check
La simple exécution de source()
sur du code fonctionne pour des extraits très courts. Tout le reste devrait ê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.
Quant à la partie "comment éditer", le manuel R Internals a d'excellentes normes de codage R dans la section 6. Sinon, J'ai tendance à utiliser les valeurs par défaut dans mode ESS d'Emacs .
Mise à jour du 13 août 2008: David Smith vient de bloguer sur le Google R Style Guide .
J'aime mettre différentes fonctionnalités dans leurs propres fichiers.
Mais je n'aime pas le système de package 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 dans un fichier til.R . Lorsque vous le sourcez, vous obtenez l'environnement 'util' afin que vous puissiez appeler util$bgrep()
et autres; mais en outre, l'appel attach()
rend juste bgrep()
et ce travail directement. Si vous ne placez pas toutes ces fonctions dans leur propre environnement, elles pollueraient l'espace de noms de niveau supérieur de l'interpréteur (celui que ls()
montre).
J'essayais de simuler le système de Python, où chaque fichier est un module. Ce serait mieux d'avoir, mais cela semble OK.
Cela peut sembler un peu évident, surtout si vous êtes un programmeur, mais voici comment je pense aux unités logiques et physiques du code.
Je ne sais pas si c'est votre cas, mais quand je travaille dans R, je commence rarement avec un grand programme complexe en tête. Je commence généralement par un script et sépare le code en unités logiquement séparables, souvent en utilisant des fonctions. Le code de manipulation et de visualisation des données est placé 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 façon de faciliter la maintenance de votre script et de réduire le taux de défauts.
La granulométrie fine/grossière de vos fonctions variera et il existe différentes règles générales: par exemple 15 lignes de code, ou "une fonction doit être responsable de l'exécution d'une tâche identifiée par son nom", etc. Votre kilométrage varie. É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 implique 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 des unités logiques dans leurs propres unités physiques (comme les fichiers source et les regroupements plus importants comme les packages)? J'ai deux cas. Premièrement, si le fichier devient trop volumineux et que le défilement parmi les unités logiquement indépendantes est gênant. 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, par exemple des fonctions de manipulation de données, dans un fichier séparé. Je peux ensuite source ce fichier à partir de tout autre script.
Si vous allez déployer vos fonctions, vous devez commencer à penser aux packages. Je ne déploie pas le code R en production ou pour une réutilisation par d'autres pour diverses raisons (brièvement: la culture organisationnelle préfère d'autres langages, des préoccupations concernant les performances, la GPL, etc.). En outre, j'ai tendance à constamment affiner et à ajouter à mes collections de fichiers sources, et je préfère ne pas traiter les packages lorsque j'apporte une modification. Vous devriez donc consulter les autres réponses liées au package, comme 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.
Je suis d'accord avec les conseils de Dirk! À mon humble avis, organiser vos programmes de scripts simples à des packages documentés est, pour la programmation en R, comme passer de Word à TeX/LaTeX pour l'écriture. Je recommande de jeter un oeil à la très utile Création de packages R: un tutoriel par Friedrich Leisch.
Ma réponse concise:
Je crois 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'auparavant. 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 vaut en tout cas bien les économies d'effort de développement. Si le temps d'exécution était le seul problème, nous serions tous des assembleurs.
J'ai voulu comprendre comment écrire des paquets mais je n'ai pas investi le temps. Pour chacun de mes mini-projets, je conserve toutes mes fonctions de bas niveau dans un dossier appelé "fonctions /" et je les source dans un espace de noms distinct 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 place généralement ces lignes en haut de mon script principal destiné à l '"interface utilisateur" à partir de laquelle les fonctions de haut niveau (appelant 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 package - 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 en ce qui concerne l'esthétique (je les aime et je les suit sans serrer mais je n'utilise pas trop d'accolades en R):
http://www1.maths.lth.se/help/R/RCC/
Il existe d'autres "conventions" concernant l'utilisation de [ drop = FALSE] et <- comme l'opérateur d'affectation suggéré dans diverses présentations (généralement le discours) sur useR! conférences, mais je ne pense pas qu’elles soient strictes (bien que le [ drop = FALSE] soit utile pour les programmes dans lesquels vous n’êtes pas sûr de l’entrée que vous attendez).
Comptez-moi comme une autre personne en faveur des forfaits. J'avoue être assez pauvre pour écrire des pages de manuel et des vignettes jusqu'à ce que je le doive (c'est-à-dire être publié), mais cela constitue un moyen très pratique de regrouper la biche source. De plus, si vous prenez au sérieux la maintenance de votre code, les points soulevés par Dirk entrent tous dans plya.
Je suis également d'accord. Utilisez la fonction package.skeleton () pour commencer. Même si vous pensez que votre code ne sera plus jamais exécuté, cela peut vous aider à vous motiver à créer un code plus général qui pourrait vous faire gagner du temps plus tard.
Quant à l'accès à l'environnement global, c'est facile avec l'opérateur << -, bien qu'il soit déconseillé.
N'ayant pas encore appris à écrire des packages, je me suis toujours organisé en sourcing de sous scripts. C'est similaire à l'écriture de cours mais pas aussi impliqué. Ce n'est pas élégant par programme 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 source car elle utilisera les objets de l'espace de travail. J'ai peut-être 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" pour d'autres personnes, ou si elle utilise une entrée interactive, un package est probablement un bon itinéraire. 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 modifier.
J'ai également cherché le Saint Graal du bon flux de travail pour monter un grand projet R. L'année dernière, j'ai trouvé ce package appelé rsuite , et c'est 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 peut être utilisé pour des projets R plus petits, de taille moyenne et de grande taille. Je donnerai 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
.
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 portée plus large. 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.
En ayant un super-projet R, vous n'avez plus besoin d'Unix make
pour gérer les niveaux inférieurs des projets R en dessous; vous utilisez les scripts R en haut. Laisse moi te montrer. Lorsque vous créez un projet maître rsuite, vous obtenez cette structure de dossiers:
Le dossier R
est l'endroit où vous placez vos scripts de gestion de projet, ceux qui remplaceront make
.
Le dossier packages
est le dossier où rsuite
contient 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.
le dossier deployment
est l'endroit où rsuite
écrira tous les binaires des packages qui étaient indiqués dans les fichiers des packages DESCRIPTION
. Ainsi, cela vous permet, à lui seul, de projeter un temps d'accroissement totalement reproductible.
rsuite
est fourni 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 addin
pour RStudio.
rsuite
vous permet également de créer une installation isolée conda
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 Python que vous voulez, à partir de n'importe quel canal conda que vous voulez.
Vous pouvez également créer des référentiels locaux pour extraire les packages R lorsque vous êtes hors ligne, ou souhaitez construire le tout plus rapidement.
Si vous le souhaitez, vous pouvez également créer le projet R en tant que fichier Zip et le partager avec vos collègues. Il s'exécutera, à condition que vos collègues aient la même version R installée.
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 construction de votre projet, vous partagez tout le conteneur Docker
avec votre projet prêt à être exécuté.
J'ai beaucoup expérimenté avec rsuite
à la recherche d'une reproductibilité totale, et évite de dépendre des packages que l'on installe dans l'environnement global. C'est faux parce que 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 a été avec les livres électroniques bookdown
. Je n'ai jamais eu la chance d'avoir une comptabilité pour survivre à l'épreuve du temps de plus de six mois. Donc, ce que j'ai fait, c'est convertir le projet original de bookdown pour suivre le framework rsuite
. Maintenant, je n'ai plus à me soucier de la mise à jour de mon environnement R global, car le projet a son propre ensemble de packages dans le dossier deployment
.
La prochaine chose que j'ai faite a été de créer des projets d'apprentissage automatique, mais de la manière rsuite
. Un maître, projet d'orchestration au sommet, et tous les sous-projets et packages sous le contrôle du maître. Cela change vraiment la façon dont vous codez avec R, ce qui vous rend plus productif.
Après cela, j'ai commencé à travailler dans un de mes nouveaux packages appelé rTorch
. Cela a été possible, en grande partie, à cause de rsuite
; il vous permet de penser et de voir grand.
Un conseil cependant. Apprendre rsuite
n'est pas facile. Parce qu'il présente une nouvelle façon de créer des projets R, cela semble difficile. Ne vous découragez pas dès les premières tentatives, continuez à monter la pente jusqu'à ce que vous y arriviez. Cela nécessite une connaissance avancée de votre système d'exploitation et de votre système de fichiers.
Je m'attends à ce qu'un jour RStudio
nous permette de générer des projets d'orchestration comme rsuite
à partir du menu. Ça serait génial.
Liens:
IntroMachineLearningWithR-rsuite