【问题标题】:Subsetting Data Frame Based on Contents of a "Column" List根据“列”列表的内容对数据框进行子集化
【发布时间】:2013-04-22 11:25:33
【问题描述】:

设置

我有一个列表矩阵,其中一个“列”是一个列表(我意识到这是一个奇怪的数据集,但我发现它对其他操作很有用)。列表的每个条目都是; (1) 空 (integer(0)),(2) 整数,或 (3) 整数向量。

例如R 对象“d.f”,d.f$ID 是索引向量,d.f$Basket_List 是列表。

ID <- c(1,2,3,4,5,6,7,8,9)
Basket_List <- list(integer(0),c(123,987),c(123,123),456,
                    c(456,123),456,c(123,987),c(987,123),987)
d.f <- data.frame(ID)
d.f$Basket_List <- Basket_List

我的问题

问题 1

我想创建一个新数据集,它是初始数据集的子集,基于“Basket_List”是否包含某些值。例如。 d.f 中所有行的子集,使得 Bask_list 具有“123”或“123”和“987”——或其他更复杂的条件。

我已经尝试了以下所有变体,但无济于事。

d.f2 <- subset(d.f, 123 %in% Basket_List)
d.f2 <- subset(d.f, 123 == any(Basket_List))
d.f2 <- d.f[which(123 %in% d.f$Basket_List,]
# should return the subset, with rows 2,3,5,7 & 8

问题 2

我的另一个问题是我将在数百万行(它是事务数据)上运行此操作,所以我想尽可能地优化它以提高速度(我现在有一个复杂的 for 循环,但它需要太多时间)。


数据的替代设置

如果您认为它可能有用,数据也可以设置如下:

ID <- c(1,2,2,3,3,4,5,5,6,7,7,8,8,9)
Basket <- c(NA,123,987,123,123,456,456,123,456,123,987,987,123,987)
alt.d.f <- data.frame(ID,Basket)

【问题讨论】:

    标签: r list object dataframe subset


    【解决方案1】:

    您可以为此使用sapply:

    ID <- c(1,2,3,4,5,6,7,8,9)
    Basket_List <- list(integer(0),c(123,987),c(123,123),456,
                        c(456,123),456,c(123,987),c(987,123),987)
    d.f <- data.frame(ID)
    
    sel <- sapply( Basket_List, function(bl,searchItem) {
      any(searchItem %in% bl)
    }, searchItem=c(123) )
    
    > sel
    [1] FALSE  TRUE  TRUE FALSE  TRUE FALSE  TRUE  TRUE FALSE
    
    > d.f[sel,,drop=FALSE]
      ID
    2  2
    3  3
    5  5
    7  7
    8  8
    

    请注意您的术语。 data.frame 不是矩阵。这是一种列表。

    在速度方面,sapply 不是最快的,但是选择会非常快,因为它是矢量化的。如果你需要更快的速度,data.tabletime。

    【讨论】:

    • +1 不错的解决方案。我最初的想法几乎完全相同。我认为 OP 的第二次数据设置会比第一次更快?
    • @SimonO101 我同意第二次数据设置可能会比第一次快得多。它也更适合使用data.table,这将大大提高大型数据集的速度。
    【解决方案2】:

    与@AriB 类似的方法是使用any 运算符,apply 跨行,如下所示:

    d.f[ apply( d.f , 1 , function(x) any(unlist(x) %in% 123) ) , ]
    #  ID Basket_List
    #2  2    123, 987
    #3  3    123, 123
    #5  5    456, 123
    #7  7    123, 987
    #8  8    987, 123
    

    我想第二次设置你的数据会非常快,因为你可以像这样简单地设置子集:

    df[ df$Basket %in% 123 , ]
    #   ID Basket
    #NA NA     NA
    #2   2    123
    #4   3    123
    #5   3    123
    #8   5    123
    #10  7    123
    #13  8    123
    

    如果您只想要包含 Basket 值的行的第一个实例,您可以随后将 match 与唯一 ID 一起使用,因为 match 在第二个参数中返回其第一个参数的第一个匹配项:

    df2 <- df[ df$Basket %in% 123 , ]
    df2[ match( unique(df2$ID) , df2$ID),]
    #   ID Basket
    #NA NA     NA
    #2   2    123
    #4   3    123
    #8   5    123
    #10  7    123
    #13  8    123
    

    您的数据的第二次设置将比我认为的第一次快得多。事实上,让我们在一个 100 万行的表上做一个粗略的基准测试:

    DF <- data.frame( ID = sample(ID , 1e6 , repl=TRUE) , Basket = sample(Basket , 1e6 , repl = TRUE) )
    df<-DF
    
    system.time({
      df2 <- df[ df$Basket %in% 123 , ]
      df2[ match( unique(df2$ID) , df2$ID),]
    })
    #   user  system elapsed 
    #   0.16    0.00    0.16 
    
    nrow(df)
    #[1] 1000000
    nrow(df2)
    #[1] 428187
    

    【讨论】:

    • 不错。不确定当您在列表行中应用时会发生什么,并且懒得尝试。现在我知道了:-)
    【解决方案3】:

    使用 purrr 和 dplyr 库(以及 magrittr 管道运算符)的更易读的解决方案是:

    library(dplyr)
    library(purrr)    
    
    d.f %>% filter(map_lgl(Basket_List,contains,as.integer(123)))
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-08-15
      • 2012-10-27
      • 2016-12-15
      • 1970-01-01
      • 2020-08-19
      • 1970-01-01
      • 2022-11-18
      • 2020-11-05
      相关资源
      最近更新 更多