0

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. 但是我们可以让事情变得更紧凑吗?例如,通过

  1. 避免重复,例如:

    $(R) script.R
    mv script.Rout ./output
    
  2. 重新组织以更通用地关联代码(本例中为 R 脚本)数据(csv)和输出(pdf、Rout)?

  3. 更好地处理输出到./output目录的导出?

4

1 回答 1

2

1) 和 2)

您可能应该查看 make 的自动变量

$ 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.pdffig_B.pdf) 生产相同plot_figs.Rout的副产品,这些副产品会被相同的配方移出。如果允许 make 以并行模式运行,则存在竞争条件的风险。

3)

这有点困难,因为您的食谱会产生 2 个不同的输出:*.csv(或*.pdf) 和*.Rout. 并且在设计时并未考虑到这种情况。它更倾向于一种配方=一种文件产品。但我们可以尝试使用宏 ( 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

make函数$(call...)扩展为其第一个参数变量(R$(1)$<$(2)

注意 的定义R:它使用递归赋值运算符 ( =),而不是简单的赋值运算符 ( :=),因为我们希望它仅在需要时展开,就在 make 将配方传递给 shell 执行之前。

于 2018-07-31T09:53:08.617 回答