遺失值
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
值傳遞給沒有接受 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
,因此 ==
無法用於測試值是否為 missing。若要測試 x
是否為 missing
,請使用 ismissing(x)
。
特殊比較運算子 isequal
和 ===
是傳播規則的例外。它們將永遠傳回一個 Bool
值,即使在有 missing
值的情況下,也會將 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
邏輯「and」運算元 &
的行為類似於 |
運算元,不同之處在於當其中一個運算元為 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
最後,「exclusive or」邏輯運算元 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}
結合表示項目類型(即 Missing
或 T
)的 Array{UInt8}
。
允許遺失值之陣列可以使用標準語法建構。使用 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
值都相等,但其中一個或兩個陣列包含 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
值,但與非 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