为什么读取行比读取列快?

12

我正在分析一个包含200行和1200列的数据集,该数据集存储在一个.CSV文件中。为了处理数据,我使用R语言的read.csv()函数来读取这个文件。

R语言需要大约600秒来读取这个数据集。后来我有一个想法,对.CSV文件中的数据进行转置,并再次使用read.csv()函数进行读取。令人惊讶的是,这次只用了大约20秒的时间。你可以看到它快了大约30倍。

我进行了以下迭代验证:

读取200行和1200列(未转置)

> system.time(dat <- read.csv(file = "data.csv", sep = ",", header = F))

   user  system elapsed 
 610.98    6.54  618.42 # 1st iteration
 568.27    5.83  574.47 # 2nd iteration
 521.13    4.73  525.97 # 3rd iteration
 618.31    3.11  621.98 # 4th iteration
 603.85    3.29  607.50 # 5th iteration

读取1200行和200列(转置)

> system.time(dat <- read.csv(file = "data_transposed.csv",
      sep = ",", header = F))

   user  system elapsed 
  17.23    0.73   17.97 # 1st iteration
  17.11    0.69   17.79 # 2nd iteration
  20.70    0.89   21.61 # 3rd iteration
  18.28    0.82   19.11 # 4th iteration
  18.37    1.61   20.01 # 5th iteration

在任何数据集中,我们采用行来观察变量,列来包含要观察的变量。转置会改变数据的结构。即使它使数据看起来奇怪,将数据转置以进行处理是一个好习惯吗?

我想知道为什么当我转置数据时,R可以快速读取数据集。我确定这是因为先前的维度是200 * 1200,转置操作后变成了1200 * 200为什么当我转置数据时,R可以快速读取数据?


更新:研究和实验


我最初提出这个问题是因为我的RStudio在读取和计算高维数据集(相对于行而言有很多列[200行,1200列])时需要很长时间。我使用内置的R函数read.csv()。我阅读了下面的评论,根据他们的建议,我尝试了read.csv2()fread()函数,它们都表现良好,但它们对于我的原始数据集[200行*1200列]执行得比较慢,而它们读取转置的数据集却更快。

我发现这也适用于 MS-Excel Libre office Calc。我甚至尝试将其打开到 Sublime Text编辑器中,即使对于这个文本编辑器,读取转置数据也很容易(快速)。我仍然无法弄清楚为什么所有这些应用程序都会这样做。如果您的数据的列比行多,所有这些应用程序都会遇到麻烦。

因此,总结整个故事,我只有3个问题。

  1. 这是什么问题?它与操作系统有关还是应用程序级别的问题?
  2. 将数据转置以进行处理是一种好习惯吗?
  3. 为什么R或其他应用程序在转置数据时读取我的数据很快?

我的实验可能帮助我重新发现一些“已知”的智慧,但我在互联网上找不到任何相关信息。请分享这样的良好编程/数据分析实践。


5
我认为这与 R 需要猜测每一列的类别有关。当你有较少的列时,R 不必经常猜测。 - Lennyy
2
另外,您可以使用readr包中的read_csv或datatable包中的fread()来加速读取CSV文件。 - Lennyy
@Len 是的,这就是为什么 [.CSV] 是最流行的数据格式。 你可能是对的,我肯定会尝试使用 read_csv(),谢谢。同时,我尝试使用 Sublime 文本编辑器读取这两个数据集,结果和我预期的一样。Sublime 花费了很多时间来读取“未转置”的数据集。另一方面,它在相对较短的时间内加载了转置数据。 - user2273202
3
特别是datatable::fread通常给我很好的结果。同时,也可以参考这里:https://dev59.com/_XI-5IYBdhLWcg3wsKh4。 - Lennyy
4
然而,这与每个列可能属于不同的类有关,这意味着R需要猜测每个类。这需要时间。由于在您的转置数据框中列数较少,因此无需经常猜测。虽然fread通常比read.csv()更快,但它不能解决csv文件具有如此多列的问题。 - Lennyy
显示剩余7条评论
2个回答

7
您的问题基本上是关于:阅读长数据集是否比阅读宽数据集快得多?
这里提供的不是最终答案,而是一个新的起点。
对于任何与性能相关的问题,进行分析比猜测更好。system.time很好,但它只告诉您总运行时间,而不是时间如何分配。如果您快速浏览一下read.table的源代码(read.csv只是read.table的包装器),它包含三个阶段:
1. 调用scan读取5行数据。我不完全确定此部分的目的; 2. 调用scan读入您的完整数据。基本上,这将把您的数据按列读入到字符字符串列表中,其中每列都是“记录”; 3. 类型转换,可以通过type.convert隐式地转换,也可以通过明确指定列类别来进行转换,例如as.numeric、as.Date等。
前两个阶段是在C级别完成的,最后一个阶段是在R级别完成的,通过循环遍历所有记录进行操作。
一个基本的分析工具是Rprof和summaryRprof。以下是一个非常简单的例子。
## configure size
m <- 10000
n <- 100

## a very very simple example, where all data are numeric
x <- runif(m * n)

## long and wide .csv
write.csv(matrix(x, m, n), file = "long.csv", row.names = FALSE, quote = FALSE)
write.csv(matrix(x, n, m), file = "wide.csv", row.names = FALSE, quote = FALSE)

## profiling (sample stage)
Rprof("long.out")
long <- read.csv("long.csv")
Rprof(NULL)

Rprof("wide.out")
wide <- read.csv("wide.csv")
Rprof(NULL)

## profiling (report stage)
summaryRprof("long.out")[c(2, 4)]
summaryRprof("wide.out")[c(2, 4)]

c(2, 4)提取所有R级函数的“by.total”时间,前提是有足够的样本和“总CPU时间”(可能低于挂钟时间)。以下是我在我的2011年Sandy Bridge上获得的Intel i5 2557m @ 1.1GHz(禁用Turbo Boost)

## "long.csv"
#$by.total
#               total.time total.pct self.time self.pct
#"read.csv"            7.0       100       0.0        0
#"read.table"          7.0       100       0.0        0
#"scan"                6.3        90       6.3       90
#".External2"          0.7        10       0.7       10
#"type.convert"        0.7        10       0.0        0
#
#$sampling.time
#[1] 7

## "wide.csv"
#$by.total
#               total.time total.pct self.time self.pct
#"read.table"        25.86    100.00      0.06     0.23
#"read.csv"          25.86    100.00      0.00     0.00
#"scan"              23.22     89.79     23.22    89.79
#"type.convert"       2.22      8.58      0.38     1.47
#"match.arg"          1.20      4.64      0.46     1.78
#"eval"               0.66      2.55      0.12     0.46
#".External2"         0.64      2.47      0.64     2.47
#"parent.frame"       0.50      1.93      0.50     1.93
#".External"          0.30      1.16      0.30     1.16
#"formals"            0.08      0.31      0.04     0.15
#"make.names"         0.04      0.15      0.04     0.15
#"sys.function"       0.04      0.15      0.02     0.08
#"as.character"       0.02      0.08      0.02     0.08
#"c"                  0.02      0.08      0.02     0.08
#"lapply"             0.02      0.08      0.02     0.08
#"sys.parent"         0.02      0.08      0.02     0.08
#"sapply"             0.02      0.08      0.00     0.00
#
#$sampling.time
#[1] 25.86

读取一个长数据集需要7秒的CPU时间,而读取一个宽数据集需要25.86秒的CPU时间。

一开始可能会感到困惑,因为宽数据集报告了更多的功能。实际上,长数据集和宽数据集执行相同的一组功能,但是长数据集更快,因此许多功能所需的时间少于采样间隔(0.02秒),因此无法测量。

但无论如何,运行时间都由scantype.convert(隐式类型转换)主导。对于这个例子,我们可以看到:

  • 即使在R级别进行了类型转换,类型转换的代价也不太高;对于长数据集和宽数据集,它们的代价都不超过总时间的10%;
  • scan基本上就是read.csv所使用的,但不幸的是,我们无法进一步将这样的时间分成阶段1和阶段2。不要认为因为阶段1只读入5行,所以它会非常快。在调试模式下,我发现阶段1可能需要相当长的时间。

那么我们接下来该怎么做呢?

  • 如果我们能找到一种方法来测量阶段1和阶段2scan所花费的时间,那就太好了;
  • 您可能希望对一般情况进行分析,其中您的数据集具有混合数据类别。

我认为内存分析也是必要的。R数据导入/导出似乎表明不同的规格将导致不同的内存使用和性能差异。由于scan函数在C级别(具有.Internal()入口点)运行,因此C级别的内存分析是有帮助的,但这可能取决于操作系统。看起来这篇文章不可能由一个人回答;相反,它需要许多人贡献实际案例研究。 - Zheyuan Li
“?read.table” 解释了第一阶段的“扫描”正在做什么;基本上它旨在检测列数。 - Zheyuan Li
你的想法很重要,我找到了一种新的分析方法,非常感谢你在这里发布的内容。我觉得我需要弄清楚缺少什么,我会再次带来更多实验结果。谢谢你的回答。 - user2273202

1
宽数据集通常比长数据集(即转置数据集)读入内存的速度慢。这会影响到许多读取数据的程序,例如 R、Python、Excel 等,尤其是对 R 更为相关:
- R 需要为每个单元格分配内存,即使它是 NA。这意味着每列至少有与 csv 文件中行数相同数量的单元格,而在长数据集中,你可以潜在地删除 NA 值并节省一些空间。 - R 必须猜测每个值的数据类型,并确保它与列的数据类型一致,这也会引入开销。
由于你的数据集似乎不包含任何 NA 值,我的直觉是你看到了第二点的速度提升。你可以通过将 colClasses = rep('numeric', 20) 传递给 20 列数据集的 read.csv 或 fread,或者将 rep('numeric', 120) 传递给 120 列数据集,来测试这个理论,这应该可以减少猜测数据类型的开销。

好的,我会尝试使用 colClasses 参数进行实验。一定会和你讨论结果的。谢谢。 - user2273202

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接