将空白单元格更改为“NA”

115
这是我的数据link
我的目标是为所有空单元格分配“NA”,无论是分类还是数值。我正在使用na.strings = ""。但它并没有为所有的空单元格分配“NA”。
## reading the data
dat <- read.csv("data2.csv")
head(dat)
  mon hr        acc   alc sex spd axles door  reg                                 cond1 drug1
1   8 21 No Control  TRUE   F   0     2    2      Physical Impairment (Eyes, Ear, Limb)     A
2   7 20 No Control FALSE   M 900     2    2                                Inattentive     D
3   3  9 No Control FALSE   F 100     2    2 2004                                Normal     D
4   1 15 No Control FALSE   M   0     2    2      Physical Impairment (Eyes, Ear, Limb)     D
5   4 21 No Control FALSE      25    NA   NA                                                D
6   4 20 No Control    NA   F  30     2    4                Drinking Alcohol - Impaired     D
       inj1 PED_STATE st rac1
1     Fatal      <NA>  F <NA>
2  Moderate      <NA>  F <NA>
3  Moderate      <NA>  M <NA>
4 Complaint      <NA>  M <NA>
5 Complaint      <NA>  F <NA>
6  Moderate      <NA>  M <NA>


## using na.strings
dat2 <- read.csv("data2.csv", header=T, na.strings="")
head(dat2)
  mon hr        acc   alc sex spd axles door  reg                                 cond1 drug1
1   8 21 No Control  TRUE   F   0     2    2 <NA> Physical Impairment (Eyes, Ear, Limb)     A
2   7 20 No Control FALSE   M 900     2    2 <NA>                           Inattentive     D
3   3  9 No Control FALSE   F 100     2    2 2004                                Normal     D
4   1 15 No Control FALSE   M   0     2    2 <NA> Physical Impairment (Eyes, Ear, Limb)     D
5   4 21 No Control FALSE      25    NA   NA <NA>                                  <NA>     D
6   4 20 No Control    NA   F  30     2    4 <NA>           Drinking Alcohol - Impaired     D
       inj1 PED_STATE st rac1
1     Fatal        NA  F   NA
2  Moderate        NA  F   NA
3  Moderate        NA  M   NA
4 Complaint        NA  M   NA
5 Complaint        NA  F   NA
6  Moderate        NA  M   NA

请使用文本而非图片/链接来呈现文本,包括表格和ERD。可以进行改写或引用其他文本。仅在无法用文本表达或为文本增添信息时使用图片。图片无法被搜索或复制粘贴。对于图片,请附上图例/键和解释。确保您的帖子是自包含的。使用编辑功能插入图片/链接。 - philipxy
15个回答

127

我假设您正在谈论第5行的“性别”列。在data2.csv文件中,单元格可能包含空格,因此R不认为它为空。

另外,我注意到在第5行的“轴”和“门”列中,从data2.csv读取的原始值是字符串“NA”。您可能也希望将它们视为na.strings。要执行此操作,

dat2 <- read.csv("data2.csv", header=T, na.strings=c("","NA"))

编辑:

我下载了你的data2.csv文件。是的,在第5行的“sex”列中有一个空格。所以你想要

na.strings=c(""," ","NA")

53

这应该就可以解决问题了

dat <- dat %>% mutate_all(na_if,"")

1
我在一个sf对象上尝试了这个操作,但它抛出了一个解析错误:未知的WKB类型12。似乎mutate尝试替换几何图形中的某些内容。 - aae
mutate_all现已被mutate + across组合所取代。请参考@glenn_in_boston的答案,了解如何简洁地实现此操作。 - Bradford

39
您可以使用gsub函数将多个空字符替换为NA,例如""或一个空格:
data= data.frame(cats=c('', ' ', 'meow'), dogs=c("woof", " ", NA))
apply(data, 2, function(x) gsub("^$|^ $", NA, x))

5
还可以使用 gsub("^$", NA, trimws(x)) 处理单元格内的多个空格。但是请注意,这两种方法都会将所有列转换为字符串/字符变量(如果尚未转换)。 - JWilliman

31

使用 dplyr 更加友好的解决方案是:

A more eye-friendly solution using dplyr would be

require(dplyr)

## fake blank cells
iris[1,1]=""

## define a helper function
empty_as_na <- function(x){
    if("factor" %in% class(x)) x <- as.character(x) ## since ifelse wont work with factors
    ifelse(as.character(x)!="", x, NA)
}

## transform all columns
iris %>% mutate_each(funs(empty_as_na)) 

您可以使用dplyr的列匹配语法指定感兴趣的列来仅将更正应用于子集。例如:mutate_each(funs(empty_as_na), matches("Width"), Species)

如果您的表包含日期,建议使用更安全类型的 ifelse 版本。


14
添加新库、创建新函数如何更加易读?我认为你需要使用 ifelse(x %in% c(""," ","NA"), NA, x) 这个函数。 - zx8754
5
使用mutate_each与函数结合使用可以提供更多的灵活性和可重用的模式。 dplyr在现今的R工作流程中随处可见,并且被添加到答案中以使其完整自足。 我认为这里使用 x!=""是正确的,因为“”和“NA”都不是空白。 此外,@sclarky的答案对包含数字的数据框无效,而@Badoe的答案并未真正解决现有数据框的问题,因此似乎没有其他答案能够以一般方式回答这个问题。我很高兴学到更好的解决方案。 - Holger Brandl
1
dplyr在现今的R工作流中无处不在 - 实际上并非如此。而“and @Badoe's does not really solve the problem for existing data.frames”这句话是什么意思?你能详细解释一下吗? - David Arenburg
11
Badoe详细说明了如何配置read.csv,在从文件读取表时将空单元格转换为NA。然而,由于问题的标题是“将空白单元格更改为“NA””,完整的答案应该涵盖数据框已经存在于环境中且用户想要去掉空白单元格的情况。 - Holger Brandl
如果您读取的是 Excel 文件,而 na.strings 选项不存在,则此答案也非常有用。 - Marinka
1
这可能不是OP想要的,但它帮助我计算缺失值,包括空字符串和NAs。 df%>% mutate_all(funs(empty_as_na))%>% summarize_all(funs(sum(is.na(.))))虽然dplyr在采用方面可能并不普遍,但它在R用户的大型子集中很受欢迎,包括我,所以感谢这个解决方案。 - Dannid

21

我最近也遇到了类似的问题,以下是解决方法:

如果变量是数字型的,那么简单的 df$Var[df$Var == ""] <- NA 就足够了。但是如果变量是分类型的(factor),那么你需要先将其转换为字符型,然后用你需要的值替换""的单元格,再将其转换回分类型。所以,就拿你的Sex变量举例,我假设它是分类型的,如果你想要替换空单元格,那么我会这样做:

df$Var <- as.character(df$Var)
df$Var[df$Var==""] <- NA
df$Var <- as.factor(df$Var)

16

截至 (dplyr 1.0.0) 版本,我们可以使用 across()

注意:有时在变量中使用NA会导致问题,您可能需要指定NA的类型 - 例如对于此情况,应使用NA_character_。对于嵌套的ifelse()语句,您可以使用case_when()

对于所有列:

dat <- dat %>%
   mutate(across(everything(), ~ifelse(.=="", NA, as.character(.))))

对于单个列:

dat <- dat %>%
   mutate(across(c("Age","Gender"), ~ifelse(.=="", NA, as.character(.))))

dplyr 0.8.0 开始,编写应该使用新的方式。 以前是在 .funs (funs(name = f(.))) 中使用 funs() 。现在我们使用 list(list(name = ~f(.))) 而不是 funs

请注意,还有一种更简单的方法可以列出列名! (列名和列索引的名称都适用)

dat <- dat %>%
mutate_at(.vars = c("Age","Gender"),
    .funs = list(~ifelse(.=="", NA, as.character(.))))
你也可以在 dplyr 中使用 mutate_at
dat <- dat %>%
mutate_at(vars(colnames(.)),
        .funs = funs(ifelse(.=="", NA, as.character(.))))

选择要更改的各个列:

dat <- dat %>%
mutate_at(vars(colnames(.)[names(.) %in% c("Age","Gender")]),
        .funs = funs(ifelse(.=="", NA, as.character(.))))

~ifelse(.=="", NA, as.character(.)))) 这段代码中的 .== 是什么意思? - Daman deep
== is used for equality testing and . references variables it is testing - for example for across(c("Age","Gender"), . would reference "Age" and then "Gender" - camnesia
当你有更复杂的数据类型,如因子或日期时,这不是一个好的解决方案。请勿使用此解决方案 - 如果您有日期,请改用https://dev59.com/cmAf5IYBdhLWcg3w52A_#64106339。但它也无法处理因子。 - tjebo

12

更新的答案 在 @camnesia 的优秀选项基础上,我发现一些有用的选项可以利用 dplyr 的 across() 来构建:

使用 na_if()

mutate(across(c("年龄","性别"), ~na_if(., ""))).

另外,也许值得注意的是,在使用 c("") 指定列的同时,您可以使用 dplyr 选择器:

mutate(across(starts_with("x_"), ~na_if(., ""))).

最后,如果您想要用NA替换多个值,我还喜欢用replace():

使用 replace():

mutate(across(everything(), ~replace(., . %in% c("N.A.", "NA", "N/A", ""), NA)))

原始答案 我猜每个人都已经有答案了,不过以防万一,从我的角度来看,dplyr na_if()将是更有效的那些解决方案之一:

# Import CSV, convert all 'blank' cells to NA
dat <- read.csv("data2.csv") %>% na_if("")

这里提供了另一种使用readr的read_delim函数的方法。我只是采用了这种方法(可能已经被广泛采用,但我将在此处进行存档以供未来用户使用)。这种方法非常简单直观,比上述方法更加灵活,因为您可以捕获csv文件中的所有类型的空白和NA相关值:

dat <- read_csv("data2.csv", na = c("", "NA", "N/A"))

请注意 readr 版本中下划线和 Base R 版本中点号之间的差异,希望这能帮助那些看到此篇文章的人!

谢谢。你关于如何使用整洁选择的详细说明对我非常有帮助。 - Bradford

9

我认为这是您最好的选择(代码简洁和速度)。 以下内容将在名为“data”的数据集中用NA替换所有空格:

data[data==""] <- NA

这是一个很好的解决方案,但与数据表无关 - 这是纯粹的基础R。 - tjebo

5
我的功能考虑到因素、字符向量和潜在属性,如果你使用haven或foreign软件包来读取外部文件的话。同时它允许匹配不同的自定义na.strings。要转换所有列,只需使用lapply: df[] = lapply(df, blank2na, na.strings=c('','NA','na','N/A','n/a','NaN','nan'))
请查看更多注释:
#' Replaces blank-ish elements of a factor or character vector to NA
#' @description Replaces blank-ish elements of a factor or character vector to NA
#' @param x a vector of factor or character or any type
#' @param na.strings case sensitive strings that will be coverted to NA. The function will do a trimws(x,'both') before conversion. If NULL, do only trimws, no conversion to NA.
#' @return Returns a vector trimws (always for factor, character) and NA converted (if matching na.strings). Attributes will also be kept ('label','labels', 'value.labels').
#' @seealso \code{\link{ez.nan2na}}
#' @export
blank2na = function(x,na.strings=c('','.','NA','na','N/A','n/a','NaN','nan')) {
    if (is.factor(x)) {
        lab = attr(x, 'label', exact = T)
        labs1 <- attr(x, 'labels', exact = T)
        labs2 <- attr(x, 'value.labels', exact = T)

        # trimws will convert factor to character
        x = trimws(x,'both')
        if (! is.null(lab)) lab = trimws(lab,'both')
        if (! is.null(labs1)) labs1 = trimws(labs1,'both')
        if (! is.null(labs2)) labs2 = trimws(labs2,'both')

        if (!is.null(na.strings)) {
            # convert to NA
            x[x %in% na.strings] = NA
            # also remember to remove na.strings from value labels 
            labs1 = labs1[! labs1 %in% na.strings]
            labs2 = labs2[! labs2 %in% na.strings]
        }

        # the levels will be reset here
        x = factor(x)

        if (! is.null(lab)) attr(x, 'label') <- lab
        if (! is.null(labs1)) attr(x, 'labels') <- labs1
        if (! is.null(labs2)) attr(x, 'value.labels') <- labs2
    } else if (is.character(x)) {
        lab = attr(x, 'label', exact = T)
        labs1 <- attr(x, 'labels', exact = T)
        labs2 <- attr(x, 'value.labels', exact = T)

        # trimws will convert factor to character
        x = trimws(x,'both')
        if (! is.null(lab)) lab = trimws(lab,'both')
        if (! is.null(labs1)) labs1 = trimws(labs1,'both')
        if (! is.null(labs2)) labs2 = trimws(labs2,'both')

        if (!is.null(na.strings)) {
            # convert to NA
            x[x %in% na.strings] = NA
            # also remember to remove na.strings from value labels 
            labs1 = labs1[! labs1 %in% na.strings]
            labs2 = labs2[! labs2 %in% na.strings]
        }

        if (! is.null(lab)) attr(x, 'label') <- lab
        if (! is.null(labs1)) attr(x, 'labels') <- labs1
        if (! is.null(labs2)) attr(x, 'value.labels') <- labs2
    } else {
        x = x
    }
    return(x)
}

2

尽管上述的选项都可以很好地运行,但我发现将非目标变量强制转化为chr会存在问题。在lapply中使用ifelsegrepl可以解决这种非目标效应(经过有限测试)。在grepl中使用slarky的正则表达式:

set.seed(42)
x1 <- sample(c("a","b"," ", "a a", NA), 10, TRUE)
x2 <- sample(c(rnorm(length(x1),0, 1), NA), length(x1), TRUE)

df <- data.frame(x1, x2, stringsAsFactors = FALSE)

字符类强制转换的问题:

df2 <- lapply(df, function(x) gsub("^$|^ $", NA, x))
lapply(df2, class)

$x1 [1] "字符型"

$x2 [1] "字符型"

使用ifelse解决分辨率问题:

df3 <- lapply(df, function(x) ifelse(grepl("^$|^ $", x)==TRUE, NA, x))
lapply(df3, class)

$x1 [1] "字符型"

$x2 [1] "数字型"


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接