Julia NaN check for NamedTuple

How to check if NaN is in a given NamedTuple?

For example,

julia> myarray = [0, 1, 2, NaN]
4-element Vector{Float64}:
   0.0
   1.0
   2.0
 NaN

julia> isnan.(myarray)
4-element BitVector:
 0
 0
 0
 1

julia> mynt = (; a = 0, b = (; b1=1, b2=2), c=NaN)
(a = 0, b = (b1 = 1, b2 = 2), c = NaN)

julia> isnan.(mynt)
ERROR: ArgumentError: broadcasting over dictionaries and `NamedTuple`s is reserved
Stacktrace:
 [1] broadcastable(#unused#::NamedTuple{(:a, :b, :c), Tuple{Int64, NamedTuple{(:b1, :b2), Tuple{Int64, Int64}}, Float64}})
   @ Base.Broadcast ./broadcast.jl:718
 [2] broadcasted(::Function, ::NamedTuple{(:a, :b, :c), Tuple{Int64, NamedTuple{(:b1, :b2), Tuple{Int64, Int64}}, Float64}})
   @ Base.Broadcast ./broadcast.jl:1309
 [3] top-level scope
   @ REPL[15]:1
 [4] top-level scope
   @ ~/.julia/packages/Infiltrator/LtFao/src/Infiltrator.jl:726

if you do not need to search more than one level of nesting

function findnan(x::NamedTuple)
   isnan.(Iterators.flatten(values(x)))
end


nt0 = (; a = 0, b = (; b1=1, b2=2), c=5)
nt1 = (; a = 0, b = (; b1=1, b2=2), c=NaN)
nt2 = (; a = 0, b = (; b1=1, b2=NaN), c=3)
nt3 = (; a = 0, b = (; b1=1, b2=NaN), c=NaN)
julia> findnan(nt0), any(findnan(nt0))
(Bool[0, 0, 0, 0], false)

julia> findnan(nt1), any(findnan(nt3))
(Bool[0, 0, 0, 1], true)

julia> findnan(nt2), any(findnan(nt3))
(Bool[0, 0, 1, 0], true)

julia> findnan(nt3), any(findnan(nt3))
(Bool[0, 0, 1, 1], true)
1 Like

Ah, thank you! Didn’t think of flatten.

Actually, nesting is required in my case. I’ll figure it out and please let me know if you know how to extend this for nesting!

Much more efficient than Iterators.flatten, more generic, and supports arbitrary nesting:

julia> using AccessorsExtra

# get all numbers from anywhere in the object:
julia> getall(mynt, RecursiveOfType(Number))
(0, 1, 2, NaN)

# check that there are nans among them:
julia> any(isnan, getall(mynt, RecursiveOfType(Number)))
true
2 Likes
julia> isnan_rec(n::NamedTuple) = any(isnan_rec, n)
isnan_rec (generic function with 1 method)

julia> isnan_rec(n::Number) = isnan(n)
isnan_rec (generic function with 2 methods)

julia> isnan_rec((; a = 1, b = (; c = 2, d = (; e = NaN))))
true

julia> isnan_rec((; a = 1, b = (; c = 2, d = (; e = 4.0))))
false

Edit: this one doesn’t allocate (not sure why any does, probably doesn’t unroll automatically even though I would have assumed that for short NamedTuples)

@generated function isnan_rec(n::NamedTuple)
    quote
        Base.Cartesian.@nany $(fieldcount(n)) i -> isnan_rec(n[i])
    end
end
3 Likes