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
, могут быть изменены для этого, если обернуть их в функцию passmissing
, предоставляемую пакетом Missings.jl. Например, 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
отсутствующим, используйте 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
Logical operators
Логические (или булевы) операторы |
, &
и xor
являются особым случаем, поскольку они распространяют значения missing
только тогда, когда это логически необходимо. Для этих операторов, зависит от конкретной операции, будет ли результат неопределенным или нет. Это соответствует хорошо установленным правилам three-valued logic, которые реализованы, например, в NULL
в SQL и NA
в R. Эта абстрактная дефиниция соответствует относительно естественному поведению, которое лучше всего объясняется через конкретные примеры.
Давайте проиллюстрируем этот принцип с помощью логического оператора "или" |
. Следуя правилам булевой логики, если один из операндов равен true
, значение другого операнда не влияет на результат, который всегда будет true
:
julia> true | true
true
julia> true | false
true
julia> false | true
true
На основе этого наблюдения мы можем заключить, что если один из операндов равен true
, а другой отсутствует, мы знаем, что результат будет true
, несмотря на неопределенность относительно фактического значения одного из операндов. Если бы мы смогли наблюдать фактическое значение второго операнда, оно могло бы быть только true
или false
, и в обоих случаях результат был бы true
. Следовательно, в этом конкретном случае отсутствие значения не распространяется:
julia> true | missing
true
julia> missing | true
true
Напротив, если один из операндов равен false
, результат может быть либо true
, либо false
в зависимости от значения другого операнда. Поэтому, если этот операнд отсутствует, результат также должен быть 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
, так же как и другие унарные операторы.
Control Flow and Short-Circuiting Operators
Операторы управления потоком, включая if
, while
и ternary operator, не допускают отсутствующих значений. Это связано с неопределенностью относительно того, будет ли фактическое значение true
или false
, если мы сможем его наблюдать. Это подразумевает, что мы не знаем, как программа должна себя вести. В этом случае 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}
, содержащему фактические значения, в сочетании с 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
записей, может быть преобразован в массив, который не допускает 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
на массиве, могут индексироваться с использованием индексов из родительского массива. Индексы, соответствующие отсутствующим значениям, не являются допустимыми для этих объектов, и при попытке их использования возникает ошибка (они также пропускаются функциями 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
Logical Operations on Arrays
Логика с тремя значениями, описанная выше для логических операторов, также используется логическими функциями, применяемыми к массивам. Таким образом, тесты на равенство массивов с использованием оператора ==
возвращают 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