Come organizzare grandi programmi R?


161

Quando intraprendo un progetto R di qualsiasi complessità, i miei script diventano rapidamente lunghi e confusi.

Quali sono alcune pratiche che posso adottare affinché il mio codice sia sempre un piacere lavorare con? Sto pensando a cose del genere

  • Posizionamento di funzioni nei file di origine
  • Quando suddividere qualcosa in un altro file sorgente
  • Cosa dovrebbe essere nel file principale
  • Usare le funzioni come unità organizzative (se vale la pena dato che R rende difficile l'accesso allo stato globale)
  • Pratiche di rientro / interruzione di riga.
    • Trattare (come {?
    • Metti cose come)} su 1 o 2 righe?

Fondamentalmente, quali sono le tue regole empiriche per l'organizzazione di script R di grandi dimensioni?


12
dovrebbe essere wiki della comunità
SilentGhost il

Potresti anche voler guardare il ProjectTemplatepacchetto.
ctbrown,

Risposte:


71

La risposta standard è quella di utilizzare i pacchetti: consultare il manuale Writing R Extensions e diversi tutorial sul Web.

Ti dà

  • un modo quasi automatico di organizzare il codice per argomento
  • ti incoraggia fortemente a scrivere un file di aiuto, facendoti pensare all'interfaccia
  • molti controlli di sanità mentale via R CMD check
  • un'opportunità per aggiungere test di regressione
  • nonché un mezzo per gli spazi dei nomi.

Il solo source()passaggio del codice funziona per frammenti davvero brevi. Tutto il resto dovrebbe essere in un pacchetto - anche se non hai intenzione di pubblicarlo in quanto puoi scrivere pacchetti interni per repository interni.

Per quanto riguarda la parte "come modificare", il manuale di R Internals ha eccellenti standard di codifica R nella Sezione 6. In caso contrario, tendo a utilizzare i valori predefiniti nella modalità ESS di Emacs .

Aggiornamento 2008-ago-13: David Smith appena bloggato circa la Style Guide di Google R .


8
Se stai crescendo il tuo albero delle fonti / analisi "organicamente", non trovi difficile / ingombrante? Se noti un errore nel tuo codice (comune durante l'esplorazione di un nuovo spazio problematico), devi (i) correggere la fonte; (ii) reinstallare il pacchetto; (iii) ricaricarlo nel tuo spazio di lavoro? C'è un modo per chiamare la libreria (...) per ricaricare un pacchetto che è già stato caricato (passaggio iii sopra)? Non devi uccidere il tuo spazio di lavoro, riavviare R quindi ricaricare la libreria / pacchetto per vedere se è giusto?
Steve Lianoglou,

1
Cercare su Google per lo stile di codifica R.
Hadley,

3
@SteveLianoglou So che è piuttosto vecchio, ma il pacchetto devtools di Hadley rende molto semplice ricaricare tutto il tuo codice.
Dason,

1
Questo post del blog offre un (mio parere) tutorial veloce davvero buono per costruire un primo pacchetto di ossa nude: hilaryparker.com/2014/04/29/writing-an-r-package-from-scratch
panterasBox

1
Ecco il link funzionante alla Guida allo stile
Denis Rasulev

51

Mi piace mettere diverse funzionalità nei propri file.

Ma non mi piace il sistema di pacchetti di R. È piuttosto difficile da usare.

Preferisco un'alternativa leggera, per posizionare le funzioni di un file all'interno di un ambiente (quello che ogni altra lingua chiama uno "spazio dei nomi") e allegarlo. Ad esempio, ho creato un gruppo di funzioni "util" in questo modo:

util = new.env()

util$bgrep = function [...]

util$timeit = function [...]

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

Questo è tutto in un file util.R . Quando lo si ottiene, si ottiene l'ambiente 'util' in modo da poter chiamare util$bgrep()e così; ma inoltre, la attach()chiamata lo rende così giusto bgrep()e tale lavoro direttamente. Se non inserissi tutte quelle funzioni nel loro ambiente, inquinerebbero lo spazio dei nomi di livello superiore dell'interprete (quello che ls()mostra).

Stavo cercando di simulare il sistema di Python, in cui ogni file è un modulo. Sarebbe meglio avere, ma questo sembra OK.


Grazie Brendan. Questo è molto utile. Cosa succede con il ciclo while? Cosa c'è di sbagliato in if (! ("Util"% in% search ())) attach (util)
Dan Goldstein

2
così puoi fare source ("util.R") ancora e ancora se vuoi modificarlo e così via.
Brendan OConnor,

non hai davvero bisogno di un ciclo while - tutto ciò che serve è staccare (util). non ricordo se dà un errore o meno se non è già caricato, ma questo è il più sicuro e funziona. suggerimenti benvenuti.
Brendan OConnor,

1
Creare ambienti specifici per le funzioni e collegarlo è la strada da percorrere per me. Un altro metodo per ottenere la stessa cosa in modo diverso (con più modularità) è di uso sys.source: MyEnv <- attach(NULL, name=s_env); sys.source(file, MyEnv). Dichiaro persino (nel suo stesso ambiente!) All'avvio una funzione sys.source2che cercherà se un ambiente con lo stesso nome è già qui e lo alimenta invece di crearne uno nuovo. Rende l'aggiunta di funzioni personali rapida, semplice e un po 'organizzata :-)
Antoine Lizée,

5
L'ho visto oggi e si applica a un'alternativa al pacchetto: github.com/klmr/modules
ctbrown,

34

Questo potrebbe sembrare un po 'ovvio soprattutto se sei un programmatore, ma ecco come penso alle unità di codice logiche e fisiche.

Non so se questo è il tuo caso, ma quando lavoro in R, raramente inizio con un grande programma complesso in mente. Di solito inizio in uno script e separo il codice in unità logicamente separabili, spesso usando le funzioni. Il codice di manipolazione e visualizzazione dei dati viene inserito nelle proprie funzioni, ecc. E tali funzioni sono raggruppate in una sezione del file (manipolazione dei dati in alto, quindi visualizzazione, ecc.). In definitiva, vuoi pensare a come semplificare la gestione della sceneggiatura e ridurre la percentuale di difetti.

La variabilità delle funzioni di grana fine / grossolana varia e esistono varie regole pratiche: ad esempio 15 righe di codice o "una funzione dovrebbe essere responsabile dell'esecuzione di un'attività identificata dal suo nome", ecc. Il tuo chilometraggio varierà . Dato che R non supporta la chiamata per riferimento, di solito sono molto incline a rendere le mie funzioni troppo dettagliate quando si tratta di passare frame di dati o strutture simili in giro. Ma questa potrebbe essere una sovracompensazione per alcuni sciocchi errori di prestazione quando ho iniziato con R.

Quando estrarre le unità logiche nelle proprie unità fisiche (come file sorgente e raggruppamenti più grandi come pacchetti)? Ho due casi. Innanzitutto, se il file diventa troppo grande e lo scorrimento tra unità logicamente non correlate è un fastidio. In secondo luogo, se ho funzioni che possono essere riutilizzate da altri programmi. Di solito inizio inserendo alcune unità raggruppate, ad esempio le funzioni di manipolazione dei dati, in un file separato. Posso quindi estrarre questo file da qualsiasi altro script.

Se hai intenzione di distribuire le tue funzioni, allora devi iniziare a pensare ai pacchetti. Non distribuisco il codice R in produzione o per il riutilizzo da parte di altri per vari motivi (brevemente: la cultura dell'organizzazione preferisce altri linguaggi, preoccupazioni riguardo alle prestazioni, GPL, ecc.). Inoltre, tendo a perfezionare e aggiungere costantemente alle mie raccolte di file di provenienza e preferirei non gestire i pacchetti quando apporto una modifica. Quindi dovresti controllare le altre risposte relative al pacchetto, come quelle di Dirk, per maggiori dettagli su questo fronte.

Infine, penso che la tua domanda non sia necessariamente particolare per R. Consiglio vivamente di leggere il Codice completo di Steve McConnell che contiene molta saggezza su tali problemi e pratiche di codifica in generale.


3
Commento molto utile, grazie. Sono un programmatore, ma è bene verificare con gli altri. Quando dici "Dato che R non supporta la chiamata per riferimento, di solito sono diffidente nel rendere le mie funzioni troppo dettagliate", ti sento. Sono abituato a scrivere funzioni come ReadData (); CleanData (); Analizzare i dati(); GraphData (); e R lo rende ingombrante. Mi sto svegliando all'idea che devo usare "source" come uso le funzioni in altre lingue.
Dan Goldstein,

2
Hai ragione, Dan. Mi ritrovo a utilizzare "source" in questo modo per le attività di preparazione del set di dati, quindi posso semplicemente utilizzare un data.frame preparato su altri script in cui viene eseguita l'analisi reale. Non sono mai stato sicuro che si trattasse di una buona pratica perché sembra strano rispetto ad altre lingue - più come lo shell scripting. È buono per confrontare le note. :)
ars

Bella risposta e commenti. Trovo che ciò sia particolarmente fastidioso in R, perché stai spesso lavorando con dati in un formato particolare, per i quali è davvero difficile scrivere funzioni riutilizzabili. Quindi scrivi un bel codice funzionale, anche se sai che non verrà mai riutilizzato, o semplicemente scrivi un codice procedurale veloce e sgradevole solo per ottenere quel po 'di efficienza in più con oggetti di grandi dimensioni? Un po 'un dilemma .. R sarebbe assolutamente fantastico con la chiamata per riferimento ..
nought101

Bene, si può fare, in un certo senso , ma non c'è alcun guadagno in termini di efficienza ...
nought101

19

Sono d'accordo con i consigli di Dirk! IMHO, organizzare i tuoi programmi da semplici script a pacchetti documentati è, per la programmazione in R, come passare da Word a TeX / LaTeX per la scrittura. Consiglio di dare un'occhiata all'utilissimo Creazione di pacchetti R: un tutorial di Friedrich Leisch.


6
I pacchetti sembrano convincenti. Tuttavia, temevo che potessero essere eccessivi. Non sto scrivendo un codice generico. La maggior parte di quello che sto facendo è testare questa ipotesi, testare quell'ipotesi, tracciare questo, regolare i parametri della trama, tracciare quella, rimodellare i dati, tracciare quella. Sto facendo cose che, una volta terminate, probabilmente non verranno mai rieseguite.
Dan Goldstein,

1
In tal caso, dovresti dare un'occhiata a Sweave. Combina il codice R con LaTeX. Quindi hai l'analisi e l'origine del rapporto insieme.
Thierry,

15

La mia risposta concisa:

  1. Scrivi le tue funzioni con attenzione, identificando output e input abbastanza generali;
  2. Limitare l'uso di variabili globali;
  3. Utilizzare oggetti S3 e, se del caso, oggetti S4;
  4. Metti le funzioni in pacchetti, specialmente quando le tue funzioni chiamano C / Fortran.

Credo che R sia sempre più utilizzato nella produzione, quindi la necessità di un codice riutilizzabile è maggiore di prima. Trovo l'interprete molto più robusto di prima. Non c'è dubbio che R è 100-300x più lento di C, ma di solito il collo di bottiglia è concentrato su alcune righe di codice, che possono essere delegate a C / C ++. Penso che sarebbe un errore delegare i punti di forza di R nella manipolazione dei dati e nell'analisi statistica in un'altra lingua. In questi casi, la penalità prestazionale è bassa e comunque vale la pena risparmiare nello sforzo di sviluppo. Se il tempo di esecuzione fosse solo il problema, scriveremmo tutti assemblatore.


11

Ho intenzione di capire come scrivere i pacchetti ma non ho investito il tempo. Per ciascuno dei miei mini-progetti tengo tutte le mie funzioni di basso livello in una cartella chiamata 'funzioni /' e le fonte in uno spazio dei nomi separato che ho creato esplicitamente.

Le seguenti righe di codice creeranno un ambiente chiamato "myfuncs" sul percorso di ricerca se non esiste già (usando attach) e lo popoleranno con le funzioni contenute nei file .r nella mia directory 'funzioni /' (usando sys.source). Di solito inserisco queste righe nella parte superiore del mio script principale pensato per "l'interfaccia utente" da cui vengono chiamate le funzioni di alto livello (che invocano le funzioni di basso livello).

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())))

Quando apporti modifiche, puoi sempre reindirizzarle con le stesse linee o utilizzare qualcosa di simile

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

per valutare aggiunte / modifiche nell'ambiente creato.

È kludgey lo so, ma evita di essere troppo formale (ma se ne avessi la possibilità incoraggio il sistema di pacchetti - si spera che migrerò in quel modo in futuro).

Per quanto riguarda le convenzioni di codifica, questa è l'unica cosa che ho visto per quanto riguarda l'estetica (mi piacciono e seguo vagamente ma non uso troppe parentesi graffe in R):

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

Esistono altre "convenzioni" sull'uso di [, drop = FALSE] e <- come suggerito dall'operatore di assegnazione in varie presentazioni (di solito keynote) in useR! conferenze, ma non credo che nessuna di queste sia rigorosa (sebbene [, drop = FALSE] sia utile per i programmi in cui non si è sicuri dell'input che ci si aspetta).


6

Contami come un'altra persona a favore dei pacchetti. Devo ammettere di essere piuttosto povero nello scrivere pagine man e vignette fino a quando / quando devo (cioè essere rilasciato), ma è un modo davvero pratico di raggruppare il doe sorgente. Inoltre, se prendi sul serio il mantenimento del tuo codice, i punti sollevati da Dirk entrano in gioco.


4

Sono anche d'accordo. Utilizzare la funzione package.skeleton () per iniziare. Anche se ritieni che il tuo codice potrebbe non essere più eseguito, potrebbe essere d'aiuto motivarti a creare un codice più generale che ti consenta di risparmiare tempo in seguito.

Per quanto riguarda l'accesso all'ambiente globale, è facile con l'operatore << -, sebbene sia scoraggiato.


3

Non avendo ancora imparato a scrivere i pacchetti, mi sono sempre organizzato procurandogli degli script secondari. È simile alle lezioni di scrittura ma non così coinvolto. Non è programmaticamente elegante ma trovo che costruisca analisi nel tempo. Una volta che ho una grande sezione che funziona, spesso la sposto in uno script diverso e la fonte solo perché utilizzerà gli oggetti dell'area di lavoro. Forse ho bisogno di importare dati da diverse fonti, ordinarli tutti e trovare le intersezioni. Potrei mettere quella sezione in uno script aggiuntivo. Tuttavia, se si desidera distribuire la propria "applicazione" per altre persone o se utilizza un input interattivo, un pacchetto è probabilmente una buona strada. Come ricercatore raramente ho bisogno di distribuire il mio codice di analisi, ma spesso ho bisogno di aumentarlo o modificarlo.


Ho usato questo metodo ma da allora ho capito che funzioni e pacchetti sono migliori di source ("next_script.R"). Ne ho scritto qui: stackoverflow.com/questions/25273166/…
Arthur Yip,

1

Ho anche cercato il santo graal del giusto flusso di lavoro per mettere insieme un grande progetto R. L'anno scorso ho trovato questo pacchetto chiamato rsuite e, certamente, era quello che cercavo. Questo pacchetto R è stato sviluppato esplicitamente per la distribuzione di progetti R di grandi dimensioni, ma ho scoperto che può essere utilizzato per progetti R di piccole, medie e grandi dimensioni. Darò collegamenti ad esempi del mondo reale in un minuto (sotto), ma prima voglio spiegare il nuovo paradigma di costruzione di progetti R conrsuite .

Nota. Non sono il creatore o lo sviluppatore di rsuite.

  1. Abbiamo fatto progetti tutti sbagliati con RStudio; l'obiettivo non dovrebbe essere la creazione di un progetto o di un pacchetto ma di un ambito più ampio. In rsuite crei un super-progetto o un progetto principale, che contiene i progetti R standard e i pacchetti R, in tutte le combinazioni possibili.

  2. Avendo un super-progetto R non è più necessario Unix makeper gestire i livelli inferiori dei progetti R sottostanti; usi gli script R in alto. Lascia che ti mostri. Quando si crea un progetto master rsuite, si ottiene questa struttura di cartelle:

inserisci qui la descrizione dell'immagine

  1. La cartella Rè dove metti i tuoi script di gestione del progetto, quelli che sostituiranno make.

  2. La cartella packagesè la cartella in cui sono contenuti rsuitetutti i pacchetti che compongono il super-progetto. Puoi anche copiare e incollare un pacchetto che non è accessibile da Internet, e anche rsuite lo costruirà.

  3. la cartella deploymentè dove rsuitescriveranno tutti i file binari dei pacchetti indicati nei DESCRIPTIONfile dei pacchetti . Quindi, questo rende di per sé proiettare tempi di riproduzione totalmente riproducibili.

  4. rsuiteviene fornito con un client per tutti i sistemi operativi. Li ho testati tutti. Ma puoi anche installarlo come addinper RStudio.

  5. rsuiteconsente inoltre di creare un'installazione isolata condanella propria cartella conda. Questo non è un ambiente ma un'installazione fisica di Python derivata da Anaconda nella tua macchina. Funziona insieme a R SystemRequirements, da cui è possibile installare tutti i pacchetti Python desiderati, da qualsiasi canale conda desiderato.

  6. Puoi anche creare repository locali per estrarre i pacchetti R quando sei offline o vuoi costruire il tutto più velocemente.

  7. Se lo desideri, puoi anche creare il progetto R come file zip e condividerlo con i colleghi. Funzionerà, a condizione che i tuoi colleghi abbiano la stessa versione R installata.

  8. Un'altra opzione è la creazione di un contenitore dell'intero progetto in Ubuntu, Debian o CentOS. Quindi, invece di condividere un file zip con la build del progetto, condividi l'intero Dockercontainer con il tuo progetto pronto per l'esecuzione.

Ho sperimentato molto nel rsuitecercare la piena riproducibilità ed evitare a seconda dei pacchetti che si installano nell'ambiente globale. Questo è sbagliato perché non appena si installa un aggiornamento del pacchetto, il progetto, il più delle volte, smette di funzionare, specialmente quei pacchetti con chiamate molto specifiche a una funzione con determinati parametri.

La prima cosa che ho iniziato a sperimentare è stata con gli bookdownebook. Non ho mai avuto la fortuna di avere un libro esaurito per sopravvivere alla prova del tempo più di sei mesi. Quindi, quello che ho fatto è stato convertire il progetto di bookdown originale per seguire il rsuiteframework. Ora, non devo preoccuparmi di aggiornare il mio ambiente R globale, perché il progetto ha il suo set di pacchetti nella deploymentcartella.

La prossima cosa che ho fatto è stata la creazione di progetti di machine learning ma tra l' rsuitealtro. Un master, orchestrando il progetto nella parte superiore e tutti i sottoprogetti e pacchetti che saranno sotto il controllo del master. Cambia davvero il modo in cui codifichi con R, rendendoti più produttivo.

Successivamente ho iniziato a lavorare in un mio nuovo pacchetto chiamato rTorch. Ciò è stato possibile, in gran parte, a causa di rsuite; ti fa pensare e andare alla grande.

Un consiglio però. L'apprendimento rsuitenon è facile. Poiché presenta un nuovo modo di creare progetti R, è difficile. Non sgomentare ai primi tentativi, continua a salire sul pendio fino a quando non ce la fai. Richiede una conoscenza avanzata del sistema operativo e del file system.

Mi aspetto che un giorno RStudioci consenta di generare progetti di orchestrazione come rsuitefa dal menu. Sarebbe fantastico.

link:

Repository RSuite GitHUb

bookdown r4ds

keras e tutorial lucido

moderndive-book-rsuite

interpretable_ml-rsuite

IntroMachineLearningWithR-rsuite

clark-intro_ml-rsuite

Hyndman-bookdown-rsuite

statistical_rethinking-rsuite

fread-benchmark-rsuite

DataViz-rsuite

retail-segmentazione-H2O dimostrativi

telco-cliente-churn dimostrativi

sclerotinia_rsuite


-7

R è OK per l'uso interattivo e piccoli script, ma non lo userei per un programma di grandi dimensioni. Userei un linguaggio tradizionale per la maggior parte della programmazione e lo avvolgo in un'interfaccia R.


1
Ci sono pacchetti seriamente grandi (cioè programmi) là fuori. Stai seriamente suggerendo che dovrebbero essere riscritti in un'altra lingua? Perché???
Eduardo Leoni,

4
Una considerazione è l'efficienza. Ho spesso riscritto il codice R come codice C ++ e l'ho reso 100 volte più veloce. Un altro è il supporto degli strumenti. R non ha nulla di paragonabile agli IDE come Eclipse o Visual Studio. Infine, se un programma è molto grande, è probabile che svolga compiti non statistici a cui R non è adatto.
John D. Cook,

2
È disponibile un plug-in (Stat-ET) che consente a Eclipse di interagire con R. Sono d'accordo sul fatto che C ++ può funzionare molto più velocemente di R. Ma quanto tempo è necessario per ricodificare le cose R in C ++? A meno che non sia possibile riutilizzare frequentemente il codice, il vantaggio di un codice più veloce non vale molto rispetto allo sforzo di ricodificarlo in C ++.
Thierry,

2
Sì, c'è un compromesso (produttività v prestazioni). E per analisi puramente di dati / lavoro statistico, R vince spesso. Ma per scrivere altre attività, ad esempio GUI, Web, ecc., Non sono sicuro che sia così. Spesso prototipiamo e lavoriamo in R ma implementiamo il codice di produzione in Python / C ++. Con quest'ultimo si ottengono prestazioni e librerie / framework molto maturi e riutilizzabili per varie attività. Ma questa è una situazione fluida e l'ecosistema R è in continua evoluzione.
ars

L'uso del Rcpppacchetto, incluso il codice C ++ nei programmi R, diventa abbastanza semplice. Quindi riscrivere alcune parti del codice R può essere integrato in R abbastanza facilmente. Inoltre, l'avvento di RStudio ha introdotto un IDE per R, anche se forse non è ancora potente come Visual Studio.
Paul Hiemstra,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.