Отсутствующие значения
Julia предоставляет поддержку для представления отсутствующих значений в статистическом смысле. Это предназначено для ситуаций, когда для наблюдаемой переменной недоступно ни одно значение, но допустимое значение теоретически существует. Отсутствующие значения представляются через объект missing
, который является одинарным экземпляром типа Missing
. missing
эквивалентно NULL
в SQL и NA
в R и ведет себя подобно им в большинстве ситуаций.
Распространение отсутствующих значений
Отсутствующие (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)
.
Операторы равенства и сравнения
Стандартные операторы равенства и сравнения следуют представленному выше правилу распространения: если любой из операндов отсутствует (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
и ===
являются исключением из правила распространения. Они всегда будут возвращать значение 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
) значения, только когда это логически требуется. Для этих операторов то, является ли результат неопределенным или нет, зависит от конкретной операции. Это следует из твердо установленных правил трехзначной логики, которые реализуются, например NULL
в SQL и NA
в R. Это абстрактное представление соответствует относительно естественному поведению, которое лучше всего объяснять на конкретных примерах.
Проиллюстрируем этот принцип логическим оператором «или» |
. Следуя правилам булевой логики, если один из операндов имеет значение 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
, если бы мы могли наблюдать это. Это подразумевает, что мы не знаем, как должна вести себя программа. В этом случае выдается 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
Массивы с отсутствующими значениями
Можно создавать массивы, содержащие отсутствующие значения, так же, как и другие массивы:
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
Использование |
Массив с типом элементов, допускающим отсутствующие (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
Пропуск отсутствующих значений
Так как отсутствующие (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
для извлечения не отсутствующих (non-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