本章主要展示和讨论关于多张表的操作。 目前为止,我们仅探讨了单张表的操作,接下来将探讨如何合并多张表。 DataFrames.jl
通过 join
函数合并多张表。 join
函数非常强大,但可能需要花些时间才能理解。 然而,你不需要记住下面所有的 join
函数,因为 DataFrames.jl
文档 和本书将会列出它们。 但是,必须要知道存在 join
操作。 如果要在某张 DataFrame
中遍历所有行并与其他数据行比较,那么可能需要如下这些 join
函数。
Section 4 给出了 2020 年的成绩 grades_2020
:
grades_2020()
name | grade_2020 |
---|---|
Sally | 1.0 |
Bob | 5.0 |
Alice | 8.5 |
Hank | 4.0 |
现在需要将 grades_2020
与 2021 年的成绩合并:
grades_2021()
name | grade_2021 |
---|---|
Bob 2 | 9.5 |
Sally | 9.5 |
Hank | 6.0 |
此功能的实现就需要用到 join
。 DataFrames.jl
列出了不少于七种的 join
函数, 这看起来令人生畏,但请坚持,因为它们都很有用,后面将会逐个讨论所有函数。
首先讨论的是 innerjoin
。 假设存在两个数据集 A
和 B
, 分别具有列 A_1, A_2, ..., A_n
和 B_1, B_2, ..., B_m
,并且 其中的一列具有相同的名字:A_1
和 B_1
都是 :id
。 然后对 :id
使用 innerjoin
,则将遍历 A_1
中的所有元素并且与 B_1
中的元素进行比较。 如果元素 是相同的,然后将会把 A_2, ..., A_n
和 B_2, ..., B_m
的相应信息添加到 :id
列后。
好吧,如果你没有明白上面的描述,请不要担心。 请查看如下所示的成绩数据集合并结果:
innerjoin(grades_2020(), grades_2021(); on=:name)
name | grade_2020 | grade_2021 |
---|---|---|
Sally | 1.0 | 9.5 |
Hank | 4.0 | 6.0 |
注意只有 “Sally” 和 “Hank” 同时存在于两个数据集中。 innerjoin
的名字对应了数学中的 交集, 即“存在于 \(A\) 的元素,也存在于 \(B\),或者说存在于 \(B\) 的元素,也存在于 \(A\)”。
也许你在想, “aha,如果我们有inner
,那我们可能也会有 outer
”。 是的,你猜对了!
outerjoin
没有 innerjoin
那么严格,只要在 至少一个数据集中发现包含的 name
,就会将相应的列合并到结果中:
outerjoin(grades_2020(), grades_2021(); on=:name)
name | grade_2020 | grade_2021 |
---|---|---|
Sally | 1.0 | 9.5 |
Hank | 4.0 | 6.0 |
Bob | 5.0 | missing |
Alice | 8.5 | missing |
Bob 2 | missing | 9.5 |
因此,当其中一个原始数据集不存在对应的值时,该方法会创建 missing
值。
如果使用 crossjoin
将会出现更多的 missing
值。 该方法会给出 行的笛卡尔积,也就是行的乘法,即对于每一行创建一个与另一张表中所有行的组合:
crossjoin(grades_2020(), grades_2021(); on=:id)
MethodError: no method matching crossjoin(::DataFrame, ::DataFrame; on=:id)
Closest candidates are:
crossjoin(::DataFrames.AbstractDataFrame, ::DataFrames.AbstractDataFrame; makeunique, renamecols) at ~/.julia/packages/DataFrames/58MUJ/src/join/composer.jl:1567 got unsupported keyword argument "on"
crossjoin(::DataFrames.AbstractDataFrame, ::DataFrames.AbstractDataFrame, !Matched::DataFrames.AbstractDataFrame...; makeunique) at ~/.julia/packages/DataFrames/58MUJ/src/join/composer.jl:1592 got unsupported keyword argument "on"
...
呃,出错了。 因为 crossjoin
并不按行考虑元素,所以不需要将 on
参数指定为想要合并的列:
crossjoin(grades_2020(), grades_2021())
ArgumentError: Duplicate variable names: :name. Pass makeunique=true to make them unique using a suffix automatically.
Stacktrace:
[1] make_unique!(names::Vector{Symbol}, src::Vector{Symbol}; makeunique::Bool)
@ DataFrames ~/.julia/packages/DataFrames/58MUJ/src/other/utils.jl:99
[2] #make_unique#4
@ ~/.julia/packages/DataFrames/58MUJ/src/other/utils.jl:121 [inlined]
[3] #Index#7
...
呃,又出错了。 这是一个 DataFrame
和 join
中很常见的错误。 2020 和 2021 年成绩表有一个重复的列名,即 :name
。 与之前一样,DataFrames.jl
的报错输出给出了一个可能修复此错误的简单建议。 尝试仅传递 makeunique=true
解决此问题:
crossjoin(grades_2020(), grades_2021(); makeunique=true)
name | grade_2020 | name_1 | grade_2021 |
---|---|---|---|
Sally | 1.0 | Bob 2 | 9.5 |
Sally | 1.0 | Sally | 9.5 |
Sally | 1.0 | Hank | 6.0 |
Bob | 5.0 | Bob 2 | 9.5 |
Bob | 5.0 | Sally | 9.5 |
Bob | 5.0 | Hank | 6.0 |
Alice | 8.5 | Bob 2 | 9.5 |
Alice | 8.5 | Sally | 9.5 |
Alice | 8.5 | Hank | 6.0 |
Hank | 4.0 | Bob 2 | 9.5 |
Hank | 4.0 | Sally | 9.5 |
Hank | 4.0 | Hank | 6.0 |
所以现在,对于 2020 和 2021 年成绩表中的每个人,新表都存在表示其成绩的一行。 对于直接的查询,例如“谁的成绩最高?”,笛卡尔积的结果通常不太可行,但对于“统计学” 查询来说具有一定意义。
对数据科学项目更有用的是 leftjoin
和 rightjoin
。 leftjoin
将考虑合并时左侧 DataFrame
中的所有元素:
leftjoin(grades_2020(), grades_2021(); on=:name)
name | grade_2020 | grade_2021 |
---|---|---|
Sally | 1.0 | 9.5 |
Hank | 4.0 | 6.0 |
Bob | 5.0 | missing |
Alice | 8.5 | missing |
此处注意,“Bob” 和 “Alice” 的成绩在 2021 成绩表格中是 缺失 的,这就是为什么对应的位置是 missing
值。 rightjoin
实现了相反的操作:
rightjoin(grades_2020(), grades_2021(); on=:name)
name | grade_2020 | grade_2021 |
---|---|---|
Sally | 1.0 | 9.5 |
Hank | 4.0 | 6.0 |
Bob 2 | missing | 9.5 |
而现在 2020 中的部分成绩是缺失的。
注意到, leftjoin(A, B) != rightjoin(B, A)
,因为它们的列顺序不同。 例如,将下面的输出与之前的输出进行比较:
leftjoin(grades_2021(), grades_2020(); on=:name)
name | grade_2021 | grade_2020 |
---|---|---|
Sally | 9.5 | 1.0 |
Hank | 6.0 | 4.0 |
Bob 2 | 9.5 | missing |
最后讨论 semijoin
和 antijoin
。
semijoin
比 innerjoin
更具有限制性。 它仅返回 存在于左侧 DataFrame
并同时存在于两张 DataFrame
的元素。 这看起来像是 innerjoin
和 leftjoin
的组合。
semijoin(grades_2020(), grades_2021(); on=:name)
name | grade_2020 |
---|---|
Sally | 1.0 |
Hank | 4.0 |
与 semijoin
相对的是 antijoin
。 它仅返回 存在于左侧 DataFrame
但不存在于右侧 DataFrame
的元素。
antijoin(grades_2020(), grades_2021(); on=:name)
name | grade_2020 |
---|---|
Bob | 5.0 |
Alice | 8.5 |