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, 刘贵欣 (中文翻译), 田俊 (中文审校)