读取模板文件并在进行一些修改后将其写入磁盘

时间:2017-02-21 17:11:23

标签: r file-io

我需要读取模板文件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.inbar.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;差异的输入值。乔的预测和实验结果之间。简而言之:

  1. 我需要生成许多输入(文本)文件
  2. 我事先并不知道输入文件的内容。在优化过程中以迭代方式修改数值参数。
  3. 我还需要为每个输入读取Joe的输出。这实际上是另一个问题,我可能会在这一点上写一个特定的问题。
  4. 所以,虽然Joe是一个商业代码,我只有可执行文件(没有源代码),贝叶斯推理是在R中执行的,因为R(以及,重要的是,Python)有很好的工具来执行这种研究。

2 个答案:

答案 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秒。