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

Posted

技术标签:

【中文标题】仅当列存在时才适用于数据框列表【英文标题】:Lapply to a list of dataframes only if column exists 【发布时间】:2019-09-16 18:48:36 【问题描述】:

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

假设以下数据框示例列表:

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) 由元素 df1df2 组成时,以下代码可以完美运行:

Mean_C <- rowMeans(do.call(cbind, lapply(mylist_old, "[", "C")))
Mean_C <- as.data.frame(Mean_C)

当列表由至少一个数据框组成时,问题就来了

Mean_C <- rowMeans(do.call(cbind, lapply(mylist_new, "[", "C")))

导致:“[.data.frame(X[[i]], ...) 中的错误:选择了未定义的列

规避此问题的一种方法是将df3mylist_new 中排除。但是,我的真实程序有一个包含 64 个数据框的列表,我不知道列 C 是否存在。仅当检测到列C 存在时,我才想lapply 我的代码段,即将该命令应用于数据帧列表,但仅适用于存在C 列的数据帧为真。

我试过了

if("C" %in% colnames(mylist_new))
 
     Mean_C <- rowMeans(do.call(cbind, lapply(mylist_new, "[", "C")))
     Mean_C <- as.data.frame(Mean_C)    
 

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

【问题讨论】:

【参考方案1】:

这是Filter list 元素的一种选择,然后将lapply 应用于过滤后的list

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

或使用tidyverse 而不使用Filtering,但使用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

【讨论】:

【参考方案2】:

我们可以在做子集之前使用 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

【讨论】:

【参考方案3】:

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

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

我们还可以使用otherwise 参数来获得NA 结果而不是NULL,我们可以在rowMeans 中将na.rm 设置为TRUE

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

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

wide <- do.call(cbind, mylist_new)
rowMeans(wide[names(wide) == "C"])
# [1] 2.0 6.5

【讨论】:

以上是关于仅当列存在时才适用于数据框列表的主要内容,如果未能解决你的问题,请参考以下文章

当列数事先未知时如何访问 Pandas 数据框列

将字典映射到数据框列中的列表

将一些函数应用于列表中的数据框列

仅在列存在时才提供数据框列表

仅当数组列表尚不存在时才将其添加到数组列表中

python:从熊猫中的数据框生成的列表比数据框列长得多