【问题标题】:Importing CSV files with CRLF broken lines in R在R中导入带有CRLF虚线的CSV文件
【发布时间】:2016-07-29 11:52:29
【问题描述】:

我是一名转向空间数据分析的城市规划师。我一般不会忘记 R 和编程,但由于我没有接受过适当的培训,我的技能有时会受到限制。

目前,我正在尝试分析大约 50 个 CSV 文件,其中包含有关公开拍卖的财务数据,这些文件的长度从 60000 到 300000 行,包含 39 个字段。这些文件是从罗马尼亚国家公开拍卖系统导出的,这是一个类似表格的平台。

问题是地址字段中间的CRLF 行结尾打破了某些行。我怀疑当人们以表格形式输入地址时,他们会从其他多行文件中复制/粘贴地址。

Find&Replace 无法解决该问题,因为这也会替换行尾的正确 CRLF。

作为一个例子,数据的格式是这样的,每行后面都有一个CRLF(他们使用 ^ 作为分隔符):

Castigator^CastigatorCUI^CastigatorTara^CastigatorLocalitate^CastigatorAdresa^Tip^TipContract^TipProcedura^AutoritateContractanta^AutoritateContractantaCUI^TipAC^TipActivitateAC^NumarAnuntAtribuire^DataAnuntAtribuire^TipIncheiereContract^TipCriteriiAtribuire^CuLicitatieElectronica^NumarOfertePrimite^Subcontractat^NumarContract^DataContract^TitluContract^Valoare^Moneda^ValoareRON^ValoareEUR^CPVCodeID^CPVCode^NumarAnuntParticipare^DataAnuntParticipare^ValoareEstimataParticipare^MonedaValoareEstimataParticipare^FonduriComunitare^TipFinantare^TipLegislatieID^FondEuropean^ContractPeriodic^DepoziteGarantii^ModalitatiFinantare
S.C. RCTHIA CO S.R.L.^65265644^Romania^Bucharest^DN1
Nr. 1, ^Anunt de atribuire la anunt de participare^Furnizare^Licitatie deschisa^COMPANIA NATIONALA DE TRANSPORT AL ENERGIEI ^R656556^^Electricitate^96594^2007-12-14^Un contract de achizitii publice^Pretul cel mai scazut^^1^^61^2007-11-08 00:00:00.000^Televizoare^304503.95^RON^304503.950000000001^89650.5^45937^323124100-1^344578^2007-10-02^49700.00^RON^^^^^^Nu este cazul;^Surse proprii;
ASOC : SC MNG SRLsi SC AquaiM SA ^56565575;656224^Romania^Ploiesti^Str. Independentei nr.15; 
Str. Carol nr. 45^Anunt de atribuire la anunt de participare^Lucrari^Negociere fara anunt de participare^MUNICIPIUL RAMNICU VALCEA^6562655^Administratie publica locala (municipii, orase, comune), institutie publica in subordonarea/coordonarea administratiei publice locale^Servicii generale ale administratiilor publice^56566^2007-10-10^Un contract de achizitii publice^Pretul cel mai scazut^^1^^65656^2007-09-12^Proiectare si executie lucrari^5665560.00^RON^659966.0^5455222^7140^65689966-2^^^^^^^^^^^

为了正确处理数据,我需要像这样读取 CSV,只删除断行的CRLF - Find&Replace 无法做到:

Castigator^CastigatorCUI^CastigatorTara^CastigatorLocalitate^CastigatorAdresa^Tip^TipContract^TipProcedura^AutoritateContractanta^AutoritateContractantaCUI^TipAC^TipActivitateAC^NumarAnuntAtribuire^DataAnuntAtribuire^TipIncheiereContract^TipCriteriiAtribuire^CuLicitatieElectronica^NumarOfertePrimite^Subcontractat^NumarContract^DataContract^TitluContract^Valoare^Moneda^ValoareRON^ValoareEUR^CPVCodeID^CPVCode^NumarAnuntParticipare^DataAnuntParticipare^ValoareEstimataParticipare^MonedaValoareEstimataParticipare^FonduriComunitare^TipFinantare^TipLegislatieID^FondEuropean^ContractPeriodic^DepoziteGarantii^ModalitatiFinantare
S.C. RCTHIA CO S.R.L.^65265644^Romania^Bucharest^DN1 Nr. 1, ^Anunt de atribuire la anunt de participare^Furnizare^Licitatie deschisa^COMPANIA NATIONALA DE TRANSPORT AL ENERGIEI ^R656556^^Electricitate^96594^2007-12-14^Un contract de achizitii publice^Pretul cel mai scazut^^1^^61^2007-11-08 00:00:00.000^Televizoare^304503.95^RON^304503.950000000001^89650.5^45937^323124100-1^344578^2007-10-02^49700.00^RON^^^^^^Nu este cazul;^Surse proprii;
ASOC : SC MNG SRLsi SC AquaiM SA ^56565575;656224^Romania^Ploiesti^Str. Independentei nr.15; Str. Carol nr. 45^Anunt de atribuire la anunt de participare^Lucrari^Negociere fara anunt de participare^MUNICIPIUL RAMNICU VALCEA^6562655^Administratie publica locala (municipii, orase, comune), institutie publica in subordonarea/coordonarea administratiei publice locale^Servicii generale ale administratiilor publice^56566^2007-10-10^Un contract de achizitii publice^Pretul cel mai scazut^^1^^65656^2007-09-12^Proiectare si executie lucrari^5665560.00^RON^659966.0^5455222^7140^65689966-2^^^^^^^^^^^

我找到了一个可能的解决方案 (Is there a way in R to join broken lines of csv file?),但需要进行一些调整才能满足我的需求。最终结果是下面的代码挂起并且没有到达进程的结尾,即使在小样本文件上也是如此。

我对上述帖子中接受的解决方案代码的更改:

dat <- readLines("filename.csv") # read whatever is in there, one line at a time
varnames <- unlist(strsplit(dat[1], "^", fixed = TRUE)) # extract variable names
nvar <- length(varnames)

k <- 1 # setting up a counter
dat1 <- matrix(NA, ncol = nvar, dimnames = list(NULL, varnames))

while(k <= length(dat)){
  k <- k + 1
  if(dat[k] == "") {k <- k + 1
  print(paste("data line", k, "is an empty string"))
  if(k > length(dat)) {break}
  }
  temp <- dat[k]
  # checks if there are enough commas or if the line was broken
  while(length(gregexpr("^", temp)[[1]]) < nvar-1){
    k <- k + 1
    temp <- paste0(temp, dat[k])
  }
  temp <- unlist(strsplit(temp, "^"))
  message(k)
  dat1 <- rbind(dat1, temp)
}

dat1 = dat1[-1,] # delete the empty initial row    

在分隔符之间计算字段似乎是一个很好的解决方案,但我找不到这样做的好方法,而且我的 R 编程技能显然还不够。

那么有没有办法在 R 中修复这种损坏的 CSV 文件?

工作文件示例可在此处访问:http://data.gv.ro/dataset/4a4903c4-b1e3-46d1-82a5-238287f9496c/resource/c6abc0ef-3efb-4aef-bc0a-411f8cab2a28/download/contracte-2007.csv

感谢您提供的任何帮助!

【问题讨论】:

    标签: r csv import data-processing data-cleaning


    【解决方案1】:

    问题似乎在于 ^ 是一个特殊字符。如果您单步执行您的代码,您将看到您有 627 个变量而不是 39 个。它使每个字符成为一个变量。试试这个:

    dat <- readLines("filename.csv") # read whatever is in there, one line at a time
    varnames <- unlist(strsplit(dat[1], "\\^"))  # extract variable names
    nvar <- length(varnames)
    
    k <- 1 # setting up a counter
    dat1 <- matrix(NA, ncol = nvar, dimnames = list(NULL, varnames))
    
    while(k <= length(dat)){
      k <- k + 1
      #if(dat[k] == "") {k <- k + 1
      #print(paste("data line", k, "is an empty string"))
      if(k > length(dat)) {break}
      #}
      temp <- dat[k]
      # checks if there are enough commas or if the line was broken
      while(length(gregexpr("\\^", temp)[[1]]) < nvar-1){
        k <- k + 1
        temp <- paste0(temp, dat[k])
      }
      temp <- unlist(strsplit(temp, "\\^"))
      message(k)
      dat1 <- rbind(dat1, temp)
    }
    
    dat1 = dat1[-1,] # delete the empty initial row    
    

    很抱歉错过了您和我的代码中的差异。你不想固定=真。将其更改为上述内容可以为您提供:

    > varnames
     [1] "Castigator"                       "CastigatorCUI"                    "CastigatorTara"                  
     [4] "CastigatorLocalitate"             "CastigatorAdresa"                 "Tip"                             
     [7] "TipContract"                      "TipProcedura"                     "AutoritateContractanta"          
    [10] "AutoritateContractantaCUI"        "TipAC"                            "TipActivitateAC"                 
    [13] "NumarAnuntAtribuire"              "DataAnuntAtribuire"               "TipIncheiereContract"            
    [16] "TipCriteriiAtribuire"             "CuLicitatieElectronica"           "NumarOfertePrimite"              
    [19] "Subcontractat"                    "NumarContract"                    "DataContract"                    
    [22] "TitluContract"                    "Valoare"                          "Moneda"                          
    [25] "ValoareRON"                       "ValoareEUR"                       "CPVCodeID"                       
    [28] "CPVCode"                          "NumarAnuntParticipare"            "DataAnuntParticipare"            
    [31] "ValoareEstimataParticipare"       "MonedaValoareEstimataParticipare" "FonduriComunitare"               
    [34] "TipFinantare"                     "TipLegislatieID"                  "FondEuropean"                    
    [37] "ContractPeriodic"                 "DepoziteGarantii"                 "ModalitatiFinantare" 
    

    【讨论】:

    • 嗨,迈克尔,感谢您的回复。您发布的解决方案对我不起作用。我更新了数据样本以反映真实数据。您能否快速测试一下问题是否出在我的机器上?将不胜感激!
    • 您建议的代码没有用“^”分隔符分隔我的示例数据中的标题。
    • 你是对的,fixed=true 不应该在那里。请参阅上面的固定代码并输出。
    • 嗨,迈克尔,这似乎可以解决问题,但是如果您在上面的示例数据上对其进行测试,您会得到 In rbind(dat1, temp) : number of columns of result is not a multiple of vector length (arg 2) 吗?我在某些行上得到了这个,而不是在所有行上。在示例数据中,我得到了第二个数据,最后一个字段填充了同一行中第一个字段的值。
    • 我设法解决了这个问题。如果字段后面跟着CRLF,strsplit 会删除行的最后一个字段,因此为了补偿我将strpsplit 修改为temp:temp &lt;- unlist(strsplit(paste(temp, "^"),"\\^"))。
    【解决方案2】:

    我们可以通过检查它是否以数字字段结尾来确定每条记录的最后一行。然后使用 cumsum 我们可以使用 1, 2, 3, ... 标记同一记录中的行。最后将它们粘贴在一起。

    # test data
    Lines <- "Name^FiscCode^Country^Adress^SomeData^
    SomeCompany^235356^Romania^Adress1
    Adress2^ 565863
    SomeCompany^235356^Romania^Adress1^ 565863"
    
    # for real problem use readLines("myfile")[-1]
    L <- readLines(textConnection(Lines))[-1]
    
    g <- rev(cumsum(rev(grepl("\\^ *\\d+$", L)))) ##
    g <- max(g) - g + 1
    L2 <- tapply(L, g, paste, collapse = " ")
    read.table(text = L2, sep = "^")
    

    以上适用于问题中显示的数据,但如果实际数据与您显示的数据存在差异,则可能需要根据这些差异进行一些修改。

    注意:如果每条记录中总是有四个 ^ 字符,请尝试将标有 ## 的行替换为:

    cnt <- count.fields(textConnection(L), sep = "^") - 1
    g <- rev(cumsum(rev(cumsum(cnt) %% 4 == 0)))
    

    更新问题已更改以提供新的示例数据。请注意,发布的答案适用于它,但当然您需要将 4 替换为 38,因为新数据每条记录有 38 个分隔符,而旧数据有 4 个。旧数据有一个标题,而新数据没有,所以我们有删除了那些用于删除标题的 -1。这是一个可以复制并粘贴到 R 中的自包含示例。

    Lines <- "Castigator^CastigatorCUI^CastigatorTara^CastigatorLocalitate^CastigatorAdresa^Tip^TipContract^TipProcedura^AutoritateContractanta^AutoritateContractantaCUI^TipAC^TipActivitateAC^NumarAnuntAtribuire^DataAnuntAtribuire^TipIncheiereContract^TipCriteriiAtribuire^CuLicitatieElectronica^NumarOfertePrimite^Subcontractat^NumarContract^DataContract^TitluContract^Valoare^Moneda^ValoareRON^ValoareEUR^CPVCodeID^CPVCode^NumarAnuntParticipare^DataAnuntParticipare^ValoareEstimataParticipare^MonedaValoareEstimataParticipare^FonduriComunitare^TipFinantare^TipLegislatieID^FondEuropean^ContractPeriodic^DepoziteGarantii^ModalitatiFinantare
    S.C. RCTHIA CO S.R.L.^65265644^Romania^Bucharest^DN1
    Nr. 1, ^Anunt de atribuire la anunt de participare^Furnizare^Licitatie deschisa^COMPANIA NATIONALA DE TRANSPORT AL ENERGIEI ^R656556^^Electricitate^96594^2007-12-14^Un contract de achizitii publice^Pretul cel mai scazut^^1^^61^2007-11-08 00:00:00.000^Televizoare^304503.95^RON^304503.950000000001^89650.5^45937^323124100-1^344578^2007-10-02^49700.00^RON^^^^^^Nu este cazul;^Surse proprii;
    ASOC : SC MNG SRLsi SC AquaiM SA ^56565575;656224^Romania^Ploiesti^Str. Independentei nr.15; 
    Str. Carol nr. 45^Anunt de atribuire la anunt de participare^Lucrari^Negociere fara anunt de participare^MUNICIPIUL RAMNICU VALCEA^6562655^Administratie publica locala (municipii, orase, comune), institutie publica in subordonarea/coordonarea administratiei publice locale^Servicii generale ale administratiilor publice^56566^2007-10-10^Un contract de achizitii publice^Pretul cel mai scazut^^1^^65656^2007-09-12^Proiectare si executie lucrari^5665560.00^RON^659966.0^5455222^7140^65689966-2^^^^^^^^^^^"
    
    L <- readLines(textConnection(Lines))
    
    cnt <- count.fields(textConnection(L), sep = "^") - 1   # 38 4 34 4 34
    g <- rev(cumsum(rev(cumsum(cnt) %% 38 == 0)))
    g <- max(g) - g + 1   # 1 2 2 3 3
    L2 <- tapply(L, g, paste, collapse = " ")
    DF <- read.table(text = L2, sep = "^")
    dim(DF)
    ## [1]  3 39
    

    示例数据不包含注释字符 (#) 或单引号或双引号,但如果确实包含这些是其数据的一部分,则需要将 comment.char = "", quote = "" 添加到 count.fields 和 read.table 调用中。

    【讨论】:

    • 感谢您的回复!事实上,数据更加多样化。该示例只是为了举例说明行中的中断。有些行缺少几个属性,并以 ^^^^^^^^^ 结尾,例如结尾必须是空字段。另外如何处理完整的行(没有意外中断)?我追求的想法(受上述帖子的启发)是计算分隔符之间的字段,如果值小于 39,那么它将从下一行添加字段,直到长度达到 39。我不知道我是否清楚。
    • 只要您能提出一个条件,即每条记录的最后一行始终满足该条件,则可以扩展该解决方案。例如,如果每条记录中只有最后一行以数字或空字段结尾,那么只需适当修改答案中的第一行 g&lt;- 即可,即将 + 替换为 *。
    • 我还添加了一个注释,如果每条记录的字段数始终相同,则该注释将起作用。
    • 从你的笔记中我收集到,为了有 R 计数并添加字段,直到它达到标题中的字段数,我应该有这样的东西(我有 39 个字段):L &lt;- readLines("testfile.csv")[-1] cnt &lt;- count.fields("testfile.csv", sep = "^") - 1 g &lt;- rev(cumsum(rev(cumsum(cnt) %% 39 == 0))) g &lt;- max(g) - g + 1 L2 &lt;- tapply(L, g, paste, collapse = " ")我的真实数据的问题是我收到了这个错误:Error in tapply(L, g, paste, collapse = " ") : arguments must have same length
    • 如果有 39 个字段,那么有 38 个分隔符,而不是 39,所以你使用 38, ;但是,这确实假设所有行都有 39 个或更少的字段,这可能是真的,也可能不是。您可以使用count.fields 找出每条记录中有多少字段,如果没有太多坏字段,只需使用好的行。 cnt &lt;- count.fields("myfile", sep = "^", quote = "")[-1]; sum(cnt == 39); sum(cnt != 39); table(cnt)
    猜你喜欢
    • 1970-01-01
    • 2019-03-25
    • 1970-01-01
    • 2013-01-29
    • 2013-08-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-04-14
    相关资源
    最近更新 更多