Comment organiser de grands programmes R?

Lorsque j’entreprends un projet de R de complexité quelconque, mes scripts deviennent rapidement longs et confus.

Quelles sont les pratiques que je peux adopter pour que mon code soit toujours un plaisir de travailler avec? Je pense à des choses comme

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

Fondamentalement, quelles sont vos règles de base pour organiser de gros scripts R?

    La solution standard consiste à utiliser des packages – consultez le manuel Writing R Extensions ainsi que différents tutoriels sur le Web.

    Ça te donne

    • un moyen quasi automatique d’organiser votre code par sujet
    • vous encourage fortement à écrire un fichier d’aide, vous faisant penser à l’interface
    • beaucoup de contrôles de santé via la vérification R CMD check
    • une chance d’append des tests de régression
    • ainsi qu’un moyen pour les espaces de noms.

    Il suffit de lancer source() sur du code pour des extraits très courts. Tout le rest 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.

    En ce qui concerne la partie “comment modifier”, le manuel R Internals possède d’excellentes normes de codage R dans la section 6. Sinon, j’ai tendance à utiliser les valeurs par défaut dans le mode ESS d’Emacs .

    Mise à jour 2008-août-13: David Smith vient de bloguer sur le guide de style Google R.

    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 plutôt 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 le joindre. Par exemple, j’ai créé un groupe de fonctions comme «util»:

     util = new.env() util$bgrep = function [...] util$timeit = function [...] while("util" %in% search()) detach("util") attach(util) 

    Tout est dans un fichier util.R. Lorsque vous le util$bgrep() , vous obtenez l’environnement “util” pour pouvoir appeler util$bgrep() et autres; mais de plus, l’appel attach() le rend si juste bgrep() et tel travail directement. Si vous n’avez pas placé toutes ces fonctions dans leur propre environnement, elles pollueraient l’espace de noms de premier niveau 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 paraître 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 en R, je commence rarement avec un programme complexe en tête. Je commence généralement par un script et sépare le code en unités logiquement séparables, en utilisant souvent 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 souhaitez réfléchir à la manière de simplifier la maintenance de votre script et de réduire le taux de défauts.

    Le degré de granularité / de précision de vos fonctions variera et il existe différentes règles de base: 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. . Étant donné que R ne prend pas en charge l’appel par référence, mes fonctions varient généralement en fonction du passage de frameworks de données ou de 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 groupes plus importants comme les packages)? J’ai deux cas. Tout d’abord, si le fichier devient trop volumineux et qu’il fait défiler des unités logiquement non apparentées, c’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 des unités groupées, par exemple des fonctions de manipulation de données, dans un fichier séparé. Je peux alors source ce fichier à partir de tout autre script.

    Si vous souhaitez déployer vos fonctions, vous devez commencer à penser aux packages. Je ne déploie pas de code R en production ou à des fins de réutilisation par d’autres pour diverses raisons (brièvement: la culture org préfère d’autres langages, les problèmes de performances, la GPL, etc.). En outre, j’ai tendance à affiner et à append constamment à mes collections de fichiers sources, et je préfère ne pas traiter les paquets lorsque je fais un changement. Vous devriez donc consulter les autres réponses relatives au paquet, comme celle de Dirk, pour plus de détails sur ce front.

    Enfin, je pense que votre question n’est pas nécessairement particulière à R. Je vous recommande 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, l’organisation de vos programmes à partir de scripts simples vers des packages documentés est, pour la programmation en langage R, comme le passage de Word à TeX / LaTeX pour l’écriture. Je recommande de jeter un oeil à la très utile Création de paquets R: Un tutoriel par Friedrich Leisch.

    Ma réponse concise:

    1. Ecrivez vos fonctions avec soin, en identifiant suffisamment de sorties et d’entrées;
    2. Limiter l’utilisation des variables globales;
    3. Utilisez des objects S3 et, le cas échéant, des objects S4;
    4. Placez les fonctions dans des packages, en particulier lorsque vos fonctions appellent C / Fortran.

    Je crois que R est de plus en plus utilisé dans la production, de sorte que le besoin de code réutilisable est plus grand qu’avant. 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 se concentre 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 de données et l’parsing statistique à une autre langue. Dans ces cas, la pénalité de performance est faible et, en tout état de cause, les économies réalisées dans les efforts de développement en valent la peine. Si le temps d’exécution était le seul problème, nous serions tous des rédacteurs assembleurs.

    Je voulais 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 nommé «functions /», et les source dans un espace de noms distinct que je crée explicitement.

    Les lignes de code suivantes vont créer un environnement nommé “myfuncs” sur le chemin de recherche s’il n’existe pas déjà (en utilisant attach), et le remplir avec les fonctions contenues dans les fichiers .r de 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 (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 avez la chance que j'encourage le système de paquets - j'espère que je vais migrer de cette façon à l'avenir).

    En ce qui concerne les conventions de codage, c'est la seule chose que j'ai vue en matière d'esthétique (je les aime et je les suis de manière lâche mais je n'utilise pas trop d'accolades en R):

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

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

    Comptez-moi comme une autre personne en faveur de forfaits. J’avoue être assez pauvre en écriture de pages de manuel et de vignettes jusqu’à ce que je sois obligé de le faire (c’est-à-dire être sorti), mais cela constitue un moyen très pratique de regrouper les sources. De plus, si vous êtes sérieux au sujet de la maintenance de votre code, les points soulevés par Dirk entrent tous en ligne de compte.

    Je suis aussi 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 à créer un code plus général qui pourrait vous faire gagner du temps ultérieurement.

    En ce qui concerne l’access à l’environnement mondial, c’est facile avec l’opérateur <<, bien qu’il soit découragé.

    N’ayant pas encore appris à écrire des paquets, je me suis toujours organisé en recherchant des sous-scripts. Son semblable à l’écriture des classes mais pas comme impliqué. Ce n’est pas élégamment programmé mais je trouve que je construis des parsings au fil du temps. Une fois que j’ai une grande section qui fonctionne, je la déplace souvent dans un script différent et je la source simplement car elle utilisera les objects de l’espace de travail. Peut-être dois-je importer des données de plusieurs sources, les sortinger toutes et trouver les intersections. Je pourrais mettre cette section dans un script supplémentaire. Cependant, si vous souhaitez dissortingbuer votre “application” à d’autres personnes ou utiliser des entrées interactives, un package est probablement une bonne voie. En tant que chercheur, j’ai rarement besoin de dissortingbuer mon code d’parsing mais j’ai souvent besoin de le compléter ou de le modifier.

    R est correct pour une utilisation interactive et des petits scripts, mais je ne l’utiliserais pas pour un grand programme. J’utiliserais un langage courant pour la plupart des programmes et les intégrerais dans une interface R.