根据条件更改变量值

时间:2019-08-28 19:53:30

标签: r dataframe vectorization data-cleaning

我有数据框:

a<-c(1,2,3,4)
b<-c(1988,1970,1999,2000)
years_practicing<-rep(NA,4)
df<-data.frame("ID"=a, "grad_year"=b, "years_practicing"=years_practicing)

如下所示:

ID   grad_year    years_practicing
1     1988           NA
2     1970           NA
3     1999           NA
4     2000           NA

现在我要执行此操作(它是伪代码!):

if (ID=1 || ID=2) 
{
   years_practicing[corresponding cell]<-2017-grad_year
}

if (ID=3 || ID=4) 
{
   years_practicing[corresponding cell]<-2018-grad_year
}

要实现这一目标:

ID   grad_year    years_practicing
1     1988           29
2     1970           47
3     1999           19
4     2000           18

我知道如何以过程方式(使用while循环和if语句)来执行此操作,但是我想以向量化方式来执行操作。

我尝试了这个(以及类似的变体):

year_2017_start<-c(1, 2)
year_2018_start<-c(3,4)
df$years_practicing[any(df$ID == year_2017_start)]<- 2017-df$grad_yr
df$years_practicing[any(df$ID == year_2018_start)]<- 2018-df$grad_yr

但收到错误:

Error in df$years_practicing[any(df$ID == year_2017_start)] <- 2017 -  : 
  replacement has length zero
> df$years_practicing[any(df$ID == year_2018_start)]<- 2018-df$grad_yr
Error in df$years_practicing[any(df$ID == year_2018_start)] <- 2018 -  : 
  replacement has length zero

问题:

  1. 如何改进代码以使其正常运行。 (需要回答)

  2. 是否有更快的方法来获得相似的结果? (可选)

4 个答案:

答案 0 :(得分:2)

您可以使用命名向量

v1 = c(`1` = 2017,
       `2` = 2017,
       `3` = 2018,
       `4` = 2018)

v1[df$ID] - df$grad_year
# 1  2  3  4 
#29 47 19 18 

答案 1 :(得分:2)

此单线仅使用基数R。如果ID为1或2,则显示的%in%表达式的值为TRUE,否则为FALSE。从2018年减去后,它们分别转换为1和0,然后从中减去grad_year

transform(df, years_practicing = 2018 - (ID %in% 1:2) - grad_year)

给予:

  ID grad_year years_practicing
1  1      1988               29
2  2      1970               47
3  3      1999               19
4  4      2000               18

答案 2 :(得分:1)

您可以使用dplyr

library(dplyr)
df %>% 
  mutate(years_practicing = ifelse(ID == 1 | ID == 2,
                                   2017-grad_year,
                                   2018-grad_year))

如果要测试的条件超过两个(例如,如果您有三年-2017年,2018年和2019年),则可以在以下情况下使用case_when:

df %>% 
  mutate(years_practicing = case_when(
    ID == 1 | ID == 2 ~ 2017-grad_year,
    ID == 3 ~ 2018-grad_year,
    TRUE ~ 2019-grad_year)
    )

编辑:比较给定答案的表现

我很好奇每个解决方案的速度。我比较了截至2019年8月29日建议的解决方案。@Chuan的答案胜出!很好玩...

library(microbenchmark)
library(dplyr)

a<-sample(c(1,2,3,4), 20000, replace = TRUE)
b<-sample(c(1988:2015), 20000, replace = TRUE)
years_practicing<-rep(NA, 20000)
df<-data.frame("ID"=a, "grad_year"=b, "years_practicing"=years_practicing)
year_2017_start<-c(1, 2)
year_2018_start<-c(3,4)
v1 = c(`1` = 2017,
       `2` = 2017,
       `3` = 2018,
       `4` = 2018)

mb <- microbenchmark(
  df$years_practicing[which(df$ID == year_2017_start)]<- 2017-df$grad_year[which(df$ID == year_2017_start)], 
  transform(df, years_practicing = 2018 - (ID %in% 1:2) - grad_year),
  df %>% 
    mutate(years_practicing = ifelse(ID == 1 | ID == 2,
                                     2017-grad_year,
                                     2018-grad_year)),
  v1[match(df$ID, names(v1))] - df$grad_year,
  times=500)

ggplot2::autoplot(mb) 

enter image description here

答案 3 :(得分:1)

不确定使用矢量化方法更新值的动机;但是某些矢量化函数(例如ifelse())在这里可能会更好。无论如何,下面是您想要的矢量化解决方案:

df$years_practicing[which(df$ID == year_2017_start)]<- 2017-df$grad_year[which(df$ID == year_2017_start)]