Come includere lo script R (sorgente) in altri script


108

Ho creato uno script R di utilità, util.R, che desidero utilizzare da altri script nel mio progetto. Qual è il modo corretto per garantire che la funzione definita da questo script sia disponibile per funzionare negli altri miei script?

Sto cercando qualcosa di simile alla requirefunzione, che carica un pacchetto solo se non è stato ancora caricato. Non voglio chiamaresource("util.R") perché questo caricherà lo script ogni volta che viene chiamato.

So che riceverò alcune risposte che mi dicono di creare un pacchetto, come in Organizzazione del codice sorgente R :) Ma non sto creando qualcosa che verrà utilizzato altrove, è solo un progetto autonomo.


37
Creo continuamente pacchetti per progetti standalone. Non è molto lavoro ei vantaggi sono enormi. Dai, sai che vuoi farlo ...
Andrie

Risposte:


93

Ecco un modo possibile. Usa la existsfunzione per verificare la presenza di qualcosa di unico nel tuo util.Rcodice.

Per esempio:

if(!exists("foo", mode="function")) source("util.R")

(Modificato per includere mode="function", come ha sottolineato Gavin Simpson)


4
Buon uso di exists()- deve essere mode = "function"aggiunto per renderlo infallibile
Gavin Simpson

1
exists()sembra lanciare un errore tranne che per restituirne uno in R 3.0.2.
Michael Schubert

L'utilizzo corretto è `esiste (" foo ") e la risposta è stata modificata.
Andrie

18

Non esiste qualcosa di simile integrato, poiché R non tiene traccia delle chiamate a sourcee non è in grado di capire cosa è stato caricato da dove (questo non è il caso quando si usano i pacchetti). Tuttavia, puoi usare la stessa idea dei .hfile C , ovvero racchiudere il tutto in:

if(!exists('util_R')){
 util_R<-T

 #Code

}

e poi chiama source("util.R")all'interno del ifcodice, giusto?
rafalotufo

1
@rafalotufo Dovresti source ("util.R") come al solito. Il codice nel post di mbq andrebbe in util.R. Devi solo mettere l'intero corpo di ciò che è in util.R in questo momento in una gigantesca istruzione if (), se ha senso.
Keith Twombley

10

Say util.Rproduce una funzione foo(). Puoi verificare se questa funzione è disponibile nell'ambiente globale e generare lo script se non lo è:

if(identical(length(ls(pattern = "^foo$")), 0))
    source("util.R")

Quello troverà qualsiasi cosa con il nome foo. Se vuoi trovare una funzione, allora (come menzionato da @Andrie) exists()è utile ma deve essere detto esattamente quale tipo di oggetto cercare, ad es.

if(exists("foo", mode = "function"))
    source("util.R")

Ecco exists()in azione:

> exists("foo", mode = "function")
[1] FALSE
> foo <- function(x) x
> exists("foo", mode = "function")
[1] TRUE
> rm(foo)
> foo <- 1:10
> exists("foo", mode = "function")
[1] FALSE

In questo caso, potresti volerlo utilizzare grepl(..., value=TRUE)perché il termine di ricerca probabilmente non è un'espressione regolare. +1, a proposito.
Andrie

?? grepl()non ha argomenti value, ma probabilmente dovrei correggere la regexp in ls()...
Gavin Simpson

Scusa, errore mio. Intendevofixed=TRUE
Andrie

@Andrie - Ah, OK. Comunque non ha funzionato. Sono stato trascinato via mentre riflettevo su questo. exists()è meglio ma ora vedo che nel frattempo hai pubblicato una risposta del genere.
Gavin Simpson

5

È possibile scrivere una funzione che prende un nome di file e un nome di ambiente, controlla se il file è stato caricato nell'ambiente e utilizza sys.sourceper generare il file in caso contrario.

Ecco una funzione rapida e non testata (miglioramenti benvenuti!):

include <- function(file, env) {
  # ensure file and env are provided
  if(missing(file) || missing(env))
    stop("'file' and 'env' must be provided")
  # ensure env is character
  if(!is.character(file) || !is.character(env))
    stop("'file' and 'env' must be a character")

  # see if env is attached to the search path
  if(env %in% search()) {
    ENV <- get(env)
    files <- get(".files",ENV)
    # if the file hasn't been loaded
    if(!(file %in% files)) {
      sys.source(file, ENV)                        # load the file
      assign(".files", c(file, files), envir=ENV)  # set the flag
    }
  } else {
    ENV <- attach(NULL, name=env)      # create/attach new environment
    sys.source(file, ENV)              # load the file
    assign(".files", file, envir=ENV)  # set the flag
  }
}

5

Ecco una funzione che ho scritto. Avvolge la base::sourcefunzione per memorizzare un elenco di file di origine in un elenco di ambiente globale denominato sourced. Risorgerà un file solo se fornisci un .force=TRUEargomento alla chiamata a source. Per il resto, la firma dell'argomento è identica a quella reale, source()quindi non è necessario riscrivere gli script per utilizzarla.

warning("overriding source with my own function FYI")
source <- function(path, .force=FALSE, ...) {
  library(tools)
  path <- tryCatch(normalizePath(path), error=function(e) path)
  m<-md5sum(path)

  go<-TRUE
  if (!is.vector(.GlobalEnv$sourced)) {
    .GlobalEnv$sourced <- list()
  }
  if(! is.null(.GlobalEnv$sourced[[path]])) {
    if(m == .GlobalEnv$sourced[[path]]) {
      message(sprintf("Not re-sourcing %s. Override with:\n  source('%s', .force=TRUE)", path, path))
      go<-FALSE
    }
    else {
      message(sprintf('re-sourcing %s as it has changed from: %s to: %s', path, .GlobalEnv$sourced[[path]], m))
      go<-TRUE
    }
  } 
  if(.force) {
    go<-TRUE
    message("  ...forcing.")
  }
  if(go) {
    message(sprintf("sourcing %s", path))
    .GlobalEnv$sourced[path] <- m
    base::source(path, ...)
  }
}

È piuttosto loquace (molte chiamate a message()) quindi puoi togliere quelle linee se ti interessa. Qualsiasi consiglio da parte degli utenti veterani di R è apprezzato; Sono abbastanza nuovo per R.


0

Ho risolto il mio problema utilizzando l'intero indirizzo in cui si trova il mio codice: Prima:

if(!exists("foo", mode="function")) source("utils.r")

Dopo:

if(!exists("foo", mode="function")) source("C:/tests/utils.r")
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.