4.7 变量变换

在 Section 4.3.1 中,我们使用 filter 函数筛选一列或多列数据。 回忆一下, filter 函数使用 source => f::Function 这样的语法:filter(:name => name -> name == "Alice", df)

在 Section 4.4 中, 我们使用 select 函数选择一列或多列源数据, 并传入一个或多个目标列 source => target。 同样也有例子帮助回忆: select(df, :name => :people_names)

本节将讨论如何 变换 变量,即如何 更改数据DataFrames.jl 中对应的语法是 source => transformation => target

与之前一样,使用 grades_2020 数据集:

function grades_2020()
    name = ["Sally", "Bob", "Alice", "Hank"]
    grade_2020 = [1, 5, 8.5, 4]
    DataFrame(; name, grade_2020)
end
grades_2020()
name grade_2020
Sally 1.0
Bob 5.0
Alice 8.5
Hank 4.0

假设想要 grades_2020 中的所有成绩加 1。 首先,需要定义一个接收向量数据并使所有元素加 1 的函数。 然后使用 DataFrames.jl 中的 transform 函数。与其他原生 DataFrames.jl 函数一样,按照其语法,它接收 DataFrame 作为第一个参数:

plus_one(grades) = grades .+ 1
transform(grades_2020(), :grade_2020 => plus_one)
name grade_2020 grade_2020_plus_one
Sally 1.0 2.0
Bob 5.0 6.0
Alice 8.5 9.5
Hank 4.0 5.0

如上, plus_one 函数接收了 :grade_2020 整列。 这就是为什么要在加 + 运算符前添加 . 广播运算符。 可以查阅 Section 3.3.1 回顾有关广播的操作。

如之前所说, DataFrames.jl 总是支持 source => transformation => target 这样的短语法。 所以,如果想在输出中保留 target 列的命名,操作如下:

transform(grades_2020(), :grade_2020 => plus_one => :grade_2020)
name grade_2020
Sally 2.0
Bob 6.0
Alice 9.5
Hank 5.0

也可以使用关键字参数 renamecols=false

transform(grades_2020(), :grade_2020 => plus_one; renamecols=false)
name grade_2020
Sally 2.0
Bob 6.0
Alice 9.5
Hank 5.0

还可以使用 select 实现相同的转换,具体如下:

select(grades_2020(), :, :grade_2020 => plus_one => :grade_2020)
name grade_2020
Sally 2.0
Bob 6.0
Alice 9.5
Hank 5.0

其中 : 表明 “选择所有列” ,正如在 Section 4.4 讨论的那样。 另外,还可以使用 Julia 广播更改 grade_2020 列,即直接访问 df.grade_2020

df = grades_2020()
df.grade_2020 = plus_one.(df.grade_2020)
df
name grade_2020
Sally 2.0
Bob 6.0
Alice 9.5
Hank 5.0

但是,尽管很容易使用 Julia 原生操作构建最后的例子,我们仍然强烈建议使用在大多数例子中提到的 DataFrames.jl 函数,因为它们更加强大并且更容易与其他代码组织

4.7.1 多条件变换

为了展示如何同时更改两列, 我们使用 Section 4.6 中的左合并数据:

leftjoined = 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

结合此数据集,我们增加一列来判断每位同学是否都有一门课的成绩大于 5.5:

pass(A, B) = [5.5 < a || 5.5 < b for (a, b) in zip(A, B)]
transform(leftjoined, [:grade_2020, :grade_2021] => pass; renamecols=false)
name grade_2020 grade_2021 grade_2020_grade_2021
Sally 1.0 9.5 true
Hank 4.0 6.0 true
Bob 5.0 missing missing
Alice 8.5 missing true

可以清理下结果,并将上述逻辑整合到一个函数中,然后最终得到符合标准学生的名单:

function only_pass()
    leftjoined = leftjoin(grades_2020(), grades_2021(); on=:name)
    pass(A, B) = [5.5 < a || 5.5 < b for (a, b) in zip(A, B)]
    leftjoined = transform(leftjoined, [:grade_2020, :grade_2021] => pass => :pass)
    passed = subset(leftjoined, :pass; skipmissing=true)
    return passed.name
end
only_pass()
["Sally", "Hank", "Alice"]


CC BY-NC-SA 4.0 Jose Storopoli, Rik Huijzer, Lazaro Alonso, 刘贵欣 (中文翻译), 田俊 (中文审校)