缺失值
Julia 提供了支持,用于表示统计意义上的缺失值。这适用于在观测中,某个变量没有可用值,但理论上存在有效值的情况。缺失值通过 missing
对象表示,它是 Missing
类型 的单例实例。missing
等同于 SQL 中的 NULL
和 R 中的 NA
,并且在大多数情况下表现得像它们一样。
缺失值的传播
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
函数中,使它们传播 missing
值。例如,f(x)
变为 passmissing(f)(x)
。
相等和比较运算符
标准相等和比较运算符遵循上面介绍的传播规则:如果任何一个操作数是 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
逻辑运算符
逻辑(或布尔)运算符 |
、&
和 xor
是另一种特殊情况,因为它们只在逻辑上需要时才会传播 missing
值。对于这些运算符,结果是否不确定取决于具体的运算。这遵循由例如 SQL 中的 NULL
和 R 中的 NA
实现的 三值逻辑 的既定规则。这个抽象定义对应于一个相对自然的行为,通过具体的示例可以最好地解释。
让我们用逻辑“或”运算符 |
来说明这个原理。遵循布尔逻辑的规则,如果其中一个操作数是 true
,则另一个操作数的值不会影响结果,结果始终为 true
julia> true | true
true
julia> true | false
true
julia> false | true
true
基于此观察结果,我们可以得出结论,如果其中一个操作数是 true
,另一个操作数是 missing
,我们知道结果是 true
,尽管对其中一个操作数的实际值存在不确定性。如果我们能够观察到第二个操作数的实际值,它只能是 true
或 false
,在这两种情况下,结果都将是 true
。因此,在这种特定情况下,缺失性不会传播
julia> true | missing
true
julia> missing | true
true
相反,如果其中一个操作数是 false
,则结果可能为 true
或 false
,具体取决于另一个操作数的值。因此,如果该操作数是 missing
,则结果也必须是 missing
julia> false | true
true
julia> true | false
true
julia> false | false
false
julia> false | missing
missing
julia> missing | false
missing
逻辑“与”运算符 &
的行为类似于 |
运算符,不同之处在于当其中一个操作数是 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
,就像其他一元运算符一样。
控制流和短路运算符
控制流运算符,包括 if
、while
和 三元运算符 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
包含缺失值的数组
可以像其他数组一样创建包含缺失值的数组
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
当前使用 undef
或 similar
可能会得到填充 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
跳过缺失值
由于 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
创建的对象可以使用来自父数组的索引进行索引。与缺失值对应的索引对于这些对象无效,并且尝试使用它们时会抛出错误(它们也会被 keys
和 eachindex
跳过)。
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
数组上的逻辑运算
上面针对逻辑运算符描述的三值逻辑也由应用于数组的逻辑函数使用。因此,使用 ==
运算符进行的数组相等性测试,只要结果无法确定而不了解 missing
条目的实际值,就会返回 missing
。实际上,这意味着如果比较数组的所有非缺失值相等,但一个或两个数组包含缺失值(可能在不同的位置),则返回 missing
。
julia> [1, missing] == [2, missing]
false
julia> [1, missing] == [1, missing]
missing
julia> [1, 2, missing] == [1, missing, 2]
missing
与单个值一样,使用 isequal
将 missing
值视为等于其他 missing
值,但不等于非缺失值。
julia> isequal([1, missing], [1, missing])
true
julia> isequal([1, 2, missing], [1, missing, 2])
false
函数 any
和 all
也遵循三值逻辑规则。因此,当结果无法确定时,返回 missing
。
julia> all([true, missing])
missing
julia> all([false, missing])
false
julia> any([true, missing])
true
julia> any([false, missing])
missing