通过 sqldf 包使用 SQL 查询数据框

在前面的章节中,我们学习了如何编写 SQL 语句,在关系型数据库(如 SQLite 和
MySQL )中查询数据。我们可能会想,有没有一种方法,能够直接使用 SQL 进行数据框
查询,就像数据框是关系型数据库中的表一样呢?sqldf 包给了我们肯定的答案。
这个包吸收了 SQLite 的轻量结构和易于嵌入 R 会话的优势。如果还没有这个包,运行
以下命令进行安装:
install.packages("sqldf")
首先,加载包:
library(sqldf)
## Loading required package: gsubfn
## Loading required package: proto
## Loading required package: RSQLite
## Loading required package: DBI
注意到,在加载 sqldf 包时,其他相关的几个包也会被自动载入。因为 sqldf 包的
实现依赖于这些包,它基本上就是在 R 和 SQLite 之间传输数据和转换数据类型。
然后,读入前面使用的产品表格:
product_info <- read_ _csv("data/product-info.csv")
product_stats <- read_ _csv("data/product-stats.csv")
product_tests <- read_ _csv("data/product-tests.csv")
toy_tests <- read_ _csv("data/product-toy-tests.csv")
这个包的神奇之处在于,我们可以使用 SQL 语句直接查询工作环境中的数据框。例如,
选择 product_info 中的所有记录:
sqldf("select * from product_info")
## Loading required package: tcltk
## id name type class released
## 1 T01 SupCar toy vehicle yes
## 2 T02 SupPlane toy vehicle no
## 3 M01 JeepX model vehicle yes
## 4 M02 AircraftX model vehicle yes
## 5 M03 Runner model people yes
## 6 M04 Dancer model people no
sqldf 和 SQLite 一样,支持简单的选择请求,例如可以选择特定的列:
sqldf("select id, name, class from product_info")
## id name class
## 1 T01 SupCar vehicle
## 2 T02 SupPlane vehicle
## 3 M01 JeepX vehicle
## 4 M02 AircraftX vehicle
## 5 M03 Runner people
## 6 M04 Dancer people
或者,根据特定的条件筛选记录:
sqldf("select id, name from product_info where released = 'yes'")
## id name
## 1 T01 SupCar
## 2 M01 JeepX
## 3 M02 AircraftX
## 4 M03 Runner
也可以计算一个新列并对其命名:
sqldf("select id, material, size / weight as density from product_stats")
## id material density
## 1 T01 Metal 12.000000
## 2 T02 Metal 7.777778
## 3 M01 Plastics NA
## 4 M02 Plastics 28.333333
## 5 M03 Wood NA
## 6 M04 Wood 26.666667
还可以根据特定的列对记录进行排序:
sqldf("select * from product_stats order by size desc")
## id material size weight
## 1 T02 Metal 350 45.0
## 2 T01 Metal 120 10.0
## 3 M02 Plastics 85 3.0
## 4 M01 Plastics 50 NA
## 5 M04 Wood 16 0.6
## 6 M03 Wood 15 NA
这个包也支持查询多个数据框,如 join。在以下代码中,我们将 product_info 和
product_ stats 按照 id 合并,与之前用 merge( ) 一样:
sqldf("select * from product_info join product_stats using (id)")
## id name type class released material size weight
## 1 T01 SupCar toy vehicle yes Metal 120 10.0
## 2 T02 SupPlane toy vehicle no Metal 350 45.0
## 3 M01 JeepX model vehicle yes Plastics 50 NA
## 4 M02 AircraftX model vehicle yes Plastics 85 3.0
## 5 M03 Runner model people yes Wood 15 NA
## 6 M04 Dancer model people no Wood 16 0.6
同时,它还支持嵌套查询。下面的代码中,我们选出 product_info 中所有的木制
产品记录:
sqldf("select * from product_info where id in
(select id from product_stats where material = 'Wood')")
## id name type class released
## 1 M03 Runner model people yes
## 2 M04 Dancer model people no
除此之外,相同的 where 条件,使用 join 也可以达到相同的目标。对于许多关系型
数据库,在数据量比较大的时候,join 通常运行得比 in 快:
sqldf("select * from product_info join product_stats using (id)
where material = 'Wood'")
## id name type class released material size weight
## 1 M03 Runner model people yes Wood 15 NA
## 2 M04 Dancer model people no Wood 16 0.6
除了join,还可以轻松地对数据进行分组汇总。例如,按照waterproof 把product_
tests 分成两组:yes 和no。对于每一组,计算quality 和durability 的均值,如下:
sqldf("select waterproof, avg(quality), avg(durability) from product_tests
group by waterproof")
## waterproof avg(quality) avg(durability)
## 1 no 10.00 9.5
## 2 yes 5.75 5.0
对于 toy_tests 数据,很容易根据每个产品对数据进行加总。例如,计算每个产品
的 quality 和 durability 在时间上的均值:
sqldf("select id, avg(quality), avg(durability) from toy_tests
group by id")
## id avg(quality) avg(durability)
## 1 T01 9.25 9.25
## 2 T02 8.50 8.50
为了返回更多有价值的信息,我们可以把 product_info 和分组汇总表连接起来,
这样就可以一起展示产品信息和平均指标了:
sqldf("select * from product_info join
(select id, avg(quality), avg(durability) from toy_tests
group by id) using (id)")
## id name type class released avg(quality) avg(durability)
## 1 T01 SupCar toy vehicle yes 9.25 9.25
## 2 T02 SupPlane toy vehicle no 8.50 8.50
使用 sqldf 和 SQL 对数据框进行查询看起来很方便,但是局限性也很明显。
第一,sqldf 默认基于SQLite,SQLite 数据库的局限性,自然也是这个包的局限性,也就
是说内置的分组汇总函数是有限的。官方页面 (https://sqlite.org/lang_aggfunc.html)提供了一系
列函数:avg( )、count( )、group_concat( )、max( )、sum( ) 和 total( )。如
果需要更多函数,如 quantile( ),就不是那么容易了。在 R 中,可以使用更多高级方法来加总列。
第二,查询数据时,需要提供一串选择语句,但是当语句的某部分是由 R 变量决定时,
动态生成 SQL 语句就不那么方便了。
第三,SQL 的限制性也是 sqldf 的限制性。很难使用更加复杂的方法计算新列。例如,
使用现有列计算出一个排序列,就没有那么简单了。然而,在 R 中,只需要调用 order( ) 就
能实现了。另外,实现更加复杂的筛选操作(如基于排序的筛选)会很难或者非常复杂。
例如,你想如何实现这样的筛选呢:按照 size 降序排列,再按 material 分组,然后提
取第 1 个或前两个产品?确实,实现类似的查询需要许多思考和技巧。
然而,如果使用 plyr 包,类似的问题就是小菜一碟。如果还没有安装它,请运行以
下代码:
install.packages("plyr")
为了体验它的便捷之处,我们使用 plyr::ddply 来实现上述筛选。这里将 material
作为数据分离器,即 product_stats 会根据 material 进行分组。此外,这里还自定义
了一个函数,将输入的数据框(的每个部分)转换为新的数据框。之后,ddply( ) 函数
将这些数据框组合在一起:
plyr::ddply(product_stats, "material",
function(x) {
head(x[order(x$size, decreasing = TRUE),], 1L)
})
## id material size weight
## 1 T02 Metal 350 45.0
## 2 M02 Plastics 85 3.0
## 3 M04 Wood 16 0.6
值得说明的是,匿名函数的调用是基于分组的 product_stats 的,也就是说,根据
material 对 product_stats 进行分组,每一组对应的 material 取值唯一。
另一个例子是选出样本数最多的两个测试结果:
plyr::ddply(toy_tests, "id",
function(x) {
head(x[order(x$sample, decreasing = TRUE), ], 2)
})
## id date sample quality durability
## 1 T01 20160405 180 9 10
## 2 T01 20160302 150 10 9
## 3 T02 20160403 90 9 8
## 4 T02 20160502 85 10 9
其中,匿名函数的调用是基于 toy_tests 的两个部分,即 id 为 T01 和 T02 的数据框。
每个数据框子集都根据 sample 降序排列,并取出前两条记录。这个任务很容易就完成了。
另外,ddply 和 plyr 针对很多输入输出的数据类型组合提供了多种多样的函数。想
要了解更多,请访问 http://had.co.nz/plyr/ 和 https://github.com/hadley/plyr。

posted @ 2019-02-11 14:00  NAVYSUMMER  阅读(480)  评论(0编辑  收藏  举报
交流群 编程书籍