Julia 朱莉娅:如何有效地计算`向量{Union{T,Missing}中的丢失数`

Julia 朱莉娅:如何有效地计算`向量{Union{T,Missing}中的丢失数`,julia,Julia,考虑 x = rand([missing, rand(Int, 100)...], 1_000_000) 它产生typeof(x)=数组{Union{Missing,Int64},1} 计算x中的丢失数量最有效的方法是什么 最干净的方法可能就是 count(ismissing, x) 简单易记,速度快 既然您要求的是“最有效”的方法,那么让我给出一些基准测试结果。它比@xiaodai的答案稍微快一点,与简单的循环实现一样快: julia> @btime count($ismissing

考虑

x = rand([missing, rand(Int, 100)...], 1_000_000)
它产生
typeof(x)
=
数组{Union{Missing,Int64},1}


计算
x
中的丢失数量最有效的方法是什么

最干净的方法可能就是

count(ismissing, x)
简单易记,速度快

既然您要求的是“最有效”的方法,那么让我给出一些基准测试结果。它比@xiaodai的答案稍微快一点,与简单的循环实现一样快:

julia> @btime count($ismissing,$x);
  278.499 μs (0 allocations: 0 bytes)

julia> @btime mapreduce($ismissing, $+, $x);
  293.901 μs (0 allocations: 0 bytes)

julia> @btime count_missing($x)
  278.499 μs (0 allocations: 0 bytes)
在哪里


无需任何成本,按照您希望的方式进行抽象。

如果您知道缺少的元素数少于40亿个(或少于65k个),您可以比@crstnbr快几倍,并使用以下代码回答:

function count_missing(x, T)
    c = zero(T)
    for i in 1:length(x)
        c += @inbounds ismissing(x[i])
    end
    return Int(c)  #we want to have stable result type
                   # this could be further combined with a barrier function
                   # that could check the size of `x` at the runtime
end
现在是基准

这是我笔记本电脑上的原始时间:

julia> @btime count_missing($x, Int)
  227.799 μs (0 allocations: 0 bytes) 
9971
如果知道匹配元素少于40亿,则将时间缩短一半:

julia> @btime count_missing($x, UInt32)
  113.899 μs (0 allocations: 0 bytes)
9971
julia> @btime count_missing($x, UInt16)
  29.200 μs (0 allocations: 0 bytes)
9971
如果知道匹配元素少于65k,则将时间缩短8倍:

julia> @btime count_missing($x, UInt32)
  113.899 μs (0 allocations: 0 bytes)
9971
julia> @btime count_missing($x, UInt16)
  29.200 μs (0 allocations: 0 bytes)
9971

这是一个不安全的答案,如果Julia更改内存布局,也不能保证在将来工作,但这很有趣

x = Vector{Union{Missing, Float64}}(missing, 100_000_000)
x[rand(1:100_000_000, 90_000_000)] .= rand.()

using BenchmarkTools

@benchmark count($ismissing, $x)
# BenchmarkTools.Trial:
#   memory estimate:  0 bytes
#   allocs estimate:  0
#   --------------
#   minimum time:     48.468 ms (0.00% GC)
#   median time:      51.755 ms (0.00% GC)
#   mean time:        66.863 ms (0.00% GC)
#   maximum time:     91.449 ms (0.00% GC)
#   --------------
#   samples:          76
#   evals/sample:     1

function unsafe_count_missing(x::Vector{Union{Missing, T}}) where T
    @assert isbitstype(T)
    l = length(x)
    GC.@preserve x begin
        y = unsafe_wrap(Vector{UInt8}, Ptr{UInt8}(pointer(x) + sizeof(T)*l), l)
        res = reduce(-, y; init = l)
    end
    res
end

@time count(ismissing, x) == unsafe_count_missing(x)

@benchmark faster_count_missing($x)
# BenchmarkTools.Trial:
#   memory estimate:  80 bytes
#   allocs estimate:  1
#   --------------
#   minimum time:     9.190 ms (0.00% GC)
#   median time:      9.718 ms (0.00% GC)
#   mean time:        9.845 ms (0.00% GC)
#   maximum time:     15.691 ms (0.00% GC)
#   --------------
#   samples:          508
#   evals/sample:     1


@小戴-在大多数情况下,返回
Int
比返回
T
更自然。特别是终端显示其他值的默认方式—尝试在终端“UInt32(15)”中键入,看看会发生什么。您也可以将其编写为
mapreduce(ismissing,+,x,init=T(0))
,这与
count
在引擎盖下实际调用的速度一样快。我刚刚打开了一个PR,添加了将
init
参数直接传递到
count
的功能:实际上,由于使用小整数类型而导致的加速似乎只发生在计算缺失值的特定情况下,而在计算非缺失值时不会出现。这可能是由于编译器的限制。事实证明,使用上面@crstnbr给出的更通用的方法可以获得相同级别的性能,但要计算非缺失值的数量。出于某种原因,编译器在这种情况下会生成更高效的代码。看见