Документация Engee

Отсутствующие значения

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

Использование 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

Пропуск отсутствующих значений

Так как отсутствующие (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