Cómo incluir el script R (fuente) en otros scripts

108

Creé un script de utilidad R, util.R, que quiero usar de otros scripts en mi proyecto. ¿Cuál es la forma correcta de garantizar que la función que define este script esté disponible para funcionar en mis otros scripts?

Estoy buscando algo similar a la requirefunción, que cargue un paquete solo si aún no se ha cargado. No quiero llamar source("util.R")porque eso cargará el script cada vez que se llame.

Sé que obtendré algunas respuestas diciéndome que cree un paquete, como en Organizar el código fuente de R :) Pero no estoy creando algo que se usará en otro lugar, es solo un proyecto independiente.

rafalotufo
fuente
37
Creo paquetes para proyectos independientes todo el tiempo. No es mucho trabajo y los beneficios son enormes. Adelante, sabes que quieres hacerlo ...
Andrie

Respuestas:

93

Aquí hay una forma posible. Use la existsfunción para buscar algo único en su util.Rcódigo.

Por ejemplo:

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

(Editado para incluir mode="function", como señaló Gavin Simpson)

Andrie
fuente
4
Buen uso de exists()- necesita mode = "function"agregarse para que sea infalible
Gavin Simpson
1
exists()parece arrojar un error excepto por devolver uno en R 3.0.2.
Michael Schubert
El uso correcto es `existe (" foo ") y la respuesta fue editada.
Andrie
18

No existe tal cosa incorporada, ya que R no rastrea las llamadas ay sourceno puede averiguar qué se cargó desde dónde (este no es el caso cuando se usan paquetes). Sin embargo, puede usar la misma idea que en los .harchivos C , es decir, envolver todo en:

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

 #Code

}
mbq
fuente
y luego llamar source("util.R")dentro del ifcódigo, ¿verdad?
rafalotufo
1
@rafalotufo Fuente ("util.R") como de costumbre. El código en la publicación de mbq entraría en util.R. Simplemente coloque todo el cuerpo de lo que está en util.R ahora mismo en una declaración if () gigante, si tiene sentido.
Keith Twombley
10

Say util.Rproduce una función foo(). Puede verificar si esta función está disponible en el entorno global y obtener el script si no lo está:

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

Que encontrará cualquier cosa con el nombre foo. Si desea encontrar una función, entonces (como lo menciona @Andrie) exists()es útil pero necesita que le digan exactamente qué tipo de objeto buscar, por ejemplo

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

Aquí está exists()en acción:

> 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
Gavin Simpson
fuente
En este caso, es posible que desee utilizarlo grepl(..., value=TRUE)porque su término de búsqueda probablemente no sea una expresión regular. +1, por cierto.
Andrie
?? grepl()no tiene argumento value, pero probablemente debería arreglar la expresión regular en ls()...
Gavin Simpson
Perdón mi error. fixed=TRUE
Quise
@Andrie - Ah, está bien. De todos modos no funcionó. Fui arrastrado mientras reflexionaba sobre esto. exists()es mejor, pero ahora veo que ha publicado una respuesta de este tipo mientras tanto.
Gavin Simpson
5

Podría escribir una función que tome un nombre de archivo y un nombre de entorno, verifique si el archivo se ha cargado en el entorno y, sys.sourcesi no, lo utiliza para obtener el archivo.

Aquí hay una función rápida y no probada (¡mejoras bienvenidas!):

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
  }
}
Joshua Ulrich
fuente
5

Aquí hay una función que escribí. Envuelve la base::sourcefunción para almacenar una lista de archivos de origen en una lista de entorno global llamada sourced. Solo volverá a generar un archivo si proporciona un .force=TRUEargumento a la llamada a la fuente. Su firma de argumento es por lo demás idéntica a la real, source()por lo que no necesita reescribir sus scripts para usar esto.

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, ...)
  }
}

Es bastante hablador (muchas llamadas a message()), por lo que puede quitar esas líneas si le importa. Se agradece cualquier consejo de los usuarios veteranos de R; Soy bastante nuevo en R.

Keith Twombley
fuente
0

Resolví mi problema usando la dirección completa donde está mi código: Antes:

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

Después:

if(!exists("foo", mode="function")) source("C:/tests/utils.r")
José Roberto Ribeiro Filho
fuente