Make在使研究和数据分析具有更高的可重现性方面非常方便,例如:
# make file
R = R CMD BATCH --no-save --no-restore
datafiles = *.csv
outputfiles = *.{pdf,Rout}
.PHONY: all clean
all: fig_A.pdf fig_B.pdf
clean:
rm -f $(datafiles) $(outputfiles)
rm -rf output
mkdir output
# produce outputs
fig_A.pdf fig_B.pdf: interim_data.csv plot_figs.R
$(R) plot_figs.R
mv plot_figs.Rout ./output
# derive interim data
interim_data.csv: source_data.csv source_to_interim.R
$(R) source_to_interim.R
mv source_to_interim.Rout ./output
# download source data
source_data.csv: download_source.R
$(R) download_source.R
mv download_source.Rout ./output
从源数据重新生成图形,并将所有输出保存到./output
。但是,我们可以使事情更紧凑吗?例如,通过
避免重复,如:
$(R) script.R
mv script.Rout ./output
是否要重组为更通用的相关代码(在此示例中为R脚本),数据(csv)和输出(pdf,Rout)?
更好地处理将输出导出到./output
目录吗?
答案 0 :(得分:2)
您可能应该看看make的automatic variables:
$ cat Makefile
.NOTPARALLEL:
OUTPUT := output
R = R CMD BATCH --no-save --no-restore
PDF := fig_A.pdf fig_B.pdf
CSV := interim_data.csv source_data.csv
all: $(PDF) $(CSV)
$(PDF): plot_figs.R interim_data.csv
interim_data.csv: source_to_interim.R source_data.csv
source_data.csv: download_source.R
$(CSV) $(PDF):
$(R) $<
mv $<out $(OUTPUT)
$ make
R CMD BATCH --no-save --no-restore download_source.R
mv download_source.Rout output
R CMD BATCH --no-save --no-restore source_to_interim.R
mv source_to_interim.Rout output
R CMD BATCH --no-save --no-restore plot_figs.R
mv plot_figs.Rout output
$<
自动变量通过make扩展为当前目标的第一个前提条件(这就是为什么我对fig_A.pdf
,fig_B.pdf
和interim_data.csv
的前提条件进行重新排序的原因) 。此外,您可以将规则与配方分开,将规则与前提条件分开(并且没有配方)。
请注意.NOTPARALLEL
,它告诉make不要并行运行多个配方。在您的情况下,这是必需的,因为您有两个目标(fig_A.pdf
和fig_B.pdf
)生产相同的plot_figs.Rout
副产品,而该副产品却被相同的配方移出。如果允许make在并行模式下运行,则可能会出现竞争状况。
这有点困难,因为您的食谱会产生2种不同的输出:*.csv
(或*.pdf
)和*.Rout
。而且make在设计时就没有考虑到这种情况。它更倾向于一种配方=一种文件产品。但是我们可以尝试使用宏(R
)隐藏这些文件移动:
$ cat Makefile
.NOTPARALLEL:
OUTPUT := output
R = R CMD BATCH --no-save --no-restore $(1) && mv $(1)out $(OUTPUT)
PDF := fig_A.pdf fig_B.pdf
CSV := interim_data.csv source_data.csv
all: $(PDF) $(CSV)
$(PDF): plot_figs.R interim_data.csv
interim_data.csv: source_to_interim.R source_data.csv
source_data.csv: download_source.R
$(CSV) $(PDF):
$(call R,$<)
$ make
R CMD BATCH --no-save --no-restore download_source.R && mv download_source.Rout output
R CMD BATCH --no-save --no-restore source_to_interim.R && mv source_to_interim.Rout output
R CMD BATCH --no-save --no-restore plot_figs.R && mv plot_figs.Rout output
$(call...)
make函数将扩展为其第一个参数变量(R
)的值,其中$(1)
已被第二个参数($<
){{ 1}}的第三个参数(在我们的例子中没有),...
请注意$(2)
的定义:它使用递归赋值运算符(R
,而不是简单赋值运算符(=
),因为我们希望仅在需要时才对其进行扩展,在make将配方传递给外壳执行之前。