仅当列存在时才应用于数据框列表

时间:2019-04-27 14:14:12

标签: r if-statement conditional-statements lapply

我有一个数据框列表,我想为其获取(在一个单独的数据框中)指定列的行均值,该列可能不存在于列表的所有数据框中。当指定的列在列表的至少一个数据框中不存在时,就会出现我的问题。

假定以下示例数据帧列表:

df1 <- read.table(text = 'X   A   B   C
                       name1  1   2   3
                       name2  5  10   4',
                 header = TRUE)  

df2 <- read.table(text = 'X   B   C   A
                       name1  8   1  31
                       name2  9   9   8', 
                 header = TRUE)

df3 <- read.table(text = 'X   B   A   E
                       name1  9   9  29
                       name2  5  15  55', 
                 header = TRUE)

mylist_old <-list(df1, df2)
mylist_new <-list(df1, df2, df3)

假设我要在rowMeansC中,当数据帧列表(mylist_old)由元素df1和{{1组成时, }},:

df2

当列表由至少一个不存在列Mean_C <- rowMeans(do.call(cbind, lapply(mylist_old, "[", "C"))) Mean_C <- as.data.frame(Mean_C) 的数据帧组成时,麻烦就来了,在我的示例中,C就是列表{{1} }:

df3

导致:“ mylist_new(X [[i]],...)出错:选择了未定义的列

规避此问题的一种方法是将Mean_C <- rowMeans(do.call(cbind, lapply(mylist_new, "[", "C"))) [.data.frame中排除。但是,我的真实程序有一个64个数据帧的列表,我不知道它们是否存在列df3。仅当检测到列mylist_new存在时,我才想C我的代码,即将命令应用于数据框列表,但仅适用于存在列{{1}的数据框}是真的。

我尝试过

lapply

但是什么也没有发生,可能是因为C是指列表而不是列表的每个数据帧。对于64个数据框,我无法“手动”引用每个数据框,因此需要一个自动化的过程。

3 个答案:

答案 0 :(得分:6)

这里是Filterlist元素的一个选项,然后将lapply应用于过滤后的list

rowMeans(do.call(cbind, lapply(Filter(function(x) "C" %in% names(x), 
               mylist_new), `[[`, "C")))
#[1] 2.0 6.5

或使用tidyverse而不使用Filter,但使用select忽略不存在该列的情况

library(tidyverse)
map(mylist_new, ~ .x %>% 
                   select(one_of("C"))) %>% # gives a warning
                   bind_cols  %>%
                   rowMeans
#[1] 2.0 6.5

最好警告一下该列不存在


或者没有警告

map(mylist_new, ~ .x %>% 
                 select(matches("^C$"))) %>%
                 bind_cols  %>%
                 rowMeans
#[1] 2.0 6.5

答案 1 :(得分:3)

我们可以在使用子集之前使用 if 检查名称

rowMeans(do.call(cbind,
         lapply(mylist_new, function(x) if('C' %in% names(x)) x['C'] else NA)),na.rm = TRUE)

或在 purrr 0.3.2

中使用map_if
library(purrr)
rowMeans(do.call(cbind,map_if(mylist_new, 
                              function(x) 'C' %in% names(x), 
                              'C', .else=~return(NA))),na.rm = TRUE)
[1] 2.0 6.5

答案 2 :(得分:0)

一种方法是使用purrr::safely,它将为每次迭代返回一个包含resulterror元素的列表,然后我们可以转置,提取result并删除NULLcompact的结果:

library(tidyverse)
rowMeans(do.call(cbind, transpose(
  lapply(mylist_new, safely(`[`), "C"))$result %>% compact()))
# [1] 2.0 6.5

我们还可以使用otherwise参数获得NA而不是NULL的结果,并且可以在{{1中将na.rm设置为TRUE }}。

rowMeans

这是为了以最小的修改解决您的问题。如果我必须解决这个确切的问题,我可以通过以下方式做到:

rowMeans(na.rm = TRUE, do.call(cbind, transpose(
  lapply(mylist_new, safely(`[`, otherwise= NA), "C"))$result))
# [1] 2.0 6.5

我们提取map(mylist_new, "C") %>% compact() %>% pmap_dbl(~mean(c(...))) # [1] 2.0 6.5 元素,将其C删除,然后按元素计算均值。

这可能更有效(不确定):

NULL

再一次,这次使用重塑:

map(set_names(mylist_new),  "C") %>% compact() %>% as_tibble() %>% rowMeans()
# [1] 2.0 6.5

还有一个基本版本,可读性强,步骤有些笨拙,其中几列具有相同的名称,但是它位于临时对象上,所以还不错:

map_dfr(mylist_new, ~gather(.,,,-1)) %>% 
  group_by(X) %>%
  filter(key == "C") %>%
  summarize_at("value", mean)

# # A tibble: 2 x 2
# X     value
# <fct> <dbl>
# 1 name1   2  
# 2 name2   6.5