【问题标题】:Importing irregular data in r在r中导入不规则数据
【发布时间】:2016-08-10 18:33:31
【问题描述】:

我希望有人可以帮助我解决数据导入问题,我认为这可能很容易解决,但还没有找到答案。我有大量包含天线扫描的 txt 文件,我需要以统一配置导入它们。问题在于,在实际数据开始之前,它们都包含不规则数量的关于天线的诊断数据行。我需要一个可以识别实际数据何时开始的函数,这样我就可以在正确的列中使用正确的数据导入它。基本上,对于每个文件,我需要确定诊断代码的行数,因此我可以在使用 read.delim 或类似的东西输入文件时指定 skip=" "。

这是我正在谈论的文件之一的示例:

Power OFF @ 12:05:50 02/15/13 
Power ON  @ 12:06:03 02/15/13 
Reader #1 12:06:03 02/15/13 

Reader #2 12:06:03 02/15/13 

Battery Voltage = 13.35 @ 13:00:00 02/15/13 
Battery Voltage = 13.42 @ 14:00:00 02/15/13 
Battery Voltage = 13.32 @ 15:00:00 02/15/13 
Battery Voltage = 13.55 @ 16:00:00 02/15/13 

Reader #2 02:57:40 02/17/13 LA 900 226000012999

Reader #2 02:57:40 02/17/13 LA 900 226000012999

Reader #2 02:57:40 02/17/13 LA 900 226000012999

Reader #2 02:57:40 02/17/13 LA 900 226000012999

【问题讨论】:

  • 那么您需要该文件中的哪些数据
  • 电池电压读取后开始的数据,即读卡器#、时间、日期、标签号
  • rl <- readLines('file.txt'); rl <- rl[grepl('Reader\\s+#\\d+\\s+[0-9:]+\\s+[0-9/]+\\s+.+$', rl)]; read.table(text = rl, comment.char = '', colClasses = 'character')怎么样

标签: r import format


【解决方案1】:

read.table

如果您使用readLines 逐行读取文本,则可以使用grep 搜索与“电池电压”匹配的最高行号并将其用于skip

read.table(file.txt, 
           skip = max(grep('Battery Voltage', readLines(file.txt))), 
           # set comment delimiting character to anything besides "#"
           comment.char = '')
##       V1 V2       V3       V4 V5  V6       V7
## 1 Reader #2 02:57:40 02/17/13 LA 900 2.26e+11
## 2 Reader #2 02:57:40 02/17/13 LA 900 2.26e+11
## 3 Reader #2 02:57:40 02/17/13 LA 900 2.26e+11
## 4 Reader #2 02:57:40 02/17/13 LA 900 2.26e+11

请注意,需要进一步清理(合并列、格式化日期)。


read.fwf

如果列宽一致,使用read.fwf (fixed width file) 可能更有意义。您需要使用na.omitcomplete.cases 或其他一些方法来消除空白行,因为read.fwf 不接受blank.lines.skip 参数,如read.table 及其变体:

na.omit(read.fwf(file.txt, 
                 widths = c(9, -1, 17, -1, 2, -1, 3, -1, 12), 
                 skip = max(grep('Battery Voltage', readLines(file.txt))), 
                 comment.char = ''))
##          V1                V2 V3  V4       V5
## 2 Reader #2 02:57:40 02/17/13 LA 900 2.26e+11
## 4 Reader #2 02:57:40 02/17/13 LA 900 2.26e+11
## 6 Reader #2 02:57:40 02/17/13 LA 900 2.26e+11
## 8 Reader #2 02:57:40 02/17/13 LA 900 2.26e+11

但是,通过计数字符来计算列宽是一件很痛苦的事情(而且容易出错)。


readr::read_fwf

readr 包使处理固定宽度文件的烦恼稍微减轻了一些,并在事情没有得到理想解析时给出有用的警告。它还提供参数以在您读取数据时解析日期和日期时间,这很方便:

library(readr)

df <- read_fwf(file.txt, 
               fwf_widths(c(9, 18, 3, 4, NA)), 
               col_types = list('c', col_datetime('%H:%M:%S %m/%d/%y'),'c', 'i', 'd'), 
               skip = max(grep('Battery Voltage', readLines(file.txt))))

df <- df[complete.cases(df), ]
# or df <- na.omit(df)
# or if some NAs are possible, more robust:
# df <- df[colSums(!apply(df, 1, is.na)) > 0, ]

df
## # A tibble: 4 x 5
##          X1                  X2    X3    X4       X5
##       <chr>              <time> <chr> <int>    <dbl>
## 1 Reader #2 2013-02-17 02:57:40    LA   900 2.26e+11
## 2 Reader #2 2013-02-17 02:57:40    LA   900 2.26e+11
## 3 Reader #2 2013-02-17 02:57:40    LA   900 2.26e+11
## 4 Reader #2 2013-02-17 02:57:40    LA   900 2.26e+11

注意解析得很好的日期时间和稍微简单的列宽输入方法(你可以用fwf_empty 让它猜测,如果你有列名,这很好用)。

如果您的宽度和列类型正确,任何不正确的都将输入为NA,因此如果您使用na.omit,则可以完全避免使用skip 参数:

na.omit(read_fwf(file.txt, 
                 fwf_widths(c(9, 18, 3, 4, 13)), 
                 col_types = list('c', col_datetime('%H:%M:%S %m/%d/%y'),'c', 'i', 'd')))
## # A tibble: 4 x 5
##          X1                  X2    X3    X4       X5
##       <chr>              <time> <chr> <int>    <dbl>
## 1 Reader #2 2013-02-17 02:57:40    LA   900 2.26e+11
## 2 Reader #2 2013-02-17 02:57:40    LA   900 2.26e+11
## 3 Reader #2 2013-02-17 02:57:40    LA   900 2.26e+11
## 4 Reader #2 2013-02-17 02:57:40    LA   900 2.26e+11

不过,这种方法有些不稳定,因此只有在您能够验证其工作正常时才应使用。

【讨论】:

    【解决方案2】:

    略有不同。这将返回以 Reader 开头并包含由空格分隔的 7 个元素的每一行。我注意到前两行 Reader 行较短,如果并非总是如此,那么这当然行不通。

    antenna0 <- readLines("antenna.txt")
    antenna0 <- antenna0[grep("^Reader", antenna0)]
    antenna <- strsplit(antenna0, " ")
    data.frame(do.call(rbind, antenna[sapply(antenna, length) == 7]))
    
    #      X1 X2       X3       X4 X5  X6           X7
    #1 Reader #2 02:57:40 02/17/13 LA 900 226000012999
    #2 Reader #2 02:57:40 02/17/13 LA 900 226000012999
    #3 Reader #2 02:57:40 02/17/13 LA 900 226000012999
    #4 Reader #2 02:57:40 02/17/13 LA 900 226000012999
    

    我想你可以使用更高级的正则表达式直接在 grep 中执行此操作,但我没有能力。

    编辑:

    稍有改进,更多依赖grep

    antenna0 <- readLines("antenna.txt")
    antenna1 <- antenna0[grep("^Reader.*[\\s{4,}]", antenna0)]
    antenna2 <- do.call(rbind, strsplit(antenna1, " "))
    data.frame(antenna3)
    
    #      X1 X2       X3       X4 X5  X6           X7
    #1 Reader #2 02:57:40 02/17/13 LA 900 226000012999
    #2 Reader #2 02:57:40 02/17/13 LA 900 226000012999
    #3 Reader #2 02:57:40 02/17/13 LA 900 226000012999
    #4 Reader #2 02:57:40 02/17/13 LA 900 226000012999
    

    简短的解释:

    [\\s{4,}] 表示将返回任何包含四个或更多 ({4,}) 的空格 (\\s) 的字符串。

    ^Reader表示返回任何以字母序列Reader开头的字符串。

    .* 结合了这两种模式,用作 AND 运算符。

    【讨论】:

      【解决方案3】:

      您是否总是在“电池电压”的最后一行之后寻找第一行?如果是这样,试试这个:

      the.file <- readLines("C:\\Users\\myfile.txt")
      row.to.begin.skip.at <- tail(grep("Battery Voltage", the.file), 1)
      

      然后在您喜欢的阅读功能中设置skip=row.to.begin.skip.at。此处的 grep() 函数在每一行中搜索短语“电池电压”,然后返回包含该短语的所有行号的列表。 tail( , 1) 函数返回包含短语的最后一行。

      【讨论】:

      • 感谢您的意见。就我而言,我实际上是在试图找出一些可以识别我想要的数据模式的东西。这是因为除了电池电压之外,各种文件中还有许多诊断读数。
      【解决方案4】:

      您可以将文件读取为文本块并使用grep 来识别您想要删除的行。在这里,我将您的文本块存储在 test.txt 中。假设您的标题一直到Battery Voltage 部分,您可以首先识别包含Battery 的行号,然后找到它的最后一个实例。这将是要跳过的行数。

      con = file('test.txt', 'r')
      text = readLines(con)
      close(con)
      
      lines_to_skip = max(grep('Battery',text))    
      

      然后您应该可以很好地读取您的数据。

      > x = read.table('test.txt', skip=lines_to_skip, sep=' ', comment.char='')
      > x
        V1     V2       V3       V4 V5  V6       V7
      1 Reader #2 02:57:40 02/17/13 LA 900 2.26e+11
      2 Reader #2 02:57:40 02/17/13 LA 900 2.26e+11
      3 Reader #2 02:57:40 02/17/13 LA 900 2.26e+11
      4 Reader #2 02:57:40 02/17/13 LA 900 2.26e+11
      

      【讨论】:

      • 谢谢,在这种情况下确实有效。但是我有数千个这样的文件,而且诊断并不总是仅仅读取电池电压(恰好是我粘贴的那个的开始)。有许多诊断读数,所以我希望找到一些东西来识别我想要开始的数据模式。感谢您的意见,我真的很感激它
      • 在这种情况下,您可以使用相同的方法,但不要使用grep 来查找您不想要的内容,而是使用它来查找您想要的内容。例如,您对带有 Reader 的行感兴趣,因为它们包含您想要的数据,但在您的示例中,标题中似乎有一些行以 Reader 开头.要摆脱这些,您可以在每一行上使用 strsplit 并计算元素的数量。如果它有 7 个元素,你就知道这是你的主要数据。如果它只有 4 个,那就是垃圾,所以不要启动你的 read.table
      猜你喜欢
      • 1970-01-01
      • 2018-01-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-10-08
      相关资源
      最近更新 更多