Missing Values
Julia fournit un support pour représenter les valeurs manquantes dans un sens statistique. Cela concerne les situations où aucune valeur n'est disponible pour une variable dans une observation, mais qu'une valeur valide existe théoriquement. Les valeurs manquantes sont représentées via l'objet missing
, qui est l'instance singleton du type Missing
. missing
est équivalent à NULL
in SQL et NA
in R, et se comporte comme eux dans la plupart des situations.
Propagation of Missing Values
Les valeurs missing
se propagent automatiquement lorsqu'elles sont passées à des opérateurs et fonctions mathématiques standard. Pour ces fonctions, l'incertitude concernant la valeur de l'un des opérandes induit une incertitude sur le résultat. En pratique, cela signifie qu'une opération mathématique impliquant une valeur missing
renvoie généralement missing
:
julia> missing + 1
missing
julia> "a" * missing
missing
julia> abs(missing)
missing
Puisque missing
est un objet Julia normal, cette règle de propagation ne fonctionne que pour les fonctions qui ont choisi de mettre en œuvre ce comportement. Cela peut être réalisé en :
- ajout d'une méthode spécifique définie pour les arguments de type
Missing
, - acceptant des arguments de ce type, et les passant à des fonctions qui les propagent (comme les opérateurs mathématiques standard).
Les packages devraient considérer s'il est logique de propager les valeurs manquantes lors de la définition de nouvelles fonctions, et définir les méthodes de manière appropriée si tel est le cas. Passer une valeur missing
à une fonction qui n'a pas de méthode acceptant des arguments de type Missing
génère une MethodError
, tout comme pour tout autre type.
Les fonctions qui ne propagent pas les valeurs manquantes
peuvent être modifiées pour le faire en les enveloppant dans la fonction passmissing
fournie par le package Missings.jl. Par exemple, f(x)
devient passmissing(f)(x)
.
Equality and Comparison Operators
Les opérateurs d'égalité et de comparaison standard suivent la règle de propagation présentée ci-dessus : si l'un des opérandes est manquant
, le résultat est manquant
. Voici quelques exemples :
julia> missing == 1
missing
julia> missing == missing
missing
julia> missing < 1
missing
julia> 2 >= missing
missing
En particulier, notez que missing == missing
renvoie missing
, donc ==
ne peut pas être utilisé pour tester si une valeur est manquante. Pour tester si x
est missing
, utilisez ismissing(x)
.
Les opérateurs de comparaison spéciaux isequal
et ===
sont des exceptions à la règle de propagation. Ils renverront toujours une valeur Bool
, même en présence de valeurs missing
, considérant missing
comme égal à missing
et différent de toute autre valeur. Ils peuvent donc être utilisés pour tester si une valeur est missing
:
julia> missing === 1
false
julia> isequal(missing, 1)
false
julia> missing === missing
true
julia> isequal(missing, missing)
true
L'opérateur isless
est une autre exception : missing
est considéré comme supérieur à toute autre valeur. Cet opérateur est utilisé par sort!
, qui place donc les valeurs missing
après toutes les autres valeurs :
julia> isless(1, missing)
true
julia> isless(missing, Inf)
false
julia> isless(missing, missing)
false
Logical operators
Les opérateurs logiques (ou booléens) |
, &
et xor
sont un cas spécial car ils ne propagent des valeurs manquantes
que lorsque cela est logiquement nécessaire. Pour ces opérateurs, que le résultat soit incertain ou non dépend de l'opération particulière. Cela suit les règles bien établies de three-valued logic, qui sont mises en œuvre par exemple par NULL
en SQL et NA
en R. Cette définition abstraite correspond à un comportement relativement naturel qui est mieux expliqué par des exemples concrets.
Illustrons ce principe avec l'opérateur logique "ou" |
. Selon les règles de la logique booléenne, si l'un des opérandes est vrai
, la valeur de l'autre opérande n'a pas d'influence sur le résultat, qui sera toujours vrai
:
julia> true | true
true
julia> true | false
true
julia> false | true
true
Sur la base de cette observation, nous pouvons conclure que si l'un des opérandes est true
et l'autre missing
, nous savons que le résultat est true
malgré l'incertitude concernant la valeur réelle de l'un des opérandes. Si nous avions pu observer la valeur réelle du deuxième opérande, elle ne pourrait être que true
ou false
, et dans les deux cas, le résultat serait true
. Par conséquent, dans ce cas particulier, l'absence ne se propage pas :
julia> true | missing
true
julia> missing | true
true
Au contraire, si l'un des opérandes est false
, le résultat peut être soit true
soit false
en fonction de la valeur de l'autre opérande. Par conséquent, si cet opérande est missing
, le résultat doit également être missing
:
julia> false | true
true
julia> true | false
true
julia> false | false
false
julia> false | missing
missing
julia> missing | false
missing
Le comportement de l'opérateur logique "et" &
est similaire à celui de l'opérateur |
, avec la différence que l'absence ne se propage pas lorsque l'un des opérandes est false
. Par exemple, lorsque c'est le cas du premier opérande :
julia> false & false
false
julia> false & true
false
julia> false & missing
false
D'autre part, l'absence se propage lorsque l'un des opérandes est true
, par exemple le premier :
julia> true & true
true
julia> true & false
false
julia> true & missing
missing
Enfin, l'opérateur logique "ou exclusif" xor
propage toujours les valeurs manquantes
, puisque les deux opérandes ont toujours un effet sur le résultat. Notez également que l'opérateur de négation !
renvoie manquant
lorsque l'opérande est manquant
, tout comme d'autres opérateurs unaires.
Control Flow and Short-Circuiting Operators
Les opérateurs de contrôle de flux, y compris if
, while
et le ternary operator, x ? y : z
, ne permettent pas de valeurs manquantes. Cela est dû à l'incertitude quant à savoir si la valeur réelle serait true
ou false
si nous pouvions l'observer. Cela implique que nous ne savons pas comment le programme devrait se comporter. Dans ce cas, un TypeError
est lancé dès qu'une valeur missing
est rencontrée dans ce contexte :
julia> if missing
println("here")
end
ERROR: TypeError: non-boolean (Missing) used in boolean context
Pour la même raison, contrairement aux opérateurs logiques présentés ci-dessus, les opérateurs booléens à court-circuit &&
et ||
ne permettent pas de valeurs manquantes
dans des situations où la valeur de l'opérande détermine si l'opérande suivant est évalué ou non. Par exemple :
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
En revanche, aucune erreur n'est générée lorsque le résultat peut être déterminé sans les valeurs missing
. C'est le cas lorsque le code s'arrête avant d'évaluer l'opérande missing
, et lorsque l'opérande missing
est le dernier :
julia> true && missing
missing
julia> false && missing
false
Arrays With Missing Values
Les tableaux contenant des valeurs manquantes peuvent être créés comme d'autres tableaux :
julia> [1, missing]
2-element Vector{Union{Missing, Int64}}:
1
missing
Comme cet exemple le montre, le type d'élément de tels tableaux est Union{Missing, T}
, avec T
le type des valeurs non manquantes. Cela reflète le fait que les entrées du tableau peuvent être soit de type T
(ici, Int64
), soit de type Missing
. Ce type de tableau utilise un stockage mémoire efficace équivalent à un Array{T}
contenant les valeurs réelles combiné avec un Array{UInt8}
indiquant le type de l'entrée (c'est-à-dire si elle est Missing
ou T
).
Les tableaux permettant des valeurs manquantes peuvent être construits avec la syntaxe standard. Utilisez Array{Union{Missing, T}}(missing, dims)
pour créer des tableaux remplis de valeurs manquantes :
julia> Array{Union{Missing, String}}(missing, 2, 3)
2×3 Matrix{Union{Missing, String}}:
missing missing missing
missing missing missing
Utiliser undef
ou similaire
peut actuellement donner un tableau rempli de manquant
, mais ce n'est pas la bonne façon d'obtenir un tel tableau. Utilisez un constructeur manquant
comme indiqué ci-dessus à la place.
Un tableau avec un type d'élément permettant des entrées missing
(par exemple, Vector{Union{Missing, T}}
) qui ne contient aucune entrée missing
peut être converti en un type de tableau qui n'autorise pas les entrées missing
(par exemple, Vector{T}
) en utilisant convert
. Si le tableau contient des valeurs missing
, une MethodError
est levée lors de la conversion :
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
Puisque les valeurs manquantes
se propagent avec les opérateurs mathématiques standard, les fonctions de réduction renvoient manquant
lorsqu'elles sont appelées sur des tableaux contenant des valeurs manquantes :
julia> sum([1, missing])
missing
Dans cette situation, utilisez la fonction skipmissing
pour ignorer les valeurs manquantes :
julia> sum(skipmissing([1, missing]))
1
Cette fonction utilitaire renvoie un itérateur qui filtre efficacement les valeurs manquantes
. Elle peut donc être utilisée avec toute fonction qui prend en charge les itérateurs :
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
Les objets créés en appelant skipmissing
sur un tableau peuvent être indexés en utilisant des indices du tableau parent. Les indices correspondant à des valeurs manquantes ne sont pas valides pour ces objets, et une erreur est générée lorsqu'on essaie de les utiliser (ils sont également ignorés par keys
et eachindex
) :
julia> x[1]
3
julia> x[2]
ERROR: MissingException: the value at index (2,) is missing
[...]
Cela permet aux fonctions qui opèrent sur des indices de fonctionner en combinaison avec skipmissing
. C'est notamment le cas pour les fonctions de recherche et de localisation. Ces fonctions renvoient des indices valides pour l'objet retourné par skipmissing
, et ce sont également les indices des entrées correspondantes dans le tableau parent :
julia> findall(==(1), x)
1-element Vector{Int64}:
4
julia> findfirst(!iszero, x)
1
julia> argmax(x)
1
Utilisez collect
pour extraire les valeurs non missing
et les stocker dans un tableau :
julia> collect(x)
3-element Vector{Int64}:
3
2
1
Logical Operations on Arrays
La logique à trois valeurs décrite ci-dessus pour les opérateurs logiques est également utilisée par les fonctions logiques appliquées aux tableaux. Ainsi, les tests d'égalité des tableaux utilisant l'opérateur ==
renvoient missing
chaque fois que le résultat ne peut pas être déterminé sans connaître la valeur réelle de l'entrée missing
. En pratique, cela signifie que missing
est renvoyé si toutes les valeurs non manquantes des tableaux comparés sont égales, mais qu'un ou les deux tableaux contiennent des valeurs manquantes (possiblement à des positions différentes) :
julia> [1, missing] == [2, missing]
false
julia> [1, missing] == [1, missing]
missing
julia> [1, 2, missing] == [1, missing, 2]
missing
En ce qui concerne les valeurs uniques, utilisez isequal
pour traiter les valeurs manquantes
comme égales à d'autres valeurs manquantes
, mais différentes des valeurs non manquantes :
julia> isequal([1, missing], [1, missing])
true
julia> isequal([1, 2, missing], [1, missing, 2])
false
Les fonctions any
et all
suivent également les règles de la logique à trois valeurs. Ainsi, elles retournent missing
lorsque le résultat ne peut pas être déterminé :
julia> all([true, missing])
missing
julia> all([false, missing])
false
julia> any([true, missing])
true
julia> any([false, missing])
missing