DataFrames.jl 较早地添加了 filter 函数, 它更强大且与 Julia Base 库的语法保持一致,因此我们先讨论 filtersubset 是较新的函数,但它通常更简便。

    由此开始,接下来将讨论 DataFrames.jl 中非常强大的特性。 在讨论伊始,首先学习一些函数,例如 selectfilter。 但请不要担心! 可以先松一口气,因为 DataFrames.jl 的总体设计目标就是让用户需学习的函数保持在最低限度

    与之前一样,从 grades_2020 开始:

    可以使用 filter(source => f::Function, df) 筛选行。 注意,这个函数与 Julia Base 模块中的 filter(f::Function, V::Vector) 函数非常相似。 这是因为 DataFrames.jl 使用多重派发 (see Section 2.3.3) 扩展filter,以使其能够接收DataFrame 作为参数。

    从第一印象来看,实际中定义和使用函数 f 可能有些困难。 但请坚持学习,我们的努力会有超高的回报,因为 这是非常强大的数据筛选方法。 如下是一个简单的例子, 创建函数 equals_alice 来检查输入是否等于 “Alice”:

    1. equals_alice(name::String) = name == "Alice"
    2. equals_alice("Bob")
    1. false
    1. equals_alice("Alice")
    1. true

    结合该函数, 可以使用 f 筛选出所有 name 等于 “Alice” 的行:

    1. filter(:name => equals_alice, grades_2020())
    namegrade_2020
    Alice8.5
    1. filter(equals_alice, ["Alice", "Bob", "Dave"])

    还可以使用 匿名函数 缩短代码长度 (请查阅 Section ):

    1. filter(n -> n == "Alice", ["Alice", "Bob", "Dave"])

      它也可用于 grades_2020:

      1. filter(:name => n -> n == "Alice", grades_2020())
      namegrade_2020
      Alice8.5

      简单来说,上述函数可以理解为 “遍历 :name 列的所有元素,对每一个元素 n,检查 n 是否等于 Alice”。 可能对于某些人来说,这样的代码些许冗长。 幸运的是,Julia 已经扩展了 ==偏函数应用(partial function application) (译注:指定部分参数的函数)。 其中的细节不重要 – 只需知道能像其他函数一样使用 ==

      1. filter(:name => ==("Alice"), grades_2020())

      4.3.2 Subset

      subset 函数的加入使得处理 missing 值 (Section ) 更加容易。 与 filter 相反, subset 对整列进行操作,而不是整行或者单个值。 如果想使用之前的函数,可以将其包装在 ByRow 里:

      1. subset(grades_2020(), :name => ByRow(equals_alice))
      namegrade_2020
      Alice8.5

      另请注意, DataFramesubset(df, args...) 的第一个参数,而而对于 filter 来说是第二个参数,即 filter(f, df)。 这是因为, Julia 定义 filter 的方式为 filter(f, V::Vector),而 DataFrames.jl 在使用多重派发将其扩展到 DataFrame 类型时,选择与现有函数形式保持一致。

      1. subset(grades_2020(), :name => ByRow(name -> name == "Alice"))
      namegrade_2020
      Alice8.5

      或者使用 == 的偏函数应用:

      最后展示 subset 的真正用处。 首先,创建一个含有 missing 值的数据集:

      1. function salaries()
      2. names = ["John", "Hank", "Karen", "Zed"]
      3. salary = [1_900, 2_800, 2_800, missing]
      4. DataFrame(; names, salary)
      5. end
      6. salaries()
      Table 6: Salaries.
      namessalary
      John1900
      Hank2800
      Karen2800
      Zedmissing

      这是一种合理的情况:你想算出同事们的工资,但还没算 Zed 的。 尽管我们不鼓励这么做,但这是一个有趣的例子。 假设我们想知道谁的工资超过了 2000。 如果使用 filter, 但未考虑 missing值,则会失败:

        1. TypeError: non-boolean (Missing) used in boolean context
        2. Stacktrace:
        3. [1] (::DataFrames.var"#99#100"{Base.Fix2{typeof(>), Int64}})(x::Missing)
        4. @ DataFrames ~/.julia/packages/DataFrames/bza1S/src/abstractdataframe/abstractdataframe.jl:1178
        5. ...

        subset 同样会失败,但幸运的是,报错指出一则简单的解决方案:

        1. subset(salaries(), :salary => ByRow(>(2_000)))
        1. ArgumentError: missing was returned in condition number 1 but only true or false are allowed; pass skipmissing=true to skip missing values
        2. Stacktrace:
        3. [1] _and(x::Missing)
        4. @ DataFrames ~/.julia/packages/DataFrames/bza1S/src/abstractdataframe/subset.jl:11

        所以仅需要传递关键字参数 skipmissing=true

        1. subset(salaries(), :salary => ByRow(>(2_000)); skipmissing=true)
        namessalary
        Hank2800
        Karen2800

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