【问题标题】:Using 'fastmatch' package in R在 R 中使用“fastmatch”包
【发布时间】:2014-07-03 07:57:12
【问题描述】:

我必须在大约 10MM 值的向量中找到 1MM 数值的索引。我找到了包fastmatch,但是当我使用函数fmatch() 时,我只返回了第一个匹配项的索引。

有人可以帮助我使用此功能查找所有值,而不仅仅是第一个吗?我意识到这是一个基本问题,但在线文档非常稀少,fmatch 大大减少了计算时间。

非常感谢!


这是一些示例数据 - 出于本练习的目的,我们将此数据框称为 A:

              DateTime     Address       Type     ID
1  2014-03-04 20:21:03   982076970          1  2752394
2  2014-03-04 20:21:07 98174238211          1  2752394
3  2014-03-04 20:21:08 76126162197          1  2752394
4  2014-03-04 20:21:16  6718053253          1  2752394
5  2014-03-04 20:21:17 98210219176          1  2752510
6  2014-03-04 20:21:20  7622877100          1  2752510
7  2014-03-04 20:21:23  2425126157          1  2752510
8  2014-03-04 20:21:23  2425126157          1  2752510
9  2014-03-04 20:21:25   701838650          1  2752394
10 2014-03-04 20:21:27 98210219176          1  2752394

我想做的是找到每个Address 的唯一Type 值的数量。有几百万行数据具有大约 1MM 的唯一地址值……平均而言,每个地址在数据集中出现大约 6 次。而且,尽管上面列出的 Type 值都是 1,但它们可以取 0:5 之间的任何值。我还意识到Address 的值很长,这增加了匹配所需的时间。

我尝试了以下方法:

uvals <- unique(A$Address)
utypes <- matrix(0,length(uvals),2)
utypes[,1] <- uvals

for (i in 1:length(unique(Address))) {
    b <- which(uvals[i] %in% A$Address)
    c <- length(unique(A$Type[b]))
    utypes[i,2] <- c
}

但是,上面的代码效率不高 - 如果我循环超过 1MM 的值,我估计这需要 10-15 小时。

我也在循环中尝试过这个......但速度并不快。

b <- which(A$Address == uvals[i])  

我知道有一种更优雅/更快的方法,我对 R 相当陌生,希望能提供任何帮助。

【问题讨论】:

    标签: r matching


    【解决方案1】:

    这可以使用data.table 中的unique 函数来完成,然后是聚合。我将或多或少地使用@Chinmay 生成的样本数据来说明它:

    创建示例数据:

    set.seed(100L)
    dat = data.frame(
             address = sample(1e6L, 1e7L, TRUE), 
               value = sample(1:5, 1e7L, TRUE, prob=c(0.5, 0.3, 0.1, 0.07, 0.03))
          )
    

    data.table 解决方案:

    require(data.table) ## >= 1.9.2
    dat.u = unique(setDT(dat), by=c("address", "value"))
    ans   = dat.u[, .N, by=address]
    

    说明:

    • setDT 函数将data.frame 转换为data.table 通过引用(非常快)。
    • 对 data.table 操作的 unique 函数调用 unique.data.table 方法,与 base:::unique 相比,这是 incredibly fast。现在,对于每个 address,我们只有 type 的唯一值。
    • 剩下要做的就是聚合分组 address 并获取每个组中的观察数。由address.N 组成的by=address 部分是一个内置的data.table 变量,用于提供该组的观察次数。

    基准测试:

    我将创建函数来生成data.tabledata.frame 的数据,以基准data.tabledplyr 的答案@beginneR 提出的解决方案(a),尽管我不认为需要arrange(.)那里,因此将跳过该部分。

    ## function to create data
    foo <- function(type = "df") {
        set.seed(100L)
        dat = data.frame(
                 address = sample(1e6L, 1e7L, TRUE), 
                   value = sample(1:5, 1e7L, TRUE, prob=c(0.5, 0.3, 0.1, 0.07, 0.03))
              )
        if (type == "dt") setDT(dat)
        dat
    } 
    
    ## DT function
    dt_sol <- function(x) {
        unique(x, by=c("address", "value"))[, .N, by=address]
    }
    
    ## dplyr function
    dplyr_sol <- function(x) {
        distinct(x) %>% group_by(address) %>% summarise(N = n_distinct(value))
    }
    

    这里报告的时间是在每个函数上连续运行三个system.time(.)

    ## benchmark timings in seconds
    ##        pkg   run-01   run-02   run-03                                 command
    ## data.table     2.4       2.3      2.4  system.time(ans1 <- dt_sol(foo("dt")))
    ##      dplyr    15.3      16.3     15.7   system.time(ans2 <- dplyr_sol(foo()))
    

    出于某种原因,dplyr 自动按分组变量对结果进行排序。所以为了比较结果,我也会在data.table的结果中排序:

    system.time(setkey(ans1, address)) ## 0.102 seconds
    identical(as.data.frame(ans1), as.data.frame(ans2)) ## TRUE
    

    所以,data.table 在这里要快约 6 倍。

    请注意,data.table 也支持bit64:::integer64 - 由于您提到地址值太长,您也可以将它们存储为integer64

    【讨论】:

    • @BrodieG,这是来自magrittr的新运营商:github.com/hadley/dplyr/blob/master/NEWS.md
    • 我在 dplyr 解决方案中使用 function(x) .Internal(unique(x, FALSE, FALSE, NA)) 而不是 unique 将差异降低到 6 倍,但仍然存在很大差异。
    • 我找不到有关 setDT 函数的任何信息,也无法让它在 R 中运行 - 您可以提供的任何文档将不胜感激。谢谢阿伦。
    • 除了提问者专门要求“快速匹配”解决方案之外,很高兴知道 data.table 索引如此之快。 fastmatch 不太清楚的一件事是哈希的生命周期。我猜,DT 遵循通常的 R 作用域/GC 规则。 fastmatch 的好处在于它在许多情况下可以作为替代品,其中 DT 带来了全新的语法,就好像 R 还不够古怪一样。我知道这并不能回答问题,但整个讨论突出了我正在努力解决的核心 R 效率低下的一个主要领域。
    【解决方案2】:

    您可以尝试创建 10MM 值的索引并对其进行排序。然后在该索引向量中查找您的 1MM 值应该更快。

    例如,使用data.table 包,您可以使用setkey 函数来索引给定的data.table 列。

    require(data.table)
    
    set.seed(100)
    
    dat <- sample(1:1e+07, size = 1e+07, replace = T)
    searchval <- sample(dat, size = 1e+06)
    
    DT <- data.table(dat, index = seq_along(dat))
    setkey(DT, dat)
    DT
    ##                dat   index
    ##        1:        1  169458
    ##        2:        1 4604823
    ##        3:        1 7793446
    ##        4:        2 5372388
    ##        5:        3 2036622
    ##       ---                 
    ##  9999996:  9999996 1271426
    ##  9999997:  9999998  530029
    ##  9999998: 10000000  556672
    ##  9999999: 10000000 6776063
    ## 10000000: 10000000 6949665
    
    
    lookup <- data.table(val = searchval)
    setkey(lookup, val)
    lookup
    ##              val
    ##       1:       2
    ##       2:      16
    ##       3:      24
    ##       4:      33
    ##       5:      36
    ##      ---        
    ##  999996: 9999970
    ##  999997: 9999973
    ##  999998: 9999988
    ##  999999: 9999996
    ## 1000000: 9999998
    

    现在您只需使用即可在DT 中查找来自lookup 的所有值

    DT[lookup]
    ##              dat   index
    ##       1:       2 5372388
    ##       2:      16  537927
    ##       3:      16 1721233
    ##       4:      24 7286522
    ##       5:      33 7448516
    ##      ---                
    ## 2000298: 9999973 8008610
    ## 2000299: 9999988 3099060
    ## 2000300: 9999988 7996302
    ## 2000301: 9999996 1271426
    ## 2000302: 9999998  530029
    

    【讨论】:

    • 这很有帮助 - 非常感谢您的回复。但是,我不太确定速度增益 - 我认为我仍然必须遍历唯一的地址值并通过 DT [查找] 查找它们。对我的 1% 的数据运行一个循环需要 5 分钟...线性推断,我们仍在讨论 ~ 8 小时。
    【解决方案3】:

    fmatch 似乎明确指出它只找到第一个匹配项。鉴于它使用底层散列策略,我想它不太可能为每个键存储多个项目,这是它保持如此快速的方式之一(这与 match 的工作方式相同)。

    你有很多重复的值吗?也许您可以将它们存储在单独的位置/表中,并为可能的匹配列表创建一个快速索引。如果您提供代表您正在尝试做的事情的示例数据以及您尝试查看它是否易于扩展的代码,将会更有帮助。

    【讨论】:

    • 这里是一些示例数据:
    【解决方案4】:

    如果我正确理解您的问题,您也可以使用dplyr

    我将包括两种不同的方式,因为我不完全确定哪个是您想要的输出。

    首先创建一些示例数据:

    Address <- rep(letters, 5)
    Type <- sample(1:5, size=5*26, replace=T)
    A <- data.frame(Address, Type)
    

    然后安装加载dplyr

    require(dplyr)
    

    a) 查找每个 Address 值的不同 Type 值的数量:

    A %.% arrange(Address, Type) %.% group_by(Address) %.% summarize(NoOfTypes = length(unique(Type)))
    

    b) 查找AddressType 的所有唯一组合:

    A %.% arrange(Address, Type) %.% group_by(Address, Type) %.% filter( 1:n() == 1)
    

    【讨论】:

    • 问题是“有人可以帮助我使用此功能查找所有值,而不仅仅是第一个吗?”其中“这个函数”是“fmatch”。
    猜你喜欢
    • 2014-11-29
    • 2019-09-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-01-29
    相关资源
    最近更新 更多