进行可重复的研究
make for reproducible research
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
目录的导出?
1) 和 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
和[=15=的先决条件]).此外,您可以将规则与配方分开,规则与先决条件分开(没有配方)。
请注意 .NOTPARALLEL
告诉 make 不要同时 运行 多个配方。在你的情况下,它是必需的,因为你有两个目标(fig_A.pdf
和 fig_B.pdf
)产生相同的 plot_figs.Rout
副产品,这些副产品被相同的配方移出。如果 make 被允许在并行模式下 运行 会有竞争条件的风险。
3)
这有点困难,因为您的食谱会产生 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)
已被第二个参数 ($<
) 替换, $(2)
通过第三个参数(none 在我们的例子中),...
注意R
的定义:它使用递归赋值运算符(=
),而不是简单的赋值运算符(:=
),因为我们希望它仅在需要,就在 make 将配方传递给 shell 执行之前。
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
目录的导出?
1) 和 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
和[=15=的先决条件]).此外,您可以将规则与配方分开,规则与先决条件分开(没有配方)。
请注意 .NOTPARALLEL
告诉 make 不要同时 运行 多个配方。在你的情况下,它是必需的,因为你有两个目标(fig_A.pdf
和 fig_B.pdf
)产生相同的 plot_figs.Rout
副产品,这些副产品被相同的配方移出。如果 make 被允许在并行模式下 运行 会有竞争条件的风险。
3)
这有点困难,因为您的食谱会产生 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)
已被第二个参数 ($<
) 替换, $(2)
通过第三个参数(none 在我们的例子中),...
注意R
的定义:它使用递归赋值运算符(=
),而不是简单的赋值运算符(:=
),因为我们希望它仅在需要,就在 make 将配方传递给 shell 执行之前。