我需要读取模板文件test.txt
,修改内容,然后将名为foo`i`.in
的修改后的副本写入磁盘(i
是迭代编号)。由于我需要多次执行此操作(一百万次并不常见),因此优选有效的解决方案。模板文件是这样的:
1
bar.out
70.000000000000000
2.000000000000000
14.850000000000000
8000.000000000000000
120.000000000000000
60.000000000000000
0.197500000000000
0.197500000000000
2.310000000000000
0.200000000000000
0.000000000000000
1.000000000000000
0.001187700000000
22.000000000000000
1.400000000000000
1.000000000000000
0.010000000000000
100
0.058600000000000
-0.217000000000000
0.078500000000000
-0.110100000000000
30
500.000000000000000
T
我不需要修改所有行,只需修改其中一些行。具体来说,我需要将bar.out
修改为bar`i`.out
,其中i
是迭代索引。我还需要使用以下值修改一些数字行:
parameters <- data.frame(index = c(1:10, 13:16, 21:22), variable = c("P1",
"P2", "T1", "s", "D", "L", "C1", "C2", "VA",
"pw", "m", "mw", "Cp", "Z", "ff_N", "ff_M"),
value = c(65, 4, 16.85, 7900, 110, 60, 0.1975, .1875, 2.31,
0.2, 0.0011877, 22.0, 1.4, 1.0, 0.0785, -0.1101))
所有其他行必须保持不变,包括最后一行T
。因此,假设我在第一次迭代时,预期输出是一个名为foo1.in
的文本文件,其内容(确切的数字格式并不重要,只要所有parameters$value
中包含foo1.in
中的有效数字:
1
bar1.out
65.000000000000000
4.000000000000000
16.850000000000000
7900.000000000000000
110.000000000000000
60.000000000000000
0.197500000000000
0.187500000000000
2.310000000000000
0.200000000000000
0.000000000000000
1.000000000000000
0.001187700000000
22.000000000000000
1.400000000000000
1.000000000000000
0.010000000000000
100
0.058600000000000
-0.217000000000000
0.078500000000000
-0.110100000000000
30
500.000000000000000
T
修改foo.in
和bar.out
非常简单:
template <- "test.txt"
infile <- "foo.in"
string1 <- "bar.out"
iteration <- 1
# build string1
elements <- strsplit(string1, "\\.")[[1]]
elements[1] <- paste0(elements[1], iteration)
string1 <- paste(elements, collapse = ".")
# build infile name
elements <- strsplit(infile, "\\.")[[1]]
elements[1] <- paste0(elements[1], iteration)
infile<- paste(elements, collapse = ".")
现在,我想阅读模板文件并仅修改预期的行。我面临的第一个问题是read.table
只输出一个数据框。由于我的模板文件在同一列中包含数字和字符串,如果我用read.table
读取所有文件,我将获得一个字符列(我猜)。我只通过阅读我感兴趣的数值来避免这个问题:
# read template file
temp <- read.table(template, stringsAsFactors = FALSE, skip = 2, nrows = 23)$V1
lines_to_read <- temp[length(temp)]
# modify numerical parameter values
temp[parameters$index] <- parameters$value
但是,现在我不知道怎么写foo1.in
。如果我使用write.table
,我只能将矩阵或数据帧写入磁盘,因此我无法在同一列中编写包含数字和字符串的文件。我怎么解决这个问题?
编辑我提供了一些关于这个问题的背景知识,解释为什么我需要多次编写这个文件。因此,想法是对计算机代码(可执行程序)的校准参数执行贝叶斯推断。基本思路很简单:你有一个黑盒子(商业)计算机代码,模拟物理问题,例如FEM代码。我们将此代码称为Joe。给定输入文件,Joe输出物理系统响应的预测。现在,我还对该系统的响应进行了实际的实验测量。我想找到Joe的输入值,以便将Joe的输出与实际测量之间的差异最小化(实际情况完全不同,但这只是为了给出一个想法)。在实践中,这意味着我需要使用不同的输入文件多次运行Joe,并迭代地找到减少&#34;差异的输入值。乔的预测和实验结果之间。简而言之:
所以,虽然Joe是一个商业代码,我只有可执行文件(没有源代码),贝叶斯推理是在R中执行的,因为R(以及,重要的是,Python)有很好的工具来执行这种研究。
答案 0 :(得分:4)
使用模板语言(例如Mustache)可以最简单地解决这个问题,该模板语言在whisker包中的R中实现。
下面是一个示例,说明如何在您的情况下完成此操作。例如,我只实现了前三个变量和bar1.out
。实现剩余的变量应该是直截了当的。
library(whisker)
# You could also read the template in using readLines
# template <- readLines("template.txt")
# but to keep example selfsufficient, I included it in the code
template <- "1
bar{{run}}.out
{{P1}}
{{P2}}
{{T1}}
8000.000000000000000
120.000000000000000
60.000000000000000
0.197500000000000
0.197500000000000
2.310000000000000
0.200000000000000
0.000000000000000
1.000000000000000
0.001187700000000
22.000000000000000
1.400000000000000
1.000000000000000
0.010000000000000
100
0.058600000000000
-0.217000000000000
0.078500000000000
-0.110100000000000
30
500.000000000000000
T"
# Store parameters in a list
parameters <- list(
run = 1,
P1 = 65,
P2 = 4,
T1 = 16.85)
for (i in seq_len(10)) {
# New set of parameters
parameters$run <- i
parameters$P1 <- sample(1:100, 1)
# Generate new script by rendering the template using paramers
current_script <- whisker.render(template, parameters)
writeLines(current_script, paste0("foo", i, ".in"))
# Run script
# system(...)
}
小胡子做了什么(在这种情况下;更复杂的模板是可能的;例如条件元素)用{{<variable>}}
列表中的相应值替换所有parameters
。
答案 1 :(得分:3)
听起来你需要自定义读/写功能;不理想,但是当你有一个类似混合列的东西时,你已经偏离了#34;整洁的数据&#34; (无论是否整洁)。
简化我认为你需要的三个功能:
read_myfile <- function(x) {
# mostly during dev
if (file.exists(x)) x <- readLines(x)
if (length(x) == 1) x <- strsplit(rawfile, "\n")[[1]]
# find all left-aligned NAMED rows
hdrs <- grep("[A-Za-z]", x)
hdrs <- c(1, hdrs) # ensure the first "1" is preserved
dat <- mapply(function(a,b,x) if (b >= a) as.numeric(x[seq(a, b)]),
hdrs + 1, c(hdrs[-1] - 1, length(x)), list(x),
SIMPLIFY = FALSE)
names(dat) <- trimws(x[hdrs])
dat
}
mod_myfile <- function(x, i, params) {
# sanity checks
stopifnot(
is.list(x),
is.numeric(i),
is.data.frame(params),
all(c("index", "value") %in% colnames(params))
)
isbarout <- which(names(x) == "bar.out")
stopifnot(
length(isbarout) == 1
)
x$bar.out[ params$index ] <- params$value
names(x)[isbarout] <- sprintf("bar%i.out", i)
x
}
write_myfile <- function(x, ...) {
newdat <- unlist(unname(
mapply(function(hdr, dat) c(hdr, sprintf("%25.15f ", dat)),
names(x), x, SIMPLIFY = TRUE)
))
writeLines(newdat, ...)
}
使用很简单。我将以单个字符串开头来模拟输入模板(read函数与字符串一样,与文件名一样):
rawfile <- "1
bar.out
70.000000000000000
2.000000000000000
14.850000000000000
8000.000000000000000
120.000000000000000
60.000000000000000
0.197500000000000
0.197500000000000
2.310000000000000
0.200000000000000
0.000000000000000
1.000000000000000
0.001187700000000
22.000000000000000
1.400000000000000
1.000000000000000
0.010000000000000
100
0.058600000000000
-0.217000000000000
0.078500000000000
-0.110100000000000
30
500.000000000000000
T
"
首先,请阅读数据:
dat <- read_myfile(rawfile)
# dat <- read_myfile("file.in")
str(dat)
# List of 3
# $ 1 : NULL
# $ bar.out: num [1:24] 70 2 14.8 8000 120 ...
# $ T : NULL
您将以某种方式确定应该如何更改的参数。我将使用您以前的数据:
parameters <- data.frame(
index = c(1:10, 13:16, 21:22),
variable = c("P1", "P2", "T1", "s", "D", "L", "C1", "C2",
"VA", "pw", "m", "mw", "Cp", "Z", "ff_N", "ff_M"),
value = c(65, 4, 16.85, 7900, 110, 60, 0.1975, .1875, 2.31,
0.2, 0.0011877, 22.0, 1.4, 1.0, 0.0785, -0.1101)
)
第一个参数是read_myfile
的输出;第二个是你要扩充的迭代器bar.out
;第三个是parameters
data.frame:
newdat <- mod_myfile(dat, 32, parameters)
str(newdat)
# List of 3
# $ 1 : NULL
# $ bar32.out: num [1:24] 65 4 16.9 7900 110 ...
# $ T : NULL
现在把它写出来。
write_myfile(newdat, sprintf("foo%d.in", 32))
我不知道@ GiovanniRighi在单个R会话中的性能会如何比较,但这些文件中的1000个在我的计算机上只需不到7秒。