【问题标题】:fast way to separate list of list into two lists将列表列表分成两个列表的快速方法
【发布时间】:2017-10-10 14:43:23
【问题描述】:

我在 C 编程方面有很好的经验,而且我习惯于用指针来思考,所以在处理大量数据时我可以得到很好的性能。 R 就不一样了,我还在学习。

我有一个包含大约 100 万行的文件,用“\n”分隔,每行里面有 1、2 个或更多整数,用“”分隔。 我已经能够编写一个读取文件并将所有内容放入列表列表的代码。 有些行可以是空的。 然后,我想将每行的第一个数字(如果存在)放入一个单独的列表中,如果一行为空,则将其传递,其余数字放入第二个列表中。

我在这里发布的代码非常慢(自从我开始写这个问题以来它一直在运行,所以现在我杀了 R),我怎样才能获得不错的速度?在 C 中,这将立即完成。

graph <- function() {
    x <- scan("result", what="", sep="\n")
    y <- strsplit(x, "[[:space:]]+") #use spaces for split number in each line
    y <- lapply(y, FUN = as.integer) #convert from a list of lists of characters to a list of lists of integers
    print("here we go")
    first <- c()
    others <- c()
    for(i in 1:length(y)) {
        if(length(y[i]) >= 1) { 
            first[i] <- y[i][1]
        }
        k <- 2;
        for(j in 2:length(y[i])) {
            others[k] <- y[i][k]
            k <- k + 1
        }
    }

在以前版本的代码中,每行至少有一个数字,并且我只对每行的第一个数字感兴趣,我使用了这段代码(我到处阅读,我应该避免使用 for 循环在 R 等语言中)

yy <- rapply(y, function(x) head(x,1))

这大约需要 5 秒,比上面的要好得多,但与 C 相比仍然烦人

编辑 这是我文件前 10 行的示例:

42 7 31 3 
23 1 34 5 


1 
-23 -34 2 2 

42 7 31 3 31 4 

1

【问题讨论】:

  • 您的文件是 CSV 文件吗?另外,你能分享一下你的“数字”的例子吗?或许您可以说出哪些可能是您文件中的数字:“1 2”、“1 23”、“1 2 3”。
  • @PDE 不,它只是上述格式。我使用 C 程序自己生成文件。如果您愿意,我可以创建一个 CSV 文件,但我想从我的问题中学习代码。你写的所有数字都是有效的,准确地说,在我的情况下,我总是有从 -74 到 50 的数字,而且每行中的数字不超过 6 个。我不使用二进制格式,因为我想用 emacs 轻松处理数据
  • 循环是唯​​一慢的部分?
  • @Moody_Mudskipper 是的
  • @Nisba 你也可以试试这个 StackOverflow 列表中的方法:stackoverflow.com/questions/8299978/…

标签: r performance


【解决方案1】:

Base R 与 purrr

your_list <- rep(list(list(1,2,3,4), list(5,6,7), list(8,9)), 100)

microbenchmark::microbenchmark(
  your_list %>% map(1),
  lapply(your_list, function(x) x[[1]])
)
Unit: microseconds
                                  expr       min        lq       mean    median         uq       max neval
                  your_list %>% map(1) 22671.198 23971.213 24801.5961 24775.258 25460.4430 28622.492   100
 lapply(your_list, function(x) x[[1]])   143.692   156.273   178.4826   162.233   172.1655  1089.939   100

microbenchmark::microbenchmark(
  your_list %>% map(. %>% .[-1]),
  lapply(your_list, function(x) x[-1])
)
Unit: microseconds
                                 expr     min       lq      mean   median       uq      max neval
       your_list %>% map(. %>% .[-1]) 916.118 942.4405 1019.0138 967.4370 997.2350 2840.066   100
 lapply(your_list, function(x) x[-1]) 202.956 219.3455  264.3368 227.9535 243.8455 1831.244   100

purrr 不是性能包,只是方便,这很好,但当您非常关心性能时就不是了。已经讨论过elsewhere


顺便说一句,如果你精通 C,你应该看看包 Rcpp

【讨论】:

  • 你只循环了 4 个元素,所以开销成本被放大了。 OP,您能否确认基本解决方案在您的完整数据上更快?
  • 另外,您的比较对于测试我的解决方案与基本解决方案是公平的,但对 map 不公平,因为管道(其中 2 个)以及可能对点的评估也会产生开销.
  • @Moody_Mudskipper 我正在循环播放 300 个元素。您可以根据需要增加尺寸。
【解决方案2】:

试试这个:

your_list <- list(list(1,2,3,4),
     list(5,6,7),
     list(8,9))

library(purrr)

first <- your_list %>% map(1)
# [[1]]
# [1] 1
# 
# [[2]]
# [1] 5
# 
# [[3]]
# [1] 8

other <- your_list %>% map(. %>% .[-1])    
# [[1]]
# [[1]][[1]]
# [1] 2
# 
# [[1]][[2]]
# [1] 3
# 
# [[1]][[3]]
# [1] 4
# 
# 
# [[2]]
# [[2]][[1]]
# [1] 6
# 
# [[2]][[2]]
# [1] 7
# 
# 
# [[3]]
# [[3]][[1]]
# [1] 9

虽然您可能想要以下内容,但在我看来,这些数字最好存储在向量中而不是列表中:

your_list %>% map(1) %>% unlist # as it seems map_dbl was slow
# [1] 1 5 8
your_list %>% map(~unlist(.x[-1]))
# [[1]]
# [1] 2 3 4
# 
# [[2]]
# [1] 6 7
# 
# [[3]]
# [1] 9

【讨论】:

  • @Nisba 这不是你想要的吗?
  • 我正在阅读它,看起来正是我想要的,我会在几分钟内尝试解决方案。你说得对,使用矢量更适合我的目的。
  • @Moody_Mudskipper 只需使用lapply(your_list, function(x) x[[1]]) 应该会更快
  • 这是迄今为止最好的解决方案,your_list %&gt;% map(. %&gt;% .[-1] %&gt;% unlist)) 相当“快”(5 秒),但是第一个 map_dbl(1) 大约需要 1 分钟。到目前为止,这是最好的解决方案,但它远非快速...... :(
【解决方案3】:

确实,从 C 到 R 会令人困惑(对我来说)。有助于提高性能的是理解 R 中的原始类型都是向量,在高度优化、本机编译的 C 和 Fortran 中实现,当有可用的向量化解决方案时,您应该努力避免循环。

也就是说,我认为您应该通过 read.csv() 将其加载为 csv。这将为您提供一个数据框,您可以使用该数据框执行基于矢量的操作。

为了更好地理解,简明(和幽默)的阅读是http://www.burns-stat.com/pages/Tutor/R_inferno.pdf

【讨论】:

  • 谢谢,我会努力的。我在找你推荐给我的书,我会读的!
【解决方案4】:

我会尝试使用stringr 包。像这样的:

set.seed(3)
d <- replicate(3, sample(1:1000, 3))
d <- apply(d, 2, function(x) paste(c(x, "\n"), collapse = " "))
d
# [1] "169 807 385 \n" "328 602 604 \n" "125 295 577 \n"


require(stringr)
str_split(d, " ", simplify = T)
# [,1]  [,2]  [,3]  [,4]
# [1,] "169" "807" "385" "\n"
# [2,] "328" "602" "604" "\n"
# [3,] "125" "295" "577" "\n"

即使是大数据也很快:

d <- replicate(1e6, sample(1:1000, 3))
d <- apply(d, 2, function(x) paste(c(x, "\n"), collapse = " "))
d
system.time(s <- str_split(d, " ", simplify = T)) #0.77 sek

【讨论】:

  • 谢谢,但是如何将每一行分成两个数字列表呢?一个用于第一列,一个用于剩余?这是我的代码中比较慢的部分
  • 为什么需要列表?在 R 列表中比向量和矩阵慢。
  • 这很好,事实上使用数组和 F. Privé 的解决方案现在代码运行得很好!
【解决方案5】:

假设文件是​​ CSV 文件,并且所有“数字”都严格采用 1 2-1 2 形式(1 2 31 23 不是文件中允许),那么可以从编码开始:

# Install package `data.table` if needed
# install.packages('data.table')

# Load `data.table` package
library(data.table)

# Load the CSV, which has just one column named `my_number`.
# Then, coerce `my_number` into character format and remove negative signs.
DT <- fread('file.csv')[, my_number := as.character(abs(my_number))]

# Extract first character, which would be the first desired digit 
# if my assumption about number formats is correct.
DT[, first_column := substr(my_number, 1, 1)]

# The rest of the substring can go into another column.
DT[, second_column := substr(my_number, 2, nchar(my_number))].

然后,如果您仍然确实需要创建两个列表,您可以执行以下操作。

# Create the first list.
first_list <- DT[, as.list(first_column)]

# Create the second list.
second_list <- DT[, as.list(second_column)]

【讨论】:

  • 我想如果我创建用零填充数字的文件,我可以使用您的解决方案支持多位数字。无论如何,我的行并不总是相同的长度,所以我会记住未来,谢谢!
  • 至少考虑到我的理解,您想要的只是将“号码”的第一个“数字”存储为first_list,其余的“号码”存储为second_list,然后是我的解决方案您的“数字”长度不同没有问题。 second_column 是作为数字的子字符串生成的,从第二个字符(据我了解,它是一个空格)到该“数字”的最后一个字符,无论“数字”可能有多少个字符。跨度>
  • 哦,有一个误解:我需要将每个第一个数字和每个其他数字都存储在其他列表中,而不是数字!
  • @Nisba 正如我上面所要求的,您的数据看起来如何的示例会很好。目前,我假设您的数据每行只有一列:my_number "1 2" "1 2 3" "1 23" "-1 2" "-1 23" 我假设您想要生成 first_list 看起来像:"1" "1" "1" "1" "1" .... 等等。我假设你想生成second_list 如果它说" 2" " 2 3" " 23" " 2" " 23" 就可以了。
  • 我编辑了我的问题以便更好地解释格式
猜你喜欢
  • 2023-02-03
  • 2021-05-23
  • 1970-01-01
  • 2018-12-24
  • 2013-04-15
  • 1970-01-01
  • 1970-01-01
  • 2013-12-12
  • 2016-07-30
相关资源
最近更新 更多