遺失值

Julia 提供支援,以表示統計意義上的遺失值。這是指在觀察中變數沒有可用值,但理論上存在有效值的情況。遺失值透過 missing 物件表示,它是 Missing 類型的單例實例。missing 等同於 SQL 中的 NULLR 中的 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,儘管不確定其中一個運算元的實際值。如果我們能夠觀察到第二個運算元的實際值,它只能為 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

邏輯「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,就像其他一元運算元一樣。

控制流程和短路運算元

控制流程運算子包含 ifwhile三元運算子 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} 結合表示項目類型(即 MissingT)的 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
注意事項

目前使用 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

略過遺失值

由於 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

陣列的邏輯運算

上述邏輯運算子所描述的三值邏輯也用於套用於陣列的邏輯函式。因此,使用 == 運算子進行陣列相等性測試,當無法在不知道 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

對於單一值,請使用 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