Missing Values
Julia proporciona soporte para representar valores faltantes en el sentido estadístico. Esto es para situaciones en las que no hay un valor disponible para una variable en una observación, pero teóricamente existe un valor válido. Los valores faltantes se representan a través del objeto missing, que es la instancia singleton del tipo Missing. missing es equivalente a NULL in SQL y NA in R, y se comporta como ellos en la mayoría de las situaciones.
Propagation of Missing Values
Los valores missing se propagan automáticamente cuando se pasan a operadores y funciones matemáticas estándar. Para estas funciones, la incertidumbre sobre el valor de uno de los operandos induce incertidumbre sobre el resultado. En la práctica, esto significa que una operación matemática que involucra un valor missing generalmente devuelve missing:
julia> missing + 1
missing
julia> "a" * missing
missing
julia> abs(missing)
missingDado que missing es un objeto normal de Julia, esta regla de propagación solo funciona para funciones que han optado por implementar este comportamiento. Esto se puede lograr mediante:
- agregando un método específico definido para argumentos de tipo
Missing, - aceptando argumentos de este tipo y pasándolos a funciones que los propagan (como los operadores matemáticos estándar).
Los paquetes deben considerar si tiene sentido propagar valores faltantes al definir nuevas funciones y definir los métodos apropiadamente si este es el caso. Pasar un valor missing a una función que no tiene un método que acepte argumentos del tipo Missing genera un MethodError, al igual que para cualquier otro tipo.
Las funciones que no propagan valores missing pueden hacerse para que lo hagan envolviéndolas en la función passmissing proporcionada por el paquete Missings.jl. Por ejemplo, f(x) se convierte en passmissing(f)(x).
Equality and Comparison Operators
Los operadores de igualdad y comparación estándar siguen la regla de propagación presentada anteriormente: si alguno de los operandos está faltante, el resultado es faltante. Aquí hay algunos ejemplos:
julia> missing == 1
missing
julia> missing == missing
missing
julia> missing < 1
missing
julia> 2 >= missing
missingEn particular, ten en cuenta que missing == missing devuelve missing, por lo que == no se puede usar para probar si un valor está ausente. Para probar si x está missing, utiliza ismissing(x).
Los operadores de comparación especiales isequal y === son excepciones a la regla de propagación. Siempre devolverán un valor Bool, incluso en presencia de valores missing, considerando missing como igual a missing y diferente de cualquier otro valor. Por lo tanto, se pueden utilizar para probar si un valor es missing:
julia> missing === 1
false
julia> isequal(missing, 1)
false
julia> missing === missing
true
julia> isequal(missing, missing)
trueEl operador isless es otra excepción: missing se considera mayor que cualquier otro valor. Este operador es utilizado por sort!, que por lo tanto coloca los valores missing después de todos los demás valores:
julia> isless(1, missing)
true
julia> isless(missing, Inf)
false
julia> isless(missing, missing)
falseLogical operators
Los operadores lógicos (o booleanos) |, & y xor son otro caso especial ya que solo propagan valores missing cuando es lógicamente necesario. Para estos operadores, si el resultado es incierto o no, depende de la operación particular. Esto sigue las reglas bien establecidas de three-valued logic, que están implementadas por ejemplo en NULL en SQL y NA en R. Esta definición abstracta corresponde a un comportamiento relativamente natural que se explica mejor a través de ejemplos concretos.
Ilustremos este principio con el operador lógico "o" |. Siguiendo las reglas de la lógica booleana, si uno de los operandos es true, el valor del otro operando no tiene influencia en el resultado, que siempre será true:
julia> true | true
true
julia> true | false
true
julia> false | true
trueBasado en esta observación, podemos concluir que si uno de los operandos es true y el otro es missing, sabemos que el resultado es true a pesar de la incertidumbre sobre el valor real de uno de los operandos. Si hubiéramos podido observar el valor real del segundo operando, solo podría ser true o false, y en ambos casos el resultado sería true. Por lo tanto, en este caso particular, la falta de datos no se propaga:
julia> true | missing
true
julia> missing | true
truePor el contrario, si uno de los operandos es false, el resultado podría ser true o false dependiendo del valor del otro operando. Por lo tanto, si ese operando está missing, el resultado también tiene que ser missing:
julia> false | true
true
julia> true | false
true
julia> false | false
false
julia> false | missing
missing
julia> missing | false
missingEl comportamiento del operador lógico "y" & es similar al del operador |, con la diferencia de que la falta de datos no se propaga cuando uno de los operandos es false. Por ejemplo, cuando ese es el caso del primer operando:
julia> false & false
false
julia> false & true
false
julia> false & missing
falsePor otro lado, la falta de datos se propaga cuando uno de los operandos es true, por ejemplo, el primero:
julia> true & true
true
julia> true & false
false
julia> true & missing
missingFinalmente, el operador lógico "o exclusivo" xor siempre propaga valores missing, ya que ambos operandos siempre tienen un efecto en el resultado. También ten en cuenta que el operador de negación ! devuelve missing cuando el operando es missing, al igual que otros operadores unarios.
Control Flow and Short-Circuiting Operators
Los operadores de control de flujo, incluyendo if, while y el ternary operator x ? y : z, no permiten valores faltantes. Esto se debe a la incertidumbre sobre si el valor real sería true o false si pudiéramos observarlo. Esto implica que no sabemos cómo debería comportarse el programa. En este caso, se lanza un TypeError tan pronto como se encuentra un valor missing en este contexto:
julia> if missing
println("here")
end
ERROR: TypeError: non-boolean (Missing) used in boolean contextPor la misma razón, a diferencia de los operadores lógicos presentados anteriormente, los operadores booleanos de cortocircuito && y || no permiten valores missing en situaciones donde el valor del operando determina si el siguiente operando se evalúa o no. Por ejemplo:
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 contextEn contraste, no se lanza ningún error cuando el resultado se puede determinar sin los valores missing. Este es el caso cuando el código se interrumpe antes de evaluar el operando missing, y cuando el operando missing es el último:
julia> true && missing
missing
julia> false && missing
falseArrays With Missing Values
Los arreglos que contienen valores faltantes se pueden crear como otros arreglos:
julia> [1, missing]
2-element Vector{Union{Missing, Int64}}:
1
missingComo muestra este ejemplo, el tipo de elemento de tales arreglos es Union{Missing, T}, siendo T el tipo de los valores no faltantes. Esto refleja el hecho de que las entradas del arreglo pueden ser de tipo T (aquí, Int64) o de tipo Missing. Este tipo de arreglo utiliza un almacenamiento de memoria eficiente equivalente a un Array{T} que contiene los valores reales combinado con un Array{UInt8} que indica el tipo de la entrada (es decir, si es Missing o T).
Los arreglos que permiten valores faltantes se pueden construir con la sintaxis estándar. Usa Array{Union{Missing, T}}(missing, dims) para crear arreglos llenos de valores faltantes:
julia> Array{Union{Missing, String}}(missing, 2, 3)
2×3 Matrix{Union{Missing, String}}:
missing missing missing
missing missing missingUsar undef o similar puede actualmente dar un array lleno de missing, pero esta no es la forma correcta de obtener tal array. Usa un constructor de missing como se mostró arriba en su lugar.
Un arreglo con un tipo de elemento que permite entradas missing (por ejemplo, Vector{Union{Missing, T}}) que no contiene ninguna entrada missing se puede convertir a un tipo de arreglo que no permite entradas missing (por ejemplo, Vector{T}) utilizando convert. Si el arreglo contiene valores missing, se lanza un MethodError durante la conversión:
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 StringSkipping Missing Values
Dado que los valores missing se propagan con los operadores matemáticos estándar, las funciones de reducción devuelven missing cuando se llaman en arreglos que contienen valores faltantes:
julia> sum([1, missing])
missingEn esta situación, utiliza la función skipmissing para omitir valores faltantes:
julia> sum(skipmissing([1, missing]))
1Esta función de conveniencia devuelve un iterador que filtra eficientemente los valores missing. Por lo tanto, se puede utilizar con cualquier función que soporte iteradores:
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.146264369941973Los objetos creados al llamar a skipmissing en un arreglo se pueden indexar utilizando índices del arreglo padre. Los índices que corresponden a valores faltantes no son válidos para estos objetos, y se lanza un error al intentar usarlos (también se omiten en keys y eachindex):
julia> x[1]
3
julia> x[2]
ERROR: MissingException: the value at index (2,) is missing
[...]Esto permite que las funciones que operan sobre índices funcionen en combinación con skipmissing. Este es el caso notable de las funciones de búsqueda y localización. Estas funciones devuelven índices válidos para el objeto devuelto por skipmissing, y también son los índices de las entradas coincidentes en el array padre:
julia> findall(==(1), x)
1-element Vector{Int64}:
4
julia> findfirst(!iszero, x)
1
julia> argmax(x)
1Utiliza collect para extraer valores no faltantes y almacenarlos en un arreglo:
julia> collect(x)
3-element Vector{Int64}:
3
2
1Logical Operations on Arrays
La lógica de tres valores descrita anteriormente para los operadores lógicos también se utiliza en funciones lógicas aplicadas a arreglos. Así, las pruebas de igualdad de arreglos utilizando el operador == devuelven missing siempre que el resultado no se pueda determinar sin conocer el valor real de la entrada missing. En la práctica, esto significa que se devuelve missing si todos los valores no faltantes de los arreglos comparados son iguales, pero uno o ambos arreglos contienen valores faltantes (posiblemente en diferentes posiciones):
julia> [1, missing] == [2, missing]
false
julia> [1, missing] == [1, missing]
missing
julia> [1, 2, missing] == [1, missing, 2]
missingEn cuanto a los valores individuales, utiliza isequal para tratar los valores missing como iguales a otros valores missing, pero diferentes de los valores no missing:
julia> isequal([1, missing], [1, missing])
true
julia> isequal([1, 2, missing], [1, missing, 2])
falseLas funciones any y all también siguen las reglas de la lógica de tres valores. Por lo tanto, devuelven missing cuando el resultado no puede ser determinado:
julia> all([true, missing])
missing
julia> all([false, missing])
false
julia> any([true, missing])
true
julia> any([false, missing])
missing