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 数据集:

  1. function grades_2020()
  2. name = ["Sally", "Bob", "Alice", "Hank"]
  3. grade_2020 = [1, 5, 8.5, 4]
  4. DataFrame(; name, grade_2020)
  5. end
  6. grades_2020()
namegrade_2020
Sally1.0
Bob5.0
Alice8.5
Hank4.0

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

  1. plus_one(grades) = grades .+ 1
  2. transform(grades_2020(), :grade_2020 => plus_one)
namegrade_2020grade_2020_plus_one
Sally1.02.0
Bob5.06.0
Alice8.59.5
Hank4.05.0

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

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

  1. transform(grades_2020(), :grade_2020 => plus_one => :grade_2020)
namegrade_2020
Sally2.0
Bob6.0
Alice9.5
Hank5.0

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

  1. transform(grades_2020(), :grade_2020 => plus_one; renamecols=false)
namegrade_2020
Sally2.0
Bob6.0
Alice9.5
Hank5.0

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

  1. select(grades_2020(), :, :grade_2020 => plus_one => :grade_2020)
namegrade_2020
Sally2.0
Bob6.0
Alice9.5
Hank5.0

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

  1. df = grades_2020()
  2. df.grade_2020 = plus_one.(df.grade_2020)
  3. df
namegrade_2020
Sally2.0
Bob6.0
Alice9.5
Hank5.0

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

4.7.1 多条件变换

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

  1. leftjoined = leftjoin(grades_2020(), grades_2021(); on=:name)
namegrade_2020grade_2021
Sally1.09.5
Hank4.06.0
Bob5.0missing
Alice8.5missing

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

  1. pass(A, B) = [5.5 < a || 5.5 < b for (a, b) in zip(A, B)]
  2. transform(leftjoined, [:grade_2020, :grade_2021] => pass; renamecols=false)
namegrade_2020grade_2021grade_2020_grade_2021
Sally1.09.5true
Hank4.06.0true
Bob5.0missingmissing
Alice8.5missingtrue

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

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

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