使用十进制和分组数字混合对财务数据进行整理

时间:2020-01-13 14:47:32

标签: r

上下文

我需要清除混合格式的财务数据。数据已由不同部门手动打入,其中一些部门使用“”。作为小数点,“,”作为分组数字(例如,美国符号:$ 1,000,000.00),而其他人则将“,”用作小数点和“”。作为分组数字(例如,在某些欧洲国家/地区使用的符号:$ 1.000.000,00)。

输入:

这是一个虚构的示例集:

  df <- data.frame(Y2019= c("17.530.000,03","28000000.05", "256.000,23", "23,000", 
                            "256.355.855","2565467,566","225,453.126") 
  )
          Y2019
1 17.530.000,03
2   28000000.05
3    256.000,23
4        23,000
5   256.355.855
6   2565467,566
7   225,453.126

所需结果:

         Y2019
1  17530000.03
2  28000000.05
3    256000.23
4     23000.00
5 256355855.00
6  2565467.566
7   225453.126

我的尝试:

通过考虑第一次出现(从右开始)“,”或“”,我已经接近了。作为小数运算符,并相应地替换其他出现的内容。但是,某些条目没有小数点(例如,条目4和5)或具有可变的小数位数,因此该策略的用处不大。

非常感谢任何输入!

编辑: 根据要求,我提供了一些原始尝试的代码。我相信它可以写得更干净。

  df %>%
    mutate(Y2019r = ifelse(str_length(Y2019)- data.frame(str_locate(pattern =",",Y2019 ))[,1]==2, gsub("\\.","", Y2019),NA )) %>%
    mutate(Y2019r = ifelse((is.na(Y2019r) & str_length(Y2019)- data.frame(str_locate(pattern ="\\.",Y2019 ))[,1]==2), gsub("\\.",",", Y2019),Y2019r ))%>%
    mutate(Y2019r =  gsub(",",".", Y2019r))

         Y2019      Y2019r
1 17.530.000,03 17530000.03
2   28000000.05 28000000.05
3    256.000,23   256000.23
4        23,000        <NA>
5   256.355.855        <NA>
6   2565467,566        <NA>
7   225,453.126        <NA>

1 个答案:

答案 0 :(得分:1)

这是一种实用的方法,可用于建立解析可能遇到的字符串所需的逻辑。我想它是通过思考读取字符串时如何解析这些字符串并尝试模拟它们而构建的。

我认为关键是要意识到,我们真正需要知道的是最后一个定界符之后的值是否为十进制。如果我们能以某种方式将字符串标记为具有小数部分,那么解析字符串就容易了。

以下方法涉及在点和逗号处分割字符串,并尝试将其标记为是否具有末尾小数。分割后的字符串将作为字符串向量的列表保存,每个向量由定界符之间的数字“块”组成。

首先,一旦我们正确地将它们标记为具有末尾小数部分,我们将编写两个帮助函数以从字符串向量创建最终数字:

last_element_is_decimal <- function(x)
{
  as.numeric(paste0(paste(x[-length(x)], collapse = ""), ".", x[length(x)]))
}

last_element_is_whole <- function(x)
{
  as.numeric(paste0(x, collapse = ""))
}

在没有定界符的情况下,很容易决定要做什么,因为我们假设这些只是整数。同样,很容易看出,任何同时包含逗号和停止位的数字(以任何顺序排列)都必须具有末尾小数部分。

但是,只有一个定界符时该怎么做就不那么明显了。在这些情况下,我们必须使用数字块的长度来确定。如果任何块的长度超过三位数,则不使用千位分隔符,并且定界符的存在表明我们有一个十进制成分。如果终端块仅包含两位数字,那么我们必须有一个小数。在所有其他情况下,我们都假设一个整数。

这在代码中说了同样的话:

decide_last_element <- function(x)
{
   if(max(nchar(x)) > 3)
     return(last_element_is_decimal(x))
   if(nchar(x[length(x)]) < 3)
     return(last_element_is_decimal(x))
  return(last_element_is_whole(x))
}

现在我们可以编写我们的主要功能了。它以我们的字符串为输入,并将每个字符串分类为具有两种类型的定界符,一种类型的定界符或无定界符。然后,我们可以相应地使用lapply来应用上述功能。

parse_money <- function(money_strings)
{
  any_comma       <- grepl(",",   money_strings)
  any_point       <- grepl("[.]", money_strings)

  both            <- any_comma & any_point
  neither         <- !any_comma & !any_point
  single          <- (any_comma & !any_point) | (any_point & !any_comma)
  digit_groups    <- strsplit(money_strings, "[.]|,")

  values          <- rep(0, length(money_strings))

  values[neither] <- as.numeric(money_strings[neither])
  values[both]    <- sapply(digit_groups[both], last_element_is_decimal)
  values[single]  <- sapply(digit_groups[single], decide_last_element)
  return(format(round(values, 2), nsmall = 2))
}

所以我们现在可以做

parse_money(df$Y2019)
#> [1] " 17530000.03" " 28000000.05" "   256000.23" "    23000.00" "256355855.00"
#> [6] "  2565467.57" "   225453.13"

请注意,我将输出作为字符串输出,以便控制台输出中的舍入误差不会归因于代码中的错误。