Missing Values

Juliaは、統計的な意味で欠損値を表現するためのサポートを提供します。これは、観測において変数の値が利用できないが、理論的には有効な値が存在する状況に対応しています。欠損値は、missingオブジェクトを介して表され、これはタイプMissingのシングルトンインスタンスです。missingは、NULL in SQLおよびNA 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 == missingmissing を返すため、== を使用して値が欠損しているかどうかをテストすることはできません。xmissing であるかどうかをテストするには、ismissing(x) を使用してください。

特別な比較演算子 isequal=== は伝播ルールの例外です。これらは常に Bool 値を返し、missing 値が存在する場合でも、missingmissing と等しいと見なし、他の値とは異なると見なします。したがって、値が 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 であることがわかります。もし私たちが第二のオペランドの実際の値を観察できていたなら、それは 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 の場合、欠損値は伝播しないという違いがあります。例えば、最初のオペランドがその場合のとき:

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

制御フロー演算子には、ifwhile、および ternary operator x ? y : z が含まれますが、これらは欠損値を許可しません。これは、実際の値が truefalse かを観察できた場合に不確実性があるためです。これは、プログラムがどのように動作すべきかがわからないことを意味します。この場合、TypeError が、コンテキスト内で missing 値が遭遇されるとすぐにスローされます。

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} と、エントリのタイプ(つまり、それが 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
Note

undefsimilarを使用すると、現在はmissingで満たされた配列が得られるかもしれませんが、これはそのような配列を取得する正しい方法ではありません。上記のようにmissingコンストラクタを使用してください。

missing エントリを許可する要素タイプの配列(例: Vector{Union{Missing, T}})は、missing エントリを含まない場合、missing エントリを許可しない配列タイプ(例: Vector{T})に変換できます。変換には convert を使用します。配列に 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 値を含む配列に対して呼び出されると 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 を返します。実際には、比較される配列のすべての非欠損値が等しい場合でも、1つまたは両方の配列に欠損値が含まれている(異なる位置にある可能性がある)場合に 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

関数 anyall も三値論理のルールに従います。したがって、結果が決定できない場合は missing を返します:

julia> all([true, missing])
missing

julia> all([false, missing])
false

julia> any([true, missing])
true

julia> any([false, missing])
missing