如何处理数据框的行而不将其强制转换为字符向量?

时间:2016-01-29 23:10:11

标签: r dataframe apply

我有这个数据框:

df <- data.frame(
  a = c(0, 1, 0, 1),
  b = c("a", "b", "c", "d")
)
#   a b
# 1 0 a
# 2 1 b
# 3 0 c
# 4 1 d

我想说我想测试每一行的条件并返回"ok""not ok"。这应该有效:

apply(df, 1, function(row){
    if (is.numeric(row[1]) & row[2] != "b") {
        "ok"
    } else {
        "not ok"
    }
})
# I should return: "ok" "not ok" "ok" "ok"

不幸的是apply强制数据帧为单一类型,因此所有内容都被视为一个字符,所以这是我得到的输出:

# "not ok" "not ok" "not ok" "not ok"

有没有办法浏览保留数据类型的数据行?也许使用dplyr::dopurrr::map

更新

我知道示例中的条件没有多大意义,但我试图简化更复杂的条件。我想避免使用嵌套的ifelse语句,因为它们不是很易读。

2 个答案:

答案 0 :(得分:2)

评论中建议使用ifelse()的解决方案,这对您来说当然很好:

df$c <- ifelse(is.numeric(df$a) & df$b != "b", "ok", "not ok")
 df
##   a b      c
## 1 0 a     ok
## 2 1 b not ok
## 3 0 c     ok
## 4 1 d     ok

但是更一般的问题是如何在数据帧的行上应用函数而不将其转换为矩阵。一种可能的方法是使用lapply(或其中一个)而不是行索引:

df$c <- vapply(1:nrow(df), function(i){
             if (is.numeric(df[i, 1]) & df[i, 2] != "b") {
               "ok"
             } else {
               "not ok"
             }
           }, character(1))
##  df
##   a b      c
## 1 0 a     ok
## 2 1 b not ok
## 3 0 c     ok
## 4 1 d     ok

同样,在你的情况下,ifelse()就好了。但是如果你想对数据框的行做一些更复杂的事情,那么应用行指数可能就好了。

答案 1 :(得分:1)

这个答案的前半部分正在扩大并试图解释@Joran的优秀评论/答案,这主要是对我和我的理解的练习,但希望它也可以帮助其他人。 (我很高兴能够纠正我的理解。)

下半部分显示了其他一些可用于更复杂情况的非基础解决方案。

Joran的回答
c('not ok','ok')[(is.numeric(df[[1]]) & (df[[2]] != 'b')) + 1]

来自?data.frame

  

数据框是变量列表

因此,data.frame中的每个列/变量都是一个列表

来自?[以及the difference between [ and [[上的这个问题,我们注意到

  

对于列表,通常使用[[选择任何单个元素,而[返回所选元素的列表。

因此,在此解决方案中使用[[选择列表中的单个元素

df[[1]]    ## select the 1st column as a single element (which is a vector)
# [1] 0 1 0 1
df[[2]]    ## select the 2nd column as a single element (which is a vector)
# [1] a b c d 

## note that df[1] would return the first column as a data.frame (which is a list), not a vector
## we can see that by 
# > str(df[1])
# 'data.frame': 4 obs. of  1 variable:
#   $ a: num  0 1 0 1
# > str(df[[1]])
# num [1:4] 0 1 0 1

现在选择了这两个向量,我们可以对其中的每个元素执行向量化逻辑检查

is.numeric(df[[1]]) & (df[[2]] != 'b')
# TRUE FALSE TRUE TRUE

来自?logical我们

  

...将TRUE映射到1L,将FALSE映射到0L ......

基本上是TRUE == 1LFALSE == 0L,我们可以通过

看到
sum(c(TRUE, TRUE, FALSE, TRUE))
# [1] 3

现在,我们选择了一个载体

c("not ok", "ok")
# [1] "not ok" "ok"

我们可以再次使用[来选择每个元素

c("not ok", "ok")[1]
# [1] "not ok"
c("not ok", "ok")[2]
# [1] "ok"
c("not ok", "ok")[3]
# [1] NA
## Because there isn't a 3rd element
c("not ok", "ok")[0]
# character(0)    ## empty
## and we can use a vector to select each element
c("not ok", "ok")[c(1,2,1,3)]
# [1] "not ok" "ok"     "not ok" NA 

这也意味着我们可以使用之前的逻辑比较来对选择进行分组。但是,当FALSE映射到0L时,我们需要向它添加1,以便它能够从向量中选择

c(TRUE, TRUE, FALSE, TRUE) + 1
# [1] 2 2 1 2

给出了

c("not ok", "ok")[c(2,2,1,2)]
# [1] "ok"     "ok"     "not ok" "ok" 

现在,它为我们提供了我们希望包含在原始data.frame

中的信息
df$c <- c("not ok", "ok")[c(2,2,1,2)]
# a b      c
# 1 0 a     ok
# 2 1 b     ok
# 3 0 c not ok
# 4 1 d     ok

非基础解决方案

## a dplyr version, still using ifelse construct
library(dplyr)
df %>%
  mutate(c = ifelse(is.numeric(a) & b != "b", "ok", "not ok")) 

## a couiple of data.table versions using by reference udpates (:=)
library(data.table)
## using an ifelse
setDT(df)[, c := ifelse(is.numeric(a) & b != "b", "ok", "not ok")]

## using filters in i
setDT(df)[is.numeric(a) & b != "b", c := "ok"][is.na(c), c := "not ok"]