如何向数据框添加新的计算变量

5

我想创建一个循环,将新变量添加到数据框中。这些变量应该是现有变量的简单二次形式。在下面的示例中,我想要三个新变量:dat$birds_2 <- dat$birds^2dat$wolfs_2 <- dat$wolfs^2dat$snakes_2 <- dat$snakes^2。我希望一次性对多个变量执行此操作。

dat <- read.table(text = " birds    wolfs     snakes
                    3        9         7
                    3        8         4
                    1        2         8
                    1        2         3
                    1        8         3
                    6        1         2
                    6        7         1
                    6        1         5
                    5        9         7
                    3        8         7
                    4        2         7
                    1        2         3
                    7        6         3
                    6        1         1
                    6        3         9
                    6        1         1   ",header = TRUE)

所需输出(dat_new)如下(仅显示前两行):
 dat_new                      birds    wolfs     snakes birds_2    wolfs_2     snakes_2
                                3        9         7    9        81         49  
                                3        8         4    9        64         16

我尝试了apply函数,但只得到了当前变量的值。我对循环不熟悉,不知道该如何开始。 - mql4beginner
只需传递以下代码:dat$birds_2<-dat$birds^2; dat$wolfs_2<-dat$wolfs^2 ; dat$snakes_2<-dat$snakes^2,您将在dat中看到3个新变量,它们是相应变量的平方值。这里不需要使用loopapply函数,^已向量化。 - Cath
1
没有经验的程序员不会使用循环。这样做更慢,需要更多的代码行。尝试寻找内置的 R 函数来避免使用循环。 - maRtin
1
如果你想要的不是 dat$birds_2[1]==dat$birds[1]^2 等等,你应该在问题中添加所需的输出。 - Cath
我通过添加输出示例来编辑了这个问题。谢谢。 - mql4beginner
显示剩余3条评论
5个回答

10

使用setNames的一行代码:

setNames(as.data.frame(cbind(dat, dat^2)), c(names(dat), paste0(names(dat),'_2')))

#   birds wolfs snakes birds_2 wolfs_2 snakes_2
#1      3     9      7       9      81       49
#2      3     8      4       9      64       16

9
使用 data.table 选项
library(data.table)
setDT(dat)[, paste0(names(dat),"_2") := lapply(.SD, '^', 2)]
head(dat,2)
#   birds wolfs snakes birds_2 wolfs_2 snakes_2
#1:     3     9      7       9      81       49
#2:     3     8      4       9      64       16

或者你可以使用set(更有效)因为有多列

setDT(dat)
dat_new <- copy(dat)
for(j in 1:ncol(dat_new)){
   set(dat_new, i=NULL, j=j, value=dat_new[[j]]^2)
 }
 cbind(dat, dat_new)

基准测试

set.seed(24)
dat <- as.data.frame(matrix(sample(0:20, 1e6*200, replace=TRUE), 
       ncol=200))

dat1 <- copy(dat)
dat2 <- copy(dat)


Colonel <- function() { setNames(as.data.frame(cbind(dat, dat^2)),
    c(names(dat), paste0(names(dat),'_2')))}
akrun1 <- function() {setDT(dat1)[, paste0(names(dat1),"_2") := 
            lapply(.SD, '^', 2)]}
akrun2 <- function() {setDT(dat2)
                  dat_new <- copy(dat2)
                  for(j in 1:ncol(dat_new)){
                      set(dat_new, i=NULL, j=j, value=dat_new[[j]]^2)
                   }
                  cbind(dat2, dat_new)}

jaap <- function() {dat_new <- dat %>% 
                     mutate_each(funs(.^2))
                names(dat_new) <- paste0(names(dat_new),"_2")
                dat_new <- cbind(dat,dat_new)}



 cathG <- function() {ncol_ori <- ncol(dat)
                datN <- cbind(dat, apply(dat, 2, "^", 2))
                colnames(datN)[(ncol_ori+1):ncol(datN)] <- 
            paste(colnames(datN)[1:ncol_ori], 2, sep="_")

     }

system.time(Colonel())
#   user  system elapsed 
#  5.589   1.472  46.843 

 system.time(akrun1())
 #   user  system elapsed 
 #  2.125   1.238  10.065 

system.time(akrun2())
#   user  system elapsed 
#  1.522   0.744   3.922 

system.time(jaap())
#   user  system elapsed 
#  1.597   0.926  11.153 

system.time(cathG())
#   user  system elapsed 
#  9.386   3.536  94.360 

有趣,你真的喜欢基准测试!当然,“as.data.frame”转换需要时间。我把它改成了“data.frame”。 - Colonel Beauvel
1
@ColonelBeauvel 我认为 as.data.frame 更快。 - akrun

7
您可以不使用循环来执行以下操作:
dat_2 <- dat^2
colnames(dat_2) <- paste0(colnames(dat),"_2")
dat_tot <- cbind(dat, dat_2)

对于误解我感到抱歉。我的意思是,我想要每一行都有一个二次值的变量,而不是每一列。 - mql4beginner
尝试使用rbind()而不是cbind()。但我不确定我是否还理解编辑后的问题。 - maRtin
以上的代码与您的输出示例完全相同。 - maRtin
你说得对,但是如何更改新变量的名称? - mql4beginner

6

作为另一种选择,您也可以使用dplyr包轻松完成此操作:

library(dplyr)

dat_new <- dat %>% mutate_all(funs(.^2))
names(dat_new) <- paste0(names(dat_new),"_2")
dat_new <- cbind(dat,dat_new)

这将会得到:
> head(dat_new)
  birds wolfs snakes birds_2 wolfs_2 snakes_2
1     3     9      7       9      81       49
2     3     8      4       9      64       16
3     1     2      8       1       4       64
4     1     2      3       1       4        9
5     1     8      3       1      64        9
6     6     1      2      36       1        4

4

如果您想获取某些变量的二次值(例如dat中的1到10个变量),可以执行以下操作:

ncol_ori <- ncol(dat)
dat <- cbind(dat, apply(dat[, 1:10], 2, "^", 2)) # or actually just cbind(dat, dat[, 1:10]^2)
colnames(dat)[(ncol_ori+1):ncol(dat)] <- paste(colnames(dat)[1:10], 2, sep="_")

如果您想针对原始数据框中的所有变量执行此操作,可以执行以下操作:

ncol_ori <- ncol(dat)
dat <- cbind(dat, apply(dat, 2, "^", 2)) # or just cbind(dat, dat^2)
colnames(dat)[(ncol_ori+1):ncol(dat)] <- paste(colnames(dat)[1:ncol_ori], 2, sep="_")

使用您的dat,您将获得以下内容:

head(dat)
#   birds wolfs snakes birds_2 wolfs_2 snakes_2
#1      3     9      7       9      81       49
#2      3     8      4       9      64       16
#3      1     2      8       1       4       64
#4      1     2      3       1       4        9
#5      1     8      3       1      64        9
#6      6     1      2      36       1        4

这很好,但是我该如何更改新变量的名称,例如在名称末尾添加“_2”? - mql4beginner
1
@mql4初学者,我添加了2行代码,这样你就可以用新变量并在结尾加上“_2”来命名你的列。 - Cath
你好 @CathG,我在 dat <- cbind(dat, apply(dat[, 1:10], 2, "^", 2)) 中遇到了一个错误。错误信息为:"Error in [.data.frame(dat, , 1:10) : undefined columns selected"。 - mql4beginner
@mql4beginner,可能是因为您的dat中少于10列?如果是这样,请用要求二次值的列的索引替换1:10。如果您想要所有列的二次值,则可以使用我回答中的第二段代码。 - Cath
是的,我更改了正确的列数,现在它可以正常工作了。谢谢。 - mql4beginner

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