如何根据列组前缀替换列组的空白?
How to replace blanks for groups of columns based on column group prefix?
如何根据以相同前缀开头的其他列中是否存在某个值,将列中的 NA 替换为全 0?例如,对于 A1 列,我只想将 NA 替换为 0,其中 A2 或 A3 列为 NONBLANK。我的真实数据有数百组列。
我的数据:
ID<-c(1,2,3,4,5,6,7,8)
A1<-c(1,NA,1,NA,1,1,1,NA)
A2<-c(1,NA,NA,1,NA,1,NA,NA)
A3<-c(1,NA,NA,NA,1,NA,NA,NA)
B1<-c(1,1,1,1,1,1,NA,1)
B2<-c(1,1,1,1,NA,1,NA,NA)
B3<-c(1,1,NA,NA,1,NA,NA,NA)
mydata<-cbind.data.frame(ID,A1,A2,A3,B1,B2,B3)
有:
求购:
如果 A2 或 A3 列为 1,则 A 0 应替换 A1 列中的 NA。如果 A1 或 A3 列为 1,则 0 应替换 A2 列中的 NA,依此类推,如下所示:
基础 R
中的一个 non-refined 答案,但似乎有效:
for(i in unique(gsub("\d","",colnames(mydata)[-1]))){
mydata[apply(mydata[,grepl(i,colnames(mydata))],1,function(x) any(!is.na(x))),grepl(i,colnames(mydata))][is.na(mydata[apply(mydata[,grepl(i,colnames(mydata))],1,function(x) any(!is.na(x))),grepl(i,colnames(mydata))])]<-0
}
给出:
ID A1 A2 A3 B1 B2 B3
1 1 1 1 1 1 1 1
2 2 NA NA NA 1 1 1
3 3 1 0 0 1 1 0
4 4 0 1 0 1 1 0
5 5 1 0 1 1 0 1
6 6 1 1 0 1 1 0
7 7 1 0 0 NA NA NA
8 8 NA NA NA 1 0 0
编辑:
这个想法是从示例中的 colnames(mydata)
、A
和 B
中提取 unique
字母,方法是用空白 ""
.
然后它遍历这些字母到 select 以它开头的列。这就是 grepl(i,colnames(mydata))
所做的。
apply
用于获取至少有 (any()
) 个 non-NA 值 (!is.na()
) 的行的向量:apply(mydata[,grepl(i,colnames(mydata))],1,function(x) any(!is.na(x)))
.
然后将所有内容组合成本质上 df[is.na(df))]<-0
但 df
对应于用给定字母表示的列,以及应该替换 NA
的行。
df
将是:mydata[apply(mydata[,grepl(i,colnames(mydata))],1,function(x) any(!is.na(x))),grepl(i,colnames(mydata))]
另一种方法是
mydata[, 2:4][is.na(mydata[, 2:4])] <- rep(NA^(rowSums(is.na(mydata[2:4])) == 3) - 1,
length(2:4))[is.na(mydata[, 2:4])]
mydata[, 5:7][is.na(mydata[, 5:7])] <- rep(NA^(rowSums(is.na(mydata[5:7])) == 3) - 1,
length(5:7))[is.na(mydata[, 5:7])]
mydata
ID A1 A2 A3 B1 B2 B3
1 1 1 1 1 1 1 1
2 2 NA NA NA 1 1 1
3 3 1 0 0 1 1 0
4 4 0 1 0 1 1 0
5 5 1 0 1 1 0 1
6 6 1 1 0 1 1 0
7 7 1 0 0 NA NA NA
8 8 NA NA NA 1 0 0
列值为 hard-coded,这对许多组没有帮助,因此按照@haboryme 的技巧,您可以
# group columns into list elements with lapply and grep
myCols <- lapply(c("A", "B"), function(i) grep(i, colnames(mydata)))
# loop through and make changes
for(i in myCols) {
mydata[, i][is.na(mydata[, i])] <- rep(NA^(rowSums(is.na(mydata[i])) == 3) - 1,
length(i))[is.na(mydata[, i])]
}
使用 lapply()
的自定义函数:一般化为具有任意数量的列,前提是它们遵循这种具有单个字母表的模式
func <- function(x){
df <- mydata[grepl(x, colnames(mydata))] # extract only the same letter columns
m <- !is.na(df) # create a logical matrix to know which all are NA's
i = which(rowSums(m)!=0) # if all had NA's then summ will be 0. so avoid that
df[i,][is.na(df[i,])] <- 0 # insert wherever NA's to be 0( but only in those rows decided above)
return(df)
}
data.frame(ID = mydata$ID,lapply(LETTERS[1:2], func))
# ID A1 A2 A3 B1 B2 B3
#1 1 1 1 1 1 1 1
#2 2 NA NA NA 1 1 1
#3 3 1 0 0 1 1 0
#4 4 0 1 0 1 1 0
#5 5 1 0 1 1 0 1
#6 6 1 1 0 1 1 0
#7 7 1 0 0 NA NA NA
#8 8 NA NA NA 1 0 0
两个 tidyverse 选项;哪个更实用取决于实际数据的维度。两者都有条件地利用 coalesce
。
手动:
library(tidyverse)
mydata %>% rowwise() %>% # group by row
mutate_at(vars(starts_with('A')), # for A prefixes, coalesce if not all NA
funs(ifelse(all(is.na(c(A1, A2, A3))), ., coalesce(., 0)))) %>%
mutate_at(vars(starts_with('B')), # likewise for B
funs(ifelse(all(is.na(c(B1, B2, B3))), ., coalesce(., 0))))
## Source: local data frame [8 x 7]
## Groups: <by row>
##
## # A tibble: 8 × 7
## ID A1 A2 A3 B1 B2 B3
## <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 1 1 1 1 1 1 1
## 2 2 NA NA NA 1 1 1
## 3 3 1 0 0 1 1 0
## 4 4 0 1 0 1 1 0
## 5 5 1 0 1 1 0 1
## 6 6 1 1 0 1 1 0
## 7 7 1 0 0 NA NA NA
## 8 8 NA NA NA 1 0 0
或以编程方式进行整形:
mydata %>% gather(var, val, -ID) %>% # reshape to long
group_by(ID, letter = substr(var, 1, 1)) %>% # group by ID and prefix
mutate(val = if(all(is.na(val))) val else coalesce(val, 0)) %>%
ungroup() %>% select(-letter) %>% spread(var, val) # clean up
## # A tibble: 8 × 7
## ID A1 A2 A3 B1 B2 B3
## * <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 1 1 1 1 1 1 1
## 2 2 NA NA NA 1 1 1
## 3 3 1 0 0 1 1 0
## 4 4 0 1 0 1 1 0
## 5 5 1 0 1 1 0 1
## 6 6 1 1 0 1 1 0
## 7 7 1 0 0 NA NA NA
## 8 8 NA NA NA 1 0 0
如果前缀可以超出单个字母,请将 substr
替换为合适的正则表达式,例如sub('\d+$', '', var)
.
如何根据以相同前缀开头的其他列中是否存在某个值,将列中的 NA 替换为全 0?例如,对于 A1 列,我只想将 NA 替换为 0,其中 A2 或 A3 列为 NONBLANK。我的真实数据有数百组列。
我的数据:
ID<-c(1,2,3,4,5,6,7,8)
A1<-c(1,NA,1,NA,1,1,1,NA)
A2<-c(1,NA,NA,1,NA,1,NA,NA)
A3<-c(1,NA,NA,NA,1,NA,NA,NA)
B1<-c(1,1,1,1,1,1,NA,1)
B2<-c(1,1,1,1,NA,1,NA,NA)
B3<-c(1,1,NA,NA,1,NA,NA,NA)
mydata<-cbind.data.frame(ID,A1,A2,A3,B1,B2,B3)
有:
求购:
如果 A2 或 A3 列为 1,则 A 0 应替换 A1 列中的 NA。如果 A1 或 A3 列为 1,则 0 应替换 A2 列中的 NA,依此类推,如下所示:
基础 R
中的一个 non-refined 答案,但似乎有效:
for(i in unique(gsub("\d","",colnames(mydata)[-1]))){
mydata[apply(mydata[,grepl(i,colnames(mydata))],1,function(x) any(!is.na(x))),grepl(i,colnames(mydata))][is.na(mydata[apply(mydata[,grepl(i,colnames(mydata))],1,function(x) any(!is.na(x))),grepl(i,colnames(mydata))])]<-0
}
给出:
ID A1 A2 A3 B1 B2 B3
1 1 1 1 1 1 1 1
2 2 NA NA NA 1 1 1
3 3 1 0 0 1 1 0
4 4 0 1 0 1 1 0
5 5 1 0 1 1 0 1
6 6 1 1 0 1 1 0
7 7 1 0 0 NA NA NA
8 8 NA NA NA 1 0 0
编辑:
这个想法是从示例中的 colnames(mydata)
、A
和 B
中提取 unique
字母,方法是用空白 ""
.
然后它遍历这些字母到 select 以它开头的列。这就是 grepl(i,colnames(mydata))
所做的。
apply
用于获取至少有 (any()
) 个 non-NA 值 (!is.na()
) 的行的向量:apply(mydata[,grepl(i,colnames(mydata))],1,function(x) any(!is.na(x)))
.
然后将所有内容组合成本质上 df[is.na(df))]<-0
但 df
对应于用给定字母表示的列,以及应该替换 NA
的行。
df
将是:mydata[apply(mydata[,grepl(i,colnames(mydata))],1,function(x) any(!is.na(x))),grepl(i,colnames(mydata))]
另一种方法是
mydata[, 2:4][is.na(mydata[, 2:4])] <- rep(NA^(rowSums(is.na(mydata[2:4])) == 3) - 1,
length(2:4))[is.na(mydata[, 2:4])]
mydata[, 5:7][is.na(mydata[, 5:7])] <- rep(NA^(rowSums(is.na(mydata[5:7])) == 3) - 1,
length(5:7))[is.na(mydata[, 5:7])]
mydata
ID A1 A2 A3 B1 B2 B3
1 1 1 1 1 1 1 1
2 2 NA NA NA 1 1 1
3 3 1 0 0 1 1 0
4 4 0 1 0 1 1 0
5 5 1 0 1 1 0 1
6 6 1 1 0 1 1 0
7 7 1 0 0 NA NA NA
8 8 NA NA NA 1 0 0
列值为 hard-coded,这对许多组没有帮助,因此按照@haboryme 的技巧,您可以
# group columns into list elements with lapply and grep
myCols <- lapply(c("A", "B"), function(i) grep(i, colnames(mydata)))
# loop through and make changes
for(i in myCols) {
mydata[, i][is.na(mydata[, i])] <- rep(NA^(rowSums(is.na(mydata[i])) == 3) - 1,
length(i))[is.na(mydata[, i])]
}
使用 lapply()
的自定义函数:一般化为具有任意数量的列,前提是它们遵循这种具有单个字母表的模式
func <- function(x){
df <- mydata[grepl(x, colnames(mydata))] # extract only the same letter columns
m <- !is.na(df) # create a logical matrix to know which all are NA's
i = which(rowSums(m)!=0) # if all had NA's then summ will be 0. so avoid that
df[i,][is.na(df[i,])] <- 0 # insert wherever NA's to be 0( but only in those rows decided above)
return(df)
}
data.frame(ID = mydata$ID,lapply(LETTERS[1:2], func))
# ID A1 A2 A3 B1 B2 B3
#1 1 1 1 1 1 1 1
#2 2 NA NA NA 1 1 1
#3 3 1 0 0 1 1 0
#4 4 0 1 0 1 1 0
#5 5 1 0 1 1 0 1
#6 6 1 1 0 1 1 0
#7 7 1 0 0 NA NA NA
#8 8 NA NA NA 1 0 0
两个 tidyverse 选项;哪个更实用取决于实际数据的维度。两者都有条件地利用 coalesce
。
手动:
library(tidyverse)
mydata %>% rowwise() %>% # group by row
mutate_at(vars(starts_with('A')), # for A prefixes, coalesce if not all NA
funs(ifelse(all(is.na(c(A1, A2, A3))), ., coalesce(., 0)))) %>%
mutate_at(vars(starts_with('B')), # likewise for B
funs(ifelse(all(is.na(c(B1, B2, B3))), ., coalesce(., 0))))
## Source: local data frame [8 x 7]
## Groups: <by row>
##
## # A tibble: 8 × 7
## ID A1 A2 A3 B1 B2 B3
## <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 1 1 1 1 1 1 1
## 2 2 NA NA NA 1 1 1
## 3 3 1 0 0 1 1 0
## 4 4 0 1 0 1 1 0
## 5 5 1 0 1 1 0 1
## 6 6 1 1 0 1 1 0
## 7 7 1 0 0 NA NA NA
## 8 8 NA NA NA 1 0 0
或以编程方式进行整形:
mydata %>% gather(var, val, -ID) %>% # reshape to long
group_by(ID, letter = substr(var, 1, 1)) %>% # group by ID and prefix
mutate(val = if(all(is.na(val))) val else coalesce(val, 0)) %>%
ungroup() %>% select(-letter) %>% spread(var, val) # clean up
## # A tibble: 8 × 7
## ID A1 A2 A3 B1 B2 B3
## * <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 1 1 1 1 1 1 1
## 2 2 NA NA NA 1 1 1
## 3 3 1 0 0 1 1 0
## 4 4 0 1 0 1 1 0
## 5 5 1 0 1 1 0 1
## 6 6 1 1 0 1 1 0
## 7 7 1 0 0 NA NA NA
## 8 8 NA NA NA 1 0 0
如果前缀可以超出单个字母,请将 substr
替换为合适的正则表达式,例如sub('\d+$', '', var)
.