遍历数据框并根据条件更改值 [R]

4

因为这个for循环的顺序已经让我很烦恼了,所以我必须注册一个账户。我在R中有一个数据框,它有1000行和10列,每个值都在1到3之间。我想重新编码每个条目,使得:1==3,2==2,3==1。我知道有更简单的方法来做到这一点,比如对每一列进行子集操作并硬编码条件,但这并不总是理想的,因为我处理的许多数据集有多达100列。

我想使用嵌套循环来完成这个任务——这是我目前的进展:

for(i in 1:nrow(dat_trans)){
  for(j in length(dat_trans)){
    if(dat_trans[i,j] == 1){
      dat_trans[i,j] <- 3
    } else if(dat_trans[i,j] == 2){
      dat_trans[i,j] <- 2
    } else{
      dat_trans[i,j] <- 1
    }
  }
}

所以我遍历第一列,获取每个数值并根据if / else的条件进行更改,我仍在学习R语言,如果您对我的代码有任何指针,请随意指出。

编辑:代码


1
如果该值已经是 2,那你为什么要将其替换为 2 呢? - Benjamin Ye
我想我可以只是利用跳过/使用那个else。 - Silver_Surfer9
4个回答

4

R是一种向量化语言,因此您实际上不需要内部循环。
此外,如果您发现4-“旧值”=“新值”,则可以消除if语句。

for(i in 1:ncol(dat_trans)){
        dat_trans[,i] <- 4-dat_trans[,i]
}

现在外循环仅迭代列的前10次,而不是所有行的1000次。这将极大地提高性能。


2
甚至更好的是将数据框转换为矩阵,并完全摆脱循环:dat_trans <- 4-as.matrix(dat_trans) - GordonShumway
@GordonShumway 你能详细说明为什么这种方法会更好吗? - Silver_Surfer9
你可以避免使用循环,而是在整个矩阵上执行一个大向量操作,从而更快地完成任务。但使用矩阵的缺点是所有列都将被转换为单一类型。例如,如果第一列是名称列表,而其余99列是数字,则在转换为矩阵时,它将成为所有字符对象的矩阵。 - Dave2e
我明白了。只有一个问题——你最初发布的那段代码确实按预期工作,但是我不明白那段代码如何知道将1重新编码为3,将3重新编码为1。我无法弄清楚4-所表示的操作。 - Silver_Surfer9
你说过你想将1重新编码为3(1+3=4),将2重新编码为2(2+2=4),将3重新编码为1(3+1=4)。在这种情况下,“旧值+新值”=4。因此,通过一些基本的数学计算,可以得出:4-“旧值”=“新值”。这是一个很方便的技巧。 - Dave2e

3

这种操作是一种交换操作。不使用for循环进行值的交换有很多方法。

设置一个简单的数据框:

df <- data.frame(
  col1 = c(1,2,3),
  col2 = c(2,3,1),
  col3 = c(3,1,2)
)

使用虚拟值:
df[df==1] <- 4
df[df==3] <- 1
df[df==4] <- 3

使用临时变量:

dftemp <- df
df[dftemp==1] <- 3
df[dftemp==3] <- 1

使用乘除和加减:

df <- 4 - df

使用布尔运算:

df <- (df==1) * 3 + (df==2) * 2 + (df==3) * 1

如果你真的需要速度,可以使用按位异或操作:

df[df!=2] <- sapply(df, function(x){bitwXor(2,x)})[df!=2]

如果需要嵌套 for 循环,使用 switch 函数是一个不错的选择。
for(i in seq(ncol(df))){
  for(j in seq(nrow(df))){
    df[j,i] <- switch(df[j,i],3,2,1)
  }
}

如果值的索引不像1、2和3一样好,可以使用文本。

for(i in seq(ncol(df))){
  for(j in seq(nrow(df))){
    df[j,i] <- switch(as.character(df[j,i]),
                      "1" = 3,
                      "2" = 2,
                      "3" = 1)
  }
}

谢谢!我很欣赏这种多样的方法。 - Silver_Surfer9

0
这听起来像是一个合并/连接操作。
set.seed(42)
dat_trans <- as.data.frame(
  setNames(lapply(1:3, function(ign) sample(1:3, size=10, replace=TRUE)),
           c("V1", "V2", "V3"))
)
dat_trans
#    V1 V2 V3
# 1   3  2  3
# 2   3  3  1
# 3   1  3  3
# 4   3  1  3
# 5   2  2  1
# 6   2  3  2
# 7   3  3  2
# 8   1  1  3
# 9   2  2  2
# 10  3  2  3

newvals <- data.frame(old = c(1, 3), new = c(3, 1))
newvals
#   old new
# 1   1   3
# 2   3   1

使用 dplyrtidyr

library(dplyr)
library(tidyr) # gather, spread
dat_trans %>%
  mutate(rn = row_number()) %>%
  gather(k, v, -rn) %>%
  left_join(newvals, by = c("v" = "old")) %>%
  mutate(v = if_else(is.na(new), v, new)) %>%
  select(-new) %>%
  spread(k, v) %>%
  select(-rn)
#    V1 V2 V3
# 1   1  2  1
# 2   1  1  3
# 3   3  1  1
# 4   1  3  1
# 5   2  2  3
# 6   2  1  2
# 7   1  1  2
# 8   3  3  1
# 9   2  2  2
# 10  1  2  1

需要使用rn可能是因为我使用的是较旧版本的tidyr:我使用的是0.8.2,尽管1.0.0最近已发布。该版本对spread/gather进行了大量增强/工作,并引入了pivot_*函数,这些函数在此方面可能更加流畅。如果您有更新的版本,请尝试不使用rn部分。


或者采用更直接的“重新编码”思维方式:

dat_trans[,c("V1", "V2", "V3")] <- lapply(dat_trans[,c("V1", "V2", "V3")], car::recode, "1=3; 3=1")
# or
dat_trans[,c("V1", "V2", "V3")] <- lapply(dat_trans[,c("V1", "V2", "V3")], dplyr::recode, '1' = 3L, '3' = 1L)

谢谢。我会尝试并研究这些函数。 - Silver_Surfer9

0
你可以使用一个赋值矩阵am。对于df1的每个属性值,使用match()am的第一列匹配,但选择第二列,然后将其赋值给df1。当然,在lapply()中实现。
df1
#   V1 V2 V3
# 1  1  2  1
# 2  1  2  1
# 3  1  1  2
# 4  1  3  2
# 5  2  3  2

am <- matrix(c(1, 2, 3, 3, 2, 1), 3)
am
#      [,1] [,2]
# [1,]    1    3
# [2,]    2    2
# [3,]    3    1

df1[] <- lapply(df1, function(x) am[match(x, am[,1]), 2])
df1
#   V1 V2 V3
# 1  3  2  3
# 2  3  2  3
# 3  3  3  2
# 4  3  1  2
# 5  2  1  2

数据

df1 <- structure(list(V1 = c(1L, 1L, 1L, 1L, 2L), V2 = c(2L, 2L, 1L, 
3L, 3L), V3 = c(1L, 1L, 2L, 2L, 2L)), class = "data.frame", row.names = c(NA, 
-5L))

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