Missing Values

Julia 提供了在统计意义上表示缺失值的支持。这适用于观察中某个变量没有可用值的情况,但理论上存在有效值。缺失值通过 missing 对象表示,该对象是类型 Missing 的单例实例。missing 等同于 NULL in SQLNA in R,并在大多数情况下表现得像它们。

Propagation of Missing Values

missing自动传播 当传递给标准数学运算符和函数时。对于这些函数,关于一个操作数值的不确定性会引起对结果的不确定性。在实践中,这意味着涉及 missing 值的数学运算通常返回 missing

julia> missing + 1
missing

julia> "a" * missing
missing

julia> abs(missing)
missing

由于 missing 是一个普通的 Julia 对象,因此此传播规则仅适用于选择实现此行为的函数。这可以通过以下方式实现:

  • 为类型为 Missing 的参数添加特定方法,
  • 接受这种类型的参数,并将它们传递给传播它们的函数(如标准数学运算符)。

包应该考虑在定义新函数时传播缺失值是否有意义,并在这种情况下适当地定义方法。将 missing 值传递给一个没有接受 Missing 类型参数的方法的函数会抛出 MethodError,就像对于任何其他类型一样。

不传播 missing 值的函数可以通过将它们包装在 Missings.jl 包提供的 passmissing 函数中来实现。例如,f(x) 变为 passmissing(f)(x)

Equality and Comparison Operators

标准的相等和比较运算符遵循上述传播规则:如果任何一个操作数是 missing,结果就是 missing。以下是一些示例:

julia> missing == 1
missing

julia> missing == missing
missing

julia> missing < 1
missing

julia> 2 >= missing
missing

特别注意,missing == missing 返回 missing,因此不能使用 == 来测试一个值是否缺失。要测试 x 是否为 missing,请使用 ismissing(x)

特殊比较运算符 isequal=== 是传播规则的例外。即使在存在 missing 值的情况下,它们也会始终返回 Bool 值,将 missing 视为等于 missing,并与任何其他值不同。因此,它们可以用于测试一个值是否为 missing

julia> missing === 1
false

julia> isequal(missing, 1)
false

julia> missing === missing
true

julia> isequal(missing, missing)
true

isless 运算符是另一个例外:missing 被视为大于任何其他值。这个运算符被 sort! 使用,因此将 missing 值放在所有其他值之后:

julia> isless(1, missing)
true

julia> isless(missing, Inf)
false

julia> isless(missing, missing)
false

Logical operators

逻辑(或布尔)运算符 |&xor 是另一种特殊情况,因为它们仅在逻辑上需要时传播 missing 值。对于这些运算符,结果是否不确定取决于特定的操作。这遵循了 three-valued logic 的成熟规则,这些规则在 SQL 中由 NULL 和在 R 中由 NA 实现。这个抽象定义对应于一种相对自然的行为,最好通过具体示例来解释。

让我们用逻辑“或”运算符 | 来说明这个原则。根据布尔逻辑的规则,如果其中一个操作数为 true,另一个操作数的值对结果没有影响,结果将始终为 true

julia> true | true
true

julia> true | false
true

julia> false | true
true

基于这一观察,我们可以得出结论:如果一个操作数为 true 而另一个为 missing,我们知道结果为 true,尽管对其中一个操作数的实际值存在不确定性。如果我们能够观察到第二个操作数的实际值,它只能是 truefalse,在这两种情况下,结果都是 true。因此,在这个特定情况下,缺失性并不会 传播

julia> true | missing
true

julia> missing | true
true

相反,如果其中一个操作数为 false,则结果可能是 truefalse,这取决于另一个操作数的值。因此,如果该操作数是 missing,结果也必须是 missing

julia> false | true
true

julia> true | false
true

julia> false | false
false

julia> false | missing
missing

julia> missing | false
missing

逻辑“与”运算符 & 的行为类似于 | 运算符,不同之处在于当其中一个操作数为 false 时,缺失值不会传播。例如,当第一个操作数为 false 时:

julia> false & false
false

julia> false & true
false

julia> false & missing
false

另一方面,当其中一个操作数为 true 时,缺失值会传播,例如第一个:

julia> true & true
true

julia> true & false
false

julia> true & missing
missing

最后,“异或”逻辑运算符 xor 始终传播 missing 值,因为两个操作数始终对结果产生影响。还要注意,否定运算符 ! 在操作数为 missing 时返回 missing,就像其他一元运算符一样。

Control Flow and Short-Circuiting Operators

控制流运算符包括 ifwhileternary operator x ? y : z 不允许缺失值。这是因为我们无法确定如果能够观察到实际值,它是 true 还是 false。这意味着我们不知道程序应该如何行为。在这种情况下,一旦在此上下文中遇到 missing 值,就会抛出 TypeError

julia> if missing
           println("here")
       end
ERROR: TypeError: non-boolean (Missing) used in boolean context

出于同样的原因,与上述逻辑运算符相反,短路布尔运算符 &&|| 在操作数的值决定是否评估下一个操作数的情况下,不允许存在 missing 值。例如:

julia> missing || false
ERROR: TypeError: non-boolean (Missing) used in boolean context

julia> missing && false
ERROR: TypeError: non-boolean (Missing) used in boolean context

julia> true && missing && false
ERROR: TypeError: non-boolean (Missing) used in boolean context

相反,当结果可以在没有 missing 值的情况下确定时,不会抛出错误。这种情况发生在代码在评估 missing 操作数之前短路时,以及当 missing 操作数是最后一个操作数时:

julia> true && missing
missing

julia> false && missing
false

Arrays With Missing Values

包含缺失值的数组可以像其他数组一样创建:

julia> [1, missing]
2-element Vector{Union{Missing, Int64}}:
 1
  missing

如这个例子所示,这种数组的元素类型是 Union{Missing, T},其中 T 是非缺失值的类型。这反映了数组条目可以是类型 T(这里是 Int64)或类型 Missing。这种数组使用一种高效的内存存储,相当于一个 Array{T} 来保存实际值,结合一个 Array{UInt8} 来指示条目的类型(即它是 Missing 还是 T)。

可以使用标准语法构造允许缺失值的数组。使用 Array{Union{Missing, T}}(missing, dims) 创建填充缺失值的数组:

julia> Array{Union{Missing, String}}(missing, 2, 3)
2×3 Matrix{Union{Missing, String}}:
 missing  missing  missing
 missing  missing  missing
Note

使用 undefsimilar 可能会当前给出一个填充了 missing 的数组,但这并不是获得此类数组的正确方法。请使用上面所示的 missing 构造函数。

一个允许 missing 条目的元素类型的数组(例如 Vector{Union{Missing, T}})如果不包含任何 missing 条目,可以使用 convert 转换为不允许 missing 条目的数组类型(例如 Vector{T})。如果数组包含 missing 值,在转换过程中会抛出 MethodError

julia> x = Union{Missing, String}["a", "b"]
2-element Vector{Union{Missing, String}}:
 "a"
 "b"

julia> convert(Array{String}, x)
2-element Vector{String}:
 "a"
 "b"

julia> y = Union{Missing, String}[missing, "b"]
2-element Vector{Union{Missing, String}}:
 missing
 "b"

julia> convert(Array{String}, y)
ERROR: MethodError: Cannot `convert` an object of type Missing to an object of type String

Skipping Missing Values

由于 missing 值在标准数学运算符中会传播,因此当在包含缺失值的数组上调用归约函数时,它们会返回 missing

julia> sum([1, missing])
missing

在这种情况下,使用 skipmissing 函数来跳过缺失值:

julia> sum(skipmissing([1, missing]))
1

这个便利函数返回一个迭代器,它有效地过滤掉 missing 值。因此,它可以与任何支持迭代器的函数一起使用:

julia> x = skipmissing([3, missing, 2, 1])
skipmissing(Union{Missing, Int64}[3, missing, 2, 1])

julia> maximum(x)
3

julia> sum(x)
6

julia> mapreduce(sqrt, +, x)
4.146264369941973

通过在数组上调用 skipmissing 创建的对象可以使用来自父数组的索引进行索引。对应于缺失值的索引对于这些对象无效,尝试使用它们时会抛出错误(它们也会被 keyseachindex 跳过):

julia> x[1]
3

julia> x[2]
ERROR: MissingException: the value at index (2,) is missing
[...]

这允许操作于索引的函数与 skipmissing 结合使用。这尤其适用于搜索和查找函数。这些函数返回对 skipmissing 返回的对象有效的索引,并且也是在父数组中匹配条目的索引:

julia> findall(==(1), x)
1-element Vector{Int64}:
 4

julia> findfirst(!iszero, x)
1

julia> argmax(x)
1

使用 collect 提取非 missing 值并将它们存储在一个数组中:

julia> collect(x)
3-element Vector{Int64}:
 3
 2
 1

Logical Operations on Arrays

上述描述的三值逻辑也适用于应用于数组的逻辑函数。因此,使用 == 运算符的数组相等性测试在无法确定结果的情况下会返回 missing,而不需要知道 missing 条目的实际值。实际上,这意味着如果被比较数组的所有非缺失值相等,但一个或两个数组包含缺失值(可能在不同的位置),则返回 missing

julia> [1, missing] == [2, missing]
false

julia> [1, missing] == [1, missing]
missing

julia> [1, 2, missing] == [1, missing, 2]
missing

对于单个值,使用 isequalmissing 值视为与其他 missing 值相等,但与非 missing 值不同:

julia> isequal([1, missing], [1, missing])
true

julia> isequal([1, 2, missing], [1, missing, 2])
false

函数 anyall 也遵循三值逻辑的规则。因此,当结果无法确定时返回 missing

julia> all([true, missing])
missing

julia> all([false, missing])
false

julia> any([true, missing])
true

julia> any([false, missing])
missing