在R中清理格式不一致的数据?

机器算法验证 r 数据预处理
2022-02-06 02:03:45

我经常处理凌乱的调查数据,在进行任何统计之前需要进行大量清理。我曾经在 Excel 中“手动”执行此操作,有时使用 Excel 公式,有时会逐个检查条目。我开始通过编写脚本在 R 中执行这些任务来完成越来越多的任务,这非常有益(好处包括记录所做的事情、减少出错的机会以及能够在数据集为更新)。

但是仍然有一些类型的数据我无法有效地处理。例如:

> d <- data.frame(subject = c(1,2,3,4,5,6,7,8,9,10,11),
+   hours.per.day = c("1", "2 hours", "2 hr", "2hr", "3 hrs", "1-2", "15 min", "30 mins", "a few hours", "1 hr 30 min", "1 hr/week"))
> d
   subject hours.per.day
1        1             1
2        2       2 hours
3        3          2 hr
4        4           2hr
5        5         3 hrs
6        6           1-2
7        7        15 min
8        8       30 mins
9        9   a few hours
10      10   1 hr 30 min
11      11     1 hr/week

hours.per.day是指每天花在某项活动上的平均小时数,但我们所拥有的正是该主题所写的。假设我对如何处理模棱两可的响应做出了一些决定,并且我想要整理后的变量hours.per.day2如下。

   subject hours.per.day hours.per.day2
1        1             1      1.0000000
2        2       2 hours      2.0000000
3        3          2 hr      2.0000000
4        4           2hr      2.0000000
5        5         3 hrs      3.0000000
6        6           1-2      1.5000000
7        7        15 min      0.2500000
8        8       30 mins      0.5000000
9        9   a few hours      3.0000000
10      10   1 hr 30 min      1.5000000
11      11     1 hr/week      0.1428571

假设案例的数量很大(比如 1000 个)并且知道受试者可以自由地写任何他们喜欢的东西,那么解决这个问题的最佳方法是什么?

4个回答

我会使用 gsub() 来识别我知道的字符串,然后可能手动完成其余的工作。

test <- c("15min", "15 min", "Maybe a few hours", 
          "4hr", "4hour", "3.5hr", "3-10", "3-10")
new_var <- rep(NA, length(test))

my_sub <- function(regex, new_var, test){
    t2 <- gsub(regex, "\\1", test)
    identified_vars <- which(test != t2)
    new_var[identified_vars] <- as.double(t2[identified_vars])
    return(new_var)    
}

new_var <- my_sub("([0-9]+)[ ]*min", new_var, test)
new_var <- my_sub("([0-9]+)[ ]*(hour|hr)[s]{0,1}", new_var, test)

要处理需要手动更改的内容,我建议如下:

# Which have we not found
by.hand <- which(is.na(new_var))

# View the unique ones not found
unique(test[by.hand])
# Create a list with the ones
my_interpretation <- list("3-10"= 5, "Maybe a few hours"=3)
for(key_string in names(my_interpretation)){
    new_var[test == key_string] <- unlist(my_interpretation[key_string])
}

这给出了:

> new_var
[1] 15.0 15.0  3.0  4.0  4.0  3.5  5.0  5.0

正则表达式可能有点棘手,每次我用正则​​表达式做任何事情时,我都会运行一些简单的测试。Se ?regex 用于手册。以下是一些基本行为:

> # Test some regex
> grep("[0-9]", "12")
[1] 1
> grep("[0-9]", "12a")
[1] 1
> grep("[0-9]$", "12a")
integer(0)
> grep("^[0-9]$", "12a")
integer(0)
> grep("^[0-9][0-9]", "12a")
[1] 1
> grep("^[0-9]{1,2}", "12a")
[1] 1
> grep("^[0-9]*", "a")
[1] 1
> grep("^[0-9]+", "a")
integer(0)
> grep("^[0-9]+", "12222a")
[1] 1
> grep("^(yes|no)$", "yes")
[1] 1
> grep("^(yes|no)$", "no")
[1] 1
> grep("^(yes|no)$", "(yes|no)")
integer(0)
> # Test some gsub, the \\1 matches default or the found text within the ()
> gsub("^(yes|maybe) and no$", "\\1", "yes and no")
[1] "yes"

@Max 的建议很好。似乎如果您编写一个识别数字以及常见的与时间相关的单词/缩写的算法,您将获得大部分的成功。这不会是漂亮的代码,但它会起作用,并且您可以在遇到问题案例时随着时间的推移对其进行改进。

但对于更健壮(且最初耗时)的方法,请尝试使用 Google 搜索“解析自然语言时间字符串”。一些有趣的发现是这个开放时间 API、一个很好的Python 模块,以及Stack Overflow上与此类似的许多密切相关的线程之一

基本上,自然语言解析是一个常见问题,您应该寻找除 R 之外的其他语言的解决方案。您可以用另一种语言构建工具,您可以使用 R 访问该工具,或者至少您可以为自己的算法获得好的想法。

对于类似的事情,如果它足够长,我想我想要一个正则表达式和转换规则的列表,并将新值带到另一列(所以你总是有机会在不重新加载原始数据的情况下进行仔细检查) ; RE 将应用于尚未转换的数据,直到所有数据都被转换或所有规则都用尽。最好还保留一个逻辑值列表,指示哪些行尚未转换。

一些这样的规则当然是显而易见的,可能会处理 80-90% 的情况,但问题是总会有一些你不知道会出现(人们非常有创造力)。

然后,您需要一个脚本,该脚本一次向您显示尚未按明显规则列表转换的值的原始值,让您有机会制作正则表达式(例如) 来识别这些案例并为适合它的案例提供的转换,它将其添加到原始列表中并应用于原始向量的尚未转换的行,然后再检查是否还有任何案例要呈现给您.

可以选择跳过一个案例(这样您就可以继续处理更简单的案例)也可能是合理的,这样您就可以将非常困难的案例推到最后。

最坏的情况,你手动做几个。

然后,您可以保留您生成的完整规则列表,以便在数据增长或出现新的类似数据集时再次应用。

我不知道它是否接近最佳实践(我认为那里需要更正式的东西),但就快速处理大量此类数据而言,它可能有一些价值。

R 包含一些用于数据操作的标准函数,可用于数据清理,在其基础包(gsubtransform等)以及各种第三方包中,例如stringrreshapereshape2plyr以下论文描述了这些软件包及其功能的使用示例和最佳实践:http: //vita.had.co.nz/papers/tidy-data.pdf

此外,R 还提供了一些专门针对数据清理和转换的软件包:

以下论文介绍了 R 中数据清理的全面而连贯的方法,包括示例和使用editrulesdeducorrect包,以及对 R 中数据清理的工作流程框架)的描述,我强烈推荐:http ://cran.r-project.org/doc/contrib/de_Jonge+van_der_Loo-Introduction_to_data_cleaning_with_R.pdf