R: source() и путь к исходным файлам

Должно быть что-то, чего я не понимаю в команде source() в R. Я все еще новичок в этом, но я не могу понять, как она получает свои каталоги! Моя проблема заключается в следующем:

У меня есть скрипт-оболочка, wrapper.R, и исходный файл, содержащий некоторые функции, functions.R. Оба они находятся в одном каталоге. Если я вызываю source('functions.R') внутри скрипта-обертки, стоя внутри каталога, где находятся оба файла, то все нормально. Однако я хочу иметь возможность запускать свой wrapper.R скрипт из какого-нибудь другого каталога, то есть не из того, где находятся эти скрипты. Если я запускаю свою оболочку для другого каталога, она не работает, и я получаю ошибку cannot open the file.

Я погуглил и нашел много разных тем, но этот вопрос казался быть очень ясным. Насколько я понимаю, то, как я это делаю, должно работать. Ясно, я что-то недопонимаю. Мое чтение этой ветки наводит меня на мысль, что source() работает с каталогом, в котором находится файл, вызывающий source(). Мое чтение также наводит меня на мысль, что мне не следует использовать chdir = TRUE, так как я хочу сохранить рекламируемый относительный каталог .

Видя, что это не работает... что я неправильно понимаю? Как я могу использовать исходные файлы в том же каталоге, что и мой скрипт-оболочку, когда они вызываются из другого места?


person erikfas    schedule 15.03.2017    source источник
comment
Все это должно сводиться к рабочему каталогу. R должен знать, где искать файлы. Вы можете найти свой текущий рабочий каталог, набрав getwd(), и вы можете сбросить его с помощью setwd(). Но вы всегда можете просто сделать что-то вроде source(c:\...) и это должно сработать.   -  person Kristofersen    schedule 15.03.2017
comment
Извините, я был неясен. Я могу указать рабочий каталог, но что, если я пытаюсь раздать эти скрипты коллеге? Я не буду точно знать, куда он их положил. Есть ли способ source файлов, фактически не зная каталога, и все еще вызывать функцию-оболочку из какого-либо другого каталога?   -  person erikfas    schedule 15.03.2017
comment
Я считаю, что если вы напишите файл bat для запуска сценариев, он автоматически будет использовать каталог, в котором он находится, в качестве рабочего каталога.   -  person Kristofersen    schedule 15.03.2017


Ответы (5)


Если вы распространяете сценарий среди коллег, вам действительно не следует писать сценарий, использующий другие сценарии. Что, если вы захотите переименовать или переместить functions.R в будущем? Что делать, если вам нужно изменить функцию в functions.R, но wrapper.R полагается на более старую версию этой функции? Это хлипкое решение, которое вызовет головную боль. Вместо этого я бы порекомендовал одно из следующих.

  1. Поместите все необходимое в один автономный скрипт и распространяйте его.

  2. Если вы действительно хотите разделить код на разные файлы, напишите пакет. Может показаться излишним, но на самом деле пакеты могут быть очень простыми и легкими. В простейшей форме пакет — это просто каталог с файлами DESCRIPTION и NAMESPACE, а также каталог R/. Хэдли красиво объясняет это: https://r-pkgs.org/whole-game.html< /а>.

person andrew    schedule 15.03.2017
comment
Аааа, хорошо, это была концепция, которую я на самом деле не представлял... Если я хочу пойти по маршруту пакета, что, если мой проект будет смешанным проектом? Если у меня есть небольшое количество скриптов Python в дополнение к скриптам R? Что было бы хорошим способом распространения такого проекта? - person erikfas; 15.03.2017
comment
Если вы хотите сделать один скрипт R, за которым следует один скрипт python, перо, вероятно, ваш лучший вариант. Это лучший способ обмена объектами, подобными data.frame, между R и python (я предполагаю, что это сценарий конвейера данных): blog.rstudio.org/2016/03/29/feather - person andrew; 16.03.2017
comment
@Sajber не стесняйтесь принять этот ответ, если он помог :) - person andrew; 16.03.2017
comment
Это помогло, спасибо! Связанный с этим вопрос: теперь, когда я пошел по маршруту пакета (это было проще, чем я изначально думал, по крайней мере, части R), как мне распространять сценарий-оболочку? Оболочку следует запускать только из командной строки, поэтому на самом деле она не является частью самого пакета (если я правильно понимаю структуру пакета), но вызывает все функции в пакете. Куда мне положить файл? Как сделать его доступным вместе с пакетом, но указать, что его нужно запускать из командной строки? - person erikfas; 17.03.2017
comment
Вы должны поместить их в exec/, а когда вы установите пакет, поместите символическую ссылку в свой $PATH, которая ссылается на /path/to/your/installed/package/exec/your_script. Также убедитесь, что вы научились использовать инструменты разработки, особенно load_all(). - person andrew; 17.03.2017
comment
Поместите все необходимое в один автономный скрипт и распространяйте его. Плохая, плохая, плохая практика программирования. Удобочитаемость имеет значение. - person Ufos; 16.01.2018
comment
В простейшей форме пакет — это просто каталог... это слишком упрощенно. Источник для пакета — это каталог с определенными файлами и подкаталогами, но чтобы фактически загрузить пакет в R, его нужно сначала скомпилировать. Это может быть сложно для новичка. - person Hong Ooi; 11.03.2020

Это можно сделать с помощью пакета здесь. Он использует «текущий рабочий каталог на момент загрузки пакета». Другими словами, каталог, из которого вы начинаете сеанс R.

В вашем случае код будет таким:

source(here::here('functions.R'))

Это будет работать, даже если скрипт-оболочка wrapper.R находится в другом каталоге проекта.

Если functions.R находится в подкаталоге проекта, просто добавьте его к вызову here(), чтобы завершить относительный путь:

source(here::here('subdirectory', 'functions.R'))
person Luis    schedule 03.02.2018
comment
Я только что обнаружил пакет here, и он решает для меня всевозможные проблемы с путями к файлам. Я призываю людей попробовать это. - person Erika; 06.05.2021

Возможно, вы можете определить вспомогательную функцию в wrapper.R, которая попытается загрузить другие файлы из того же каталога. Например

source_here <- function(x, ...) {
    dir <- "."
    if(sys.nframe()>0) {
        frame <- sys.frame(1)
        if (!is.null(frame$ofile)) {
            dir <- dirname(frame$ofile)
        }
    }
    source(file.path(dir, x), ...)
}

Тогда вы бы позвонили

# inside wrapper.R
source_here("functions.R")

Тогда у вас будет просто источник wrapper.R, и он будет искать functions.R в том же каталоге.

person MrFlick    schedule 15.03.2017
comment
Я пытаюсь немного лучше понять ваш код. Какой смысл иметь if(sys.nframe()>0)? - person Kristofersen; 15.03.2017
comment
Наверное, это перебор. Сначала я запускал код вне функции, поэтому не всегда был родительский фрейм. - person MrFlick; 15.03.2017
comment
Если source_here находится в другом файле, wrapper.R то как сделать source это? - person PeterVermont; 14.04.2017
comment
@PeterVermont source_here будет работать только с содержащимся в нем файлом. Его нельзя включить в отдельный файл wrapper.R, иначе он будет искать только в папке, где находится этот файл. - person MrFlick; 14.04.2017

Один ответ, которого я еще не видел, - просто использовать абсолютные пути. Когда вы source("myfunctions.R") используете неявный относительный путь из getwd(). Используйте полный путь, чтобы избежать проблем при смене рабочего каталога. Хотя, при совместном использовании работы, другим придется менять все пути самостоятельно.

person jiggunjer    schedule 12.05.2020
comment
Использование абсолютных путей решает некоторые проблемы, но создает другие, когда код запускается в немного другом дереве. Другие языки предлагают немного больше, хотя мне интересно взглянуть на пакет здесь, упомянутый выше. - person Chris; 13.04.2021

source принципиально не поддерживает это. В других ответах показаны некоторые обходные пути, которые работают в ограниченных случаях, но не работают в некоторых (общих) случаях. В частности, chdir = TRUE не лучший вариант, как вы сами заметили.

Лучшим решением будет box::use из пакета ‘box. Этот пакет позволяет обрабатывать исходный код R как правильные модули. Одним из свойств этого является то, что модули могут загружать локальные модули.

Внутри вашего wrapper.R замените вызов source на

box::use(./functions[...])

Или, если вы хотите экспортировать эти функции из модуля wrapper.R (а не использовать их внутри), сделайте следующее:

#' @export
box::use(./functions[...])

И чтобы загрузить сам wrapper.R, используйте

box::use(project/wrapper)

Где project — это имя проекта, которое должно соответствовать имени папки, в которой сохранен ваш скрипт wrapper.R.

Дополнительную информацию о использование «коробочных» модулей.

person Konrad Rudolph    schedule 04.06.2021