Unverified Commit 6b6a64d5 authored by Hope's avatar Hope Committed by GitHub
Browse files

Add files via upload

parent 7fd04f9b
Loading
Loading
Loading
Loading
+99 −5
Original line number Diff line number Diff line
@@ -4,7 +4,7 @@ execute:
  eval: true
---

在现代数据分析和处理过程中,随着数据量的不断增加,单纯依赖内存来存储和处理数据已经变得不再现实。数据库作为一种高效的外存储解决方案,能够应对大规模数据管理的需求,为数据的存储、检索和处理提供了可靠保障。本章节将详细介绍如何通过数据库来管理和操作数据,从而实现数据持久化、快速查询和高效分析。通过学习数据库管理的基本概念、SQL语言的使用技巧以及数据库与R语言的无缝集成,可以掌握在大数据时代下,高效管理和利用数据的方法。同时,我们还会探讨另一外存储数据处理方案,即基于Arrow的大数据系统,从而通过内存高效的数据交换格式和跨语言的互操作性来提升数据处理性能和灵活性。
在现代数据分析和处理过程中,随着数据量的不断增加,单纯依赖内存来存储和处理数据已经变得不再现实。数据库作为一种高效的外存储解决方案,能够应对大规模数据管理的需求,为数据的存储、检索和处理提供了可靠保障。本章节将详细介绍如何通过数据库来管理和操作数据,从而实现数据持久化、快速查询和高效分析。通过学习数据库管理的基本概念、SQL语言的使用技巧以及数据库与R语言的无缝集成,可以掌握在大数据时代下,高效管理和利用数据的方法。同时,我们还会探讨另一外存储数据处理方案,即基于Arrow或Polars的大数据系统,从而通过内存高效的数据交换格式和跨语言的互操作性来提升数据处理性能和灵活性。

## 磁盘数据处理

@@ -12,7 +12,7 @@ execute:

这种方法的一个显著优势是其可扩展性,能够处理比内存容量大得多的数据集,非常适合大数据分析和处理任务。例如,数据库管理系统(如MySQL、PostgreSQL)、分布式文件系统(如Hadoop HDFS)以及流式处理框架(如Apache Kafka)都广泛使用磁盘数据处理技术。然而,由于磁盘的读写速度相对较慢,磁盘数据处理可能会面临较高的I/O延迟。为了优化性能,通常会采用高速缓存、数据预取和并行I/O操作等技术。

总的来说,磁盘数据处理通过有效利用外部存储设备,为大规模数据处理提供了一种解决方案。尽管存在I/O速度较慢的挑战,但通过适当的优化,可以在大数据环境中实现高效的数据处理和分析。在本章中,我们会描述如何在R环境中调用数据库资源,同时还会介绍另一新兴的大数据处理系统——Apache Arrow。通过对这些工具的介绍,我们可以有效地利用计算机的磁盘资源来对比内存大的数据进行高效处理。
总的来说,磁盘数据处理通过有效利用外部存储设备,为大规模数据处理提供了一种解决方案。尽管存在I/O速度较慢的挑战,但通过适当的优化,可以在大数据环境中实现高效的数据处理和分析。在本章中,我们会描述如何在R环境中调用数据库资源,同时还会介绍另一新兴的大数据处理系统(Arrow和Polars),这些大数据处理方案允许用户把数据先存储为Parquet格式,实际处理的时候不需要把数据载入环境就能够对大数据进行分析。通过对这些工具的介绍,我们可以有效地利用计算机的磁盘资源来对比内存大的数据进行高效处理。

## 数据库操作——以duckdb为例

@@ -120,7 +120,7 @@ big_diamonds_db %>%
dbDisconnect(con)
```

## 基于Arrow的大数据处理平台
## 基于Arrow的大数据处理方案

Apache Arrow是一个跨语言的开发平台,用于高性能数据分析,提供了一种内存中的数据格式,旨在高效地共享数据而无需额外的序列化和反序列化步骤。它的设计目标是加速大数据处理和分析,使在处理和传输大规模数据集时表现出色。Arrow支持多种编程语言,包括C++, Java, Python, R等,使得不同语言之间的数据交换变得非常高效。其列式内存格式使数据在内存中的表示非常紧凑和高效,不仅减少了内存使用,还提升了CPU缓存命中率,从而加速数据处理。此外,通过Arrow的内存格式,不同进程和系统之间可以实现零拷贝的数据共享,大幅减少数据传输的开销。Arrow还与许多大数据系统(如Apache Parquet、Apache Spark、DuckDB等)无缝集成,支持高效的数据存储和处理。除了基本的数据类型,Arrow还支持复杂的数据结构和操作,如嵌套数据、时间戳和向量化操作。因此,Apache Arrow通过提供高效的内存格式和跨语言支持,为大数据处理和分析提供了一个强大而灵活的基础设施,极大地提升了数据密集型应用的性能。

@@ -209,12 +209,106 @@ df_pq %>%

在这种背景下,使用parquet对数据进行存储是非常诱人的,因为这样能够让我们轻松地使用dplyr函数来对存储在磁盘的数据进行操作。

## 基于Polars的大数据处理方案

Polars 是一个高性能的数据框架库,专为数据操作和分析设计。它由 Rust 编写,确保了速度和内存安全,并利用并行处理来最大化性能。Polars 在处理大型数据集时表现出色,设计目的是最小化内存使用,使用高效的数据结构以减少开销。它提供了丰富的数据操作功能,如过滤、排序、聚合和连接等,支持链式操作,使得代码简洁且易读。总的来说,Polars 的特点包括:

-   高性能:Polars利用并行计算和SIMD(单指令多数据)技术,在执行数据操作时大大提高了处理速度。它在处理大数据集时的性能优于许多传统的数据分析库。
-   内存效率:Polars可以使用 Apache Arrow的内存格式(如Parquet),这使得它能够更有效地利用内存。其数据结构经过优化,可以处理更大的数据集,而不会消耗过多的内存资源。
-   灵活的表达能力:Polars提供了一系列丰富的操作功能,包括数据选择、过滤、聚合、排序和连接等,支持链式调用,使得数据处理过程更为流畅。
-   惰性计算:Polars 采用惰性执行策略,意味着只有在必要时才会执行计算。这种设计可以减少不必要的计算和内存开销,提升整体性能。
-   跨平台支持:除了支持多种编程语言外,Polars还可以在不同的操作系统上运行,适用于各种开发环境。
-   用户友好:Polars 的 API设计直观,易于学习和使用,适合各种数据分析任务。

尽管核心是用 Rust 编写的,Polars 提供了 R 接口,因此可以在 R 中方便地使用。Polars 能处理复杂的数据查询和操作,包括时间序列数据、缺失值和类别数据等,未来可能还会支持更多编程语言。在R中要安装核心的Polars包,可以这样操作:

```{r}
#| eval: false
install.packages("polars", repos = "https://community.r-multiverse.org")
install.packages(
  'tidypolars', 
  repos = c('https://etiennebacher.r-universe.dev', getOption("repos"))
)
```

以上代码会安装**polars**和**tidypolars**两个R包,前者负责在R中调用Rust所构建的Polars工具,后者则可以把常用的tidyverse代码(特别是**dplyr**和**tidyr**包中的函数)直接转译为Polars所支持的代码。下面我们对该工具进行简单的演示,首先我们生成一份数据集,并保存在根目录下的temp文件夹中:

```{r}
#| eval: false
library(pacman)
p_load(tidyfst,arrow)

# 生成一亿行
nr_of_rows <- 1e8

# 构造数据框
df <- data.frame(
  Logical = sample(c(TRUE, FALSE, NA), prob = c(0.85, 0.1, 0.05), nr_of_rows, replace = TRUE),
  Integer = sample(1L:100L, nr_of_rows, replace = TRUE),
  Real = sample(sample(1:10000, 20) / 100, nr_of_rows, replace = TRUE),
  Factor = as.factor(sample(labels(UScitiesD), nr_of_rows, replace = TRUE))
)

# 检查大小
object_size(df) # 1.9 Gb

# 导出parquet文件
arrow::write_parquet(df,"temp/df.parquet") # 209.1 Mb

# 清除环境内的所有变量
rm(list = ls())
```

然后,我们利用**polars**包的`scan_parquet`方法把数据扫描到R环境中:

```{r}
#| eval: false
library(pacman)
p_load(polars,tidypolars,tidyverse,tidyfst)

# 扫描数据
pl$scan_parquet("df.parquet") -> dat_pl
```

需要注意的是,在上面的操作中,我们并没有把数据导入到环境里面。我们用了“扫描”一词,其实相当于对数据进行了连接,类似于我们在前一章节中提到的`open_dataset`操作。在这个背景下,我们可以对这个没有导入环境的数据进行各种操作,并把结果收集到环境中进行展示,操作方法如下:

```{r}
#| eval: false
# 观察前6行
dat_pl %>% 
  head() %>% 
  compute()

# 看看总共有多少行
dat_pl %>% count() %>% compute()

# 分组汇总计算
pst(
  dat_pl %>% 
    group_by(Logical,Factor) %>% 
    summarise(Real_mean = mean(Real),Real_sd = sd(Real),
              median_Integer = median(Integer)) %>% 
    compute() -> res 
) # Finished in 3.920s elapsed (15.4s cpu)

# 查看结果
res

# 把结果转化为R中的数据框
res$to_data_frame()

# 把结果转化为数据框并使用tibble形式进行展示
res %>% as_tibble()
```

通过上面的试验,我们可以发现只需要把数据先存为Parquet格式,然后使用`scan_parquet`方法进行数据连接,就可以利用我们熟悉的**dplyr**和**tidyr**函数对保存在磁盘中的数据进行各式的数据操作,这给我们的大数据分析提供了巨大的便利,是解决内存不足计算(Out-of-Memory Computation)的最佳方案之一。

## 小结

本章介绍了如何在数据分析中有效地使用数据库管理数据,并讨论如何使用Arrow来对存在磁盘的数据进行高效处理。通过学习使用**DBI**包连接数据库、执行SQL查询,以及借助**dbplyr**包将**dplyr**代码翻译成SQL,我们能够在R中直接操作和查询数据库中的数据。这种方法不仅提高了数据处理的效率,还减少了对中间文件(如CSV)的依赖,避免了繁琐的数据导入导出步骤。此外,我们还学习了Apache Arrow, 它所提供的Parquet内存格式减少了数据在不同系统间转换的开销,这对于需要处理大量数据的应用程序来说尤为重要。
本章介绍了如何在数据分析中有效地使用数据库管理数据,并讨论如何使用Arrow来对存在磁盘的数据进行高效处理。通过学习使用**DBI**包连接数据库、执行SQL查询,以及借助**dbplyr**包将**dplyr**代码翻译成SQL,我们能够在R中直接操作和查询数据库中的数据。这种方法不仅提高了数据处理的效率,还减少了对中间文件(如CSV)的依赖,避免了繁琐的数据导入导出步骤。此外,我们还学习了Apache Arrow和Polars, 它所提供的Parquet内存格式减少了数据在不同系统间转换的开销,这对于需要处理大量数据的应用程序来说尤为重要。

## 练习

-   尝试使用duckdb方法构建一个数据库,然后实现所有数据库的日常操作,比如对某一列创建索引
-   请比较一下是数据库操作快,还是使用Arrow对数据进行操作快,注意使用同样数据进行比较,同时对数据操作的时间和数据占据的内存进行比较。
-   请比较一下是数据库操作快,还是使用Arrow/Polars对数据进行操作快,注意使用同样数据进行比较,同时对数据操作的时间和数据占据的内存进行比较。
-   请比较一下,在内存允许的情况下,究竟是在内存中对数据进行处理快,还是使用磁盘进行数据处理速度快。
+16 −2
Original line number Diff line number Diff line
@@ -102,10 +102,12 @@ sc <- spark_connect(master = "local")
spark_disconnect(sc)
```

关于连接到其他集群的各种操作,可以参考官方的[配置文档](<https://spark.posit.co/guides/connections.html>)进行查阅。
关于连接到其他集群的各种操作,可以参考官方的[配置文档](https://spark.posit.co/guides/connections.html)进行查阅。

### 读取

把数据导入到Spark数据库中有两种方法,一种方法是利用`copy_to`函数从R环境中导入:

```{r}
tbl_mtcars <- copy_to(sc, mtcars, "spark_mtcars")

@@ -126,7 +128,9 @@ tbl_mtcars
#> # … with more rows, and 2 more variables: gear <dbl>,
#> #   carb <dbl>
```

另一种方法则是从本地的文件直接进行导入,大致形式如下:

```{r}
spark_read_csv(sc, name = "test_table",  path = "/data/test.csv")
```
@@ -134,7 +138,9 @@ spark_read_csv(sc, name = "test_table", path = "/data/test.csv")
**sparklyr**支持直接读入的文件包括CSV、JSON、Parquet等。此外,不仅仅可以对这些格式的文件进行读入,还可以写出不同格式的文件,更多的信息可以参考相关的帮助文档(<https://spark.posit.co/packages/sparklyr/latest/reference/index.html#spark-data>)。

### 清洗

在**sparklyr**中,我们可以直接使用dplyr命令来对数据进行操作:

```{r}
tbl_mtcars %>% 
  group_by(vs,am) %>% 
@@ -153,7 +159,9 @@ tbl_mtcars %>%
# 3     1     0     7 102.   3.57
# 4     0     0    12 194.   3.12
```

在底层,其实这些代码都转化为了SQL语句,如果需要观察,可以使用`show_query`函数,这与我们之前介绍的数据库操作是一致的:

```{r}
tbl_mtcars %>% 
  group_by(vs,am) %>% 
@@ -168,7 +176,9 @@ tbl_mtcars %>%
# FROM `spark_mtcars`
# GROUP BY `vs`, `am`
```

事实上,我们也可以直接使用SQL语句从中调取数据:

```{r}
p_load(DBI)

@@ -180,10 +190,13 @@ dbGetQuery(sc,"SELECT vs, am, hp FROM spark_mtcars LIMIT 5")
# 4  1  0 110
# 5  0  0 175
```

本部分与数据库操作基本一致,因此不再进行赘述。

### 建模

在R中对处于Spark集群的数据进行分析,都是依赖把R命令转化为Spark能够理解的指令来实现的,**sparklyr**把这些指令转化为见名知意的R代码,下面我们来简单进行展示:

```{r}
# 只选取其中3列,并划分训练集(70%)和测试集(30%)
partitions <- mtcars_tbl %>%
@@ -197,8 +210,9 @@ fit <- partitions$training %>%
# 利用模型对测试集进行预测
pred <- ml_predict(fit, partitions$test)
```

我们可以观察到,这里的`sdf_random_split`和`ml_predict`都是**sparklyr**提供的机器学习函数,能够协助我们对集群中的数据进行类似在R中开展的机器学习操作。关于更多在Spark中实现机器学习的操作,可以参考官方文档(<https://spark.posit.co/guides/mlib.html>)。

## 小结
本章我们对分布式计算进行了了解,并学会如何使用**sparklyr**包来在R中对分布式计算进行实现。我们所介绍的功能只是冰山一角,更多的内容可以参考[sparklyr](https://spark.posit.co/)的[官方指南](https://spark.posit.co/guides/)。我们相信,随着前沿工具的蓬勃发展,分布式计算的实现工具会越来越便利,这意味着用户在不清楚底层逻辑的前提下就能够自由地对数据进行调度、处理和建模。正因为有这样的愿景,因此本章没有对更多的技术细节进行深究。相信通过对基本概念的了解,未来读者将会用到更加便捷的工具,从而在应对大规模数据集的时候更加从容地利用分布式计算方法开展分析。

本章我们对分布式计算进行了了解,并学会如何使用**sparklyr**包来在R中对分布式计算进行实现。我们所介绍的功能只是冰山一角,更多的内容可以参考[sparklyr](https://spark.posit.co/)的[官方指南](https://spark.posit.co/guides/)。我们相信,随着前沿工具的蓬勃发展,分布式计算的实现工具会越来越便利,这意味着用户在不清楚底层逻辑的前提下就能够自由地对数据进行调度、处理和建模。正因为有这样的愿景,因此本章没有对更多的技术细节进行深究。相信通过对基本概念的了解,未来读者将会用到更加便捷的工具,从而在应对大规模数据集的时候更加从容地利用分布式计算方法开展分析。
+41 −4
Original line number Diff line number Diff line
@@ -68,16 +68,21 @@ openxlsx2的核心功能包括: 1. XML解析改进:通过整合pugixml库,
这里我们不会浪费篇幅来介绍工具包的技术细节,感兴趣的读者可以参考官方文档(<https://janmarvin.github.io/openxlsx2/>)进行学习。下面我们将会直接在实践中对综合运用本章介绍的工具,从而对文件进行高效的批处理。

## 综合实践

在本部分,会首先生成一个数据集,然后模拟现实情况的需求,对数据进行各式各样的批处理,从而展示如何利用R语言各种工具包对各种文件进行高效的操作。

### 环境配置与数据生成

首先,我们会加载必要的工具包:

```{r}
#| eval: false
library(pacman)
p_load(fs,openxlsx2,archive,tidyfst,tidyverse)
```

然后,我们来生成相关的数据集:

```{r}
#| eval: false
nr_of_rows <- 1e7
@@ -90,21 +95,41 @@ df <- data.table(
  )

df

#           Logical Integer  Real       Factor
#            <lgcl>   <int> <num>       <fctr>
#        1:    TRUE      75 89.32      NewYork
#        2:    TRUE      39  6.99      NewYork
#        3:    TRUE      76 62.89        Miami
#        4:    TRUE      41 41.42      Atlanta
#        5:    TRUE      36 53.85 SanFrancisco
#       ---                                   
#  9999996:    TRUE      37 66.42      Seattle
#  9999997:    TRUE      46  6.99        Miami
#  9999998:    TRUE      78 53.85      NewYork
#  9999999:   FALSE      85 62.89   LosAngeles
# 10000000:    TRUE      20 46.48      Atlanta
```
经过观察,我们知道数据集一共有4列,分别是逻辑型、整数型、数值型和因子型数据。我们不妨来检测一下数据的大小:

由于数据集生成具有一定的随机性,因此读者生成的数据框不会与上面展示的数据框完全一致,但是不影响试验的开展。经过观察,我们知道数据集一共有4列,分别是逻辑型、整数型、数值型和因子型数据。我们不妨来检测一下数据的大小:

```{r}
#| eval: false
object_size(df)
# 190.7 Mb
```

### 数据的保存
在这一部分,我们需要根据需求来对目标数据进行保存。我们的诉求和实现代码如下所示。
1. 在根目录下创建temp文件夹,实现代码如下:

在这一部分,我们需要根据需求来对目标数据进行保存。我们的诉求和实现代码如下所示。 1. 在根目录下创建temp文件夹,实现代码如下:

```{r}
#| eval: false
dir_create("temp")
```

2.  在temp文件夹下创建csv文件夹,把数据根据Integer分组,保存在不同的csv文件中,实现代码如下:

```{r}
#| eval: false
dir_create("temp/csv")
@@ -117,6 +142,7 @@ df %>%
```

3.  在temp文件夹下创建fst文件夹,把数据根据Integer分组,保存在不同的fst文件中,实现代码如下:

```{r}
#| eval: false
dir_create("temp/fst")
@@ -129,12 +155,16 @@ df %>%
```

### 文件的压缩

在本部分中,我们会对先前生成的数据进行打包压缩。由于Excel文件已经是一整个文件,因此不需要再进行压缩操作。我们首先把csv文件都打包为zip文件:

```{r}
#| eval: false
archive_write_dir(archive = "temp/csv.zip",dir = "temp/csv")
```

另一方面,我们把fst文件都打包为tar文件:

```{r}
#| eval: false
archive_write_dir(archive = "temp/fst.tar",dir = "temp/fst")
@@ -143,16 +173,20 @@ archive_write_dir(archive = "temp/fst.tar",dir = "temp/fst")
### 文件的移动

我们知道,文件打包后,移动会更加快。我们不妨来进行尝试,在temp中再建立一个dest文件夹,然后分别把csv文件夹和csv压缩包移动进去,并测试移动时间:

```{r}
#| eval: false
dir_create("temp/dest")
pst(file_move("temp/csv","temp/dest/csv"))
pst(file_move("temp/csv.zip","temp/dest/csv.zip"))
```

如果效果不明显,可以尝试增加文件数量。

### 保存为Excel文件

在这一步中,我们会把fst中的1到3号文件转存在一个Excel文件中(命名为“1-3.xlsx”),分成不同的工作簿进行保存,实现方法如下:

```{r}
#| eval: false
map(path("temp","fst",1:3,ext = "fst"),import_fst) %>% 
@@ -161,15 +195,18 @@ map(path("temp","fst",1:3,ext = "fst"),import_fst) %>%
```

### 文件的删除

下面我们尝试删除temp文件夹,操作方法如下:

```{r}
#| eval: false
file_delete("temp")
```

## 小结

本章介绍了如何在R语言中利用**fs**、**archive**和**openxlsx2**这些工具包,从而对文件进行批处理操作。当我们有海量文件的时候,我们就能够利用命令行对其进行移动、删除、复制等操作,并可以对多个文件进行压缩打包以便于传输、存储和管理。尽管本章没有对各个工具包的特性细节进行介绍(比如**openxlsx2**能够进行图片插入、表格格式设置、样式的更改等),这些内容笔者认为应该在读者需要的时候自行查询帮助文档,进而进行实现。通过本章的学习,相信读者可以了解并熟悉在R语言中如何利用脚本对多个文件进行批处理。

## 练习
- 请设计一套代码,来测试各种压缩方法的特点(压缩比率、压缩时间),并对压缩后文件传输的速度进行测试。

-   请设计一套代码,来测试各种压缩方法的特点(压缩比率、压缩时间),并对压缩后文件传输的速度进行测试。
+3 −0
Original line number Diff line number Diff line
@@ -7,3 +7,5 @@
(@) [Larger-Than-Memory Data Workflows with Apache Arrow](https://arrow-user2022.netlify.app/)
(@) [Apache Arrow R Cookbook](https://arrow.apache.org/cookbook/r/index.html)
(@) [R interface to Apache Spark](https://spark.posit.co/)
(@) [An Introduction to Polars from R](https://pola-rs.github.io/r-polars/vignettes/polars.html)
(@) [tidypolars](https://tidypolars.etiennebacher.com/)
 No newline at end of file
+0 −0

File changed.

Preview size limit exceeded, changes collapsed.

Loading