Skip to content

Commit e200a72

Browse files
oschulzandreasnoack
authored andcommitted
Fixes for type instabilities in histogram functions (#253)
* Add type stability tests for histogram functions * Fix type instabilities in histogram functions * Remove unused isdensity arguments from fit(::Histogram...) isdensity arguments don't belong there in the first place. * Add more elegant Julia v0.6 implementation in _nbins_tuple. Suggested by @nalimilan.
1 parent 38c62a1 commit e200a72

File tree

2 files changed

+63
-31
lines changed

2 files changed

+63
-31
lines changed

src/hist.jl

Lines changed: 41 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,14 @@ end
2727
end
2828

2929

30+
# Need a generated function to promote edge types, because a simple
31+
# promote_type(map(eltype, h.edges)...) isn't type stable (tested
32+
# with Julia v0.5).
33+
@generated function _promote_edge_types{N}(edges::NTuple{N,AbstractVector})
34+
promote_type(map(eltype, edges.parameters)...)
35+
end
36+
37+
3038
## nice-valued ranges for histograms
3139
function histrange{T}(v::AbstractArray{T}, n::Integer, closed::Symbol=:default_left)
3240
closed = _check_closed_arg(closed,:histrange)
@@ -134,7 +142,7 @@ type Histogram{T<:Real,N,E} <: AbstractHistogram{T,N,E}
134142
weights::Array{T,N}, closed::Symbol, isdensity::Bool=false)
135143
closed == :right || closed == :left || error("closed must :left or :right")
136144
isdensity && !(T <: AbstractFloat) && error("Density histogram must have float-type weights")
137-
map(x -> length(x)-1,edges) == size(weights) || error("Histogram edge vectors must be 1 longer than corresponding weight dimensions")
145+
_edges_nbins(edges) == size(weights) || error("Histogram edge vectors must be 1 longer than corresponding weight dimensions")
138146
new{T,N,E}(edges,weights,closed,isdensity)
139147
end
140148
end
@@ -143,7 +151,7 @@ Histogram{T,N}(edges::NTuple{N,AbstractVector},weights::AbstractArray{T,N},close
143151
Histogram{T,N,typeof(edges)}(edges,weights,_check_closed_arg(closed,:Histogram),isdensity)
144152

145153
Histogram{T,N}(edges::NTuple{N,AbstractVector},::Type{T},closed::Symbol=:default_left, isdensity::Bool=false) =
146-
Histogram(edges,zeros(T,map(x -> length(x)-1,edges)...),_check_closed_arg(closed,:Histogram),isdensity)
154+
Histogram(edges,zeros(T,_edges_nbins(edges)...),_check_closed_arg(closed,:Histogram),isdensity)
147155

148156
Histogram{N}(edges::NTuple{N,AbstractVector},closed::Symbol=:default_left, isdensity::Bool=false) =
149157
Histogram(edges,Int,_check_closed_arg(closed,:Histogram),isdensity)
@@ -180,7 +188,7 @@ binvolume{T,E}(h::AbstractHistogram{T,1,E}, binidx::Integer) = binvolume(h, (bin
180188
binvolume{V,T,E}(::Type{V}, h::AbstractHistogram{T,1,E}, binidx::Integer) = binvolume(V, h, (binidx,))
181189

182190
binvolume{T,N,E}(h::Histogram{T,N,E}, binidx::NTuple{N,Integer}) =
183-
binvolume(promote_type(map(eltype, h.edges)...), h, binidx)
191+
binvolume(_promote_edge_types(h.edges), h, binidx)
184192

185193
binvolume{V,T,N,E}(::Type{V}, h::Histogram{T,N,E}, binidx::NTuple{N,Integer}) =
186194
prod(map((edge, i) -> _edge_binvolume(V, edge, i), h.edges, binidx))
@@ -190,6 +198,11 @@ binvolume{V,T,N,E}(::Type{V}, h::Histogram{T,N,E}, binidx::NTuple{N,Integer}) =
190198
@inline _edge_binvolume(edge::AbstractVector, i::Integer) = _edge_binvolume(eltype(edge), edge, i)
191199

192200

201+
@inline _edges_nbins{N}(edges::NTuple{N,AbstractVector}) = map(_edge_nbins, edges)
202+
203+
@inline _edge_nbins(edge::AbstractVector) = length(edge) - 1
204+
205+
193206
# 1-dimensional
194207

195208
Histogram{T}(edge::AbstractVector, weights::AbstractVector{T}, closed::Symbol=:default_left, isdensity::Bool=false) =
@@ -259,34 +272,48 @@ end
259272
append!{T,N}(h::AbstractHistogram{T,N}, vs::NTuple{N,AbstractVector}, wv::WeightVec) = append!(h, vs, values(wv))
260273

261274

275+
# Turn kwargs nbins into a type-stable tuple of integers:
276+
function _nbins_tuple{N}(vs::NTuple{N,AbstractVector}, nbins)
277+
template = map(length, vs)
278+
279+
@static if VERSION < v"0.6.0-dev.695"
280+
result = if isa(nbins, Integer)
281+
map(t -> typeof(t)(nbins), template)
282+
elseif isa(nbins, NTuple{N, Integer})
283+
map((t, x) -> typeof(t)(x), template, nbins)
284+
else
285+
throw(ArgumentError("nbins must be an Integer or NTuple{N, Integer}"))
286+
end
287+
else
288+
result = broadcast((t, x) -> typeof(t)(x), template, nbins)
289+
end
290+
291+
result::typeof(template)
292+
end
293+
262294
fit{T,N}(::Type{Histogram{T}}, vs::NTuple{N,AbstractVector}, edges::NTuple{N,AbstractVector}; closed::Symbol=:default_left) =
263295
append!(Histogram(edges, T, _check_closed_arg(closed,:fit), false), vs)
264296

265-
fit{T,N}(::Type{Histogram{T}}, vs::NTuple{N,AbstractVector}; closed::Symbol=:default_left, isdensity::Bool=false, nbins=sturges(length(vs[1]))) = begin
297+
fit{T,N}(::Type{Histogram{T}}, vs::NTuple{N,AbstractVector}; closed::Symbol=:default_left, nbins=sturges(length(vs[1]))) = begin
266298
closed = _check_closed_arg(closed,:fit)
267-
fit(Histogram{T}, vs, histrange(vs,nbins,closed); closed=closed)
299+
fit(Histogram{T}, vs, histrange(vs,_nbins_tuple(vs, nbins),closed); closed=closed)
268300
end
269301

270302
fit{T,N,W}(::Type{Histogram{T}}, vs::NTuple{N,AbstractVector}, wv::WeightVec{W}, edges::NTuple{N,AbstractVector}; closed::Symbol=:default_left) =
271303
append!(Histogram(edges, T, _check_closed_arg(closed,:fit), false), vs, wv)
272304

273-
fit{T,N}(::Type{Histogram{T}}, vs::NTuple{N,AbstractVector}, wv::WeightVec; closed::Symbol=:default_left, isdensity::Bool=false, nbins=sturges(length(vs[1]))) = begin
305+
fit{T,N}(::Type{Histogram{T}}, vs::NTuple{N,AbstractVector}, wv::WeightVec; closed::Symbol=:default_left, nbins=sturges(length(vs[1]))) = begin
274306
closed = _check_closed_arg(closed,:fit)
275-
fit(Histogram{T}, vs, wv, histrange(vs,nbins,closed); closed=closed)
307+
fit(Histogram{T}, vs, wv, histrange(vs,_nbins_tuple(vs, nbins),closed); closed=closed)
276308
end
277309

278310
fit(::Type{Histogram}, args...; kwargs...) = fit(Histogram{Int}, args...; kwargs...)
279311
fit{N,W}(::Type{Histogram}, vs::NTuple{N,AbstractVector}, wv::WeightVec{W}, args...; kwargs...) = fit(Histogram{W}, vs, wv, args...; kwargs...)
280312

281313

282314
# Get a suitable high-precision type for the norm of a histogram.
283-
@generated function norm_type{T, N, E}(h::Histogram{T, N, E})
284-
args = [:( eltype(edges[$d]) ) for d = 1:N]
285-
quote
286-
edges = h.edges
287-
norm_type(promote_type(T, $(args...)))
288-
end
289-
end
315+
norm_type{T, N, E}(h::Histogram{T, N, E}) =
316+
promote_type(T, _promote_edge_types(h.edges))
290317

291318
norm_type{T<:Integer}(::Type{T}) = promote_type(T, Int64)
292319
norm_type{T<:AbstractFloat}(::Type{T}) = promote_type(T, Float64)

test/hist.jl

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13,20 +13,22 @@ using Base.Test
1313
h1 = Histogram(edg1, :left)
1414
h2 = Histogram((edg1, edg2), :left)
1515
h3 = Histogram((edg1f0, edg2), :left)
16-
@test StatsBase.binindex(h1, -0.5) == 4
17-
@test StatsBase.binindex(h2, (1.5, 2)) == (8, 3)
16+
@test @inferred StatsBase.binindex(h1, -0.5) == 4
17+
@test @inferred StatsBase.binindex(h2, (1.5, 2)) == (8, 3)
1818

1919
@test [StatsBase.binvolume(h1, i) for i in indices(h1.weights, 1)] diff(edg1)
2020
@test [StatsBase.binvolume(h2, (i,j)) for i in indices(h2.weights, 1), j in indices(h2.weights, 2)] diff(edg1) * diff(edg2)'
2121

22-
@test typeof(StatsBase.binvolume(h2, (1,1))) == Float64
23-
@test typeof(StatsBase.binvolume(h3, (1,1))) == Float32
24-
@test typeof(StatsBase.binvolume(Float64, h3, (1,1))) == Float64
22+
@test typeof(@inferred(StatsBase.binvolume(h2, (1,1)))) == Float64
23+
@test typeof(@inferred(StatsBase.binvolume(h3, (1,1)))) == Float32
24+
@test typeof(@inferred(StatsBase.binvolume(Float64, h3, (1,1)))) == Float64
2525
end
2626

2727

2828
@testset "Histogram append" begin
2929
# FIXME: closed (all lines in this block):
30+
h = Histogram(0:20:100, Float64, :left, false)
31+
@test @inferred(append!(h, 0:0.5:99.99)) == h
3032
@test append!(Histogram(0:20:100, Float64, :left, false), 0:0.5:99.99).weights [40,40,40,40,40]
3133
@test append!(Histogram(0:20:100, Float64, :left, true), 0:0.5:99.99).weights [2,2,2,2,2]
3234
@test append!(Histogram(0:20:100, Float64, :left, false), 0:0.5:99.99, fill(2, 200)).weights [80,80,80,80,80]
@@ -64,16 +66,16 @@ end
6466

6567
@testset "Histogram element type" begin
6668
# FIXME: closed (all lines in this block):
67-
@test eltype(fit(Histogram,1:100,weights(ones(Int,100)),nbins=5, closed=:left).weights) == Int
68-
@test eltype(fit(Histogram{Float32},1:100,weights(ones(Int,100)),nbins=5, closed=:left).weights) == Float32
69-
@test eltype(fit(Histogram,1:100,weights(ones(Float64,100)),nbins=5, closed=:left).weights) == Float64
70-
@test eltype(fit(Histogram{Float32},1:100,weights(ones(Float64,100)),nbins=5, closed=:left).weights) == Float32
69+
@test eltype(@inferred(fit(Histogram,1:100,weights(ones(Int,100)),nbins=5, closed=:left)).weights) == Int
70+
@test eltype(@inferred(fit(Histogram{Float32},1:100,weights(ones(Int,100)),nbins=5, closed=:left)).weights) == Float32
71+
@test eltype(@inferred(fit(Histogram,1:100,weights(ones(Float64,100)),nbins=5, closed=:left)).weights) == Float64
72+
@test eltype(@inferred(fit(Histogram{Float32},1:100,weights(ones(Float64,100)),nbins=5, closed=:left)).weights) == Float32
7173
end
7274

7375

7476
@testset "histrange" begin
7577
# Note: atm histrange must be qualified
76-
@test StatsBase.histrange(Float64[], 0, :left) == 0.0:1.0:0.0
78+
@test @inferred(StatsBase.histrange(Float64[], 0, :left)) == 0.0:1.0:0.0
7779
@test StatsBase.histrange(Float64[1:5;], 1, :left) == 0.0:5.0:10.0
7880
@test StatsBase.histrange(Float64[1:10;], 1, :left) == 0.0:10.0:20.0
7981
@test StatsBase.histrange(1.0, 10.0, 1, :left) == 0.0:10.0:20.0
@@ -90,7 +92,7 @@ end
9092
@test StatsBase.histrange([200.0,300.0], 10, :left) == 200.0:10.0:310.0
9193
@test StatsBase.histrange([200.0,300.0], 10, :right) == 190.0:10.0:300.0
9294

93-
@test StatsBase.histrange(Int64[1:5;], 1, :left) == 0:5:10
95+
@test @inferred(StatsBase.histrange(Int64[1:5;], 1, :left)) == 0:5:10
9496
@test StatsBase.histrange(Int64[1:10;], 1, :left) == 0:10:20
9597

9698
# FIXME: closed (all lines in this block):
@@ -149,25 +151,28 @@ end
149151

150152
@test norm(h) sum(h.weights .* bin_vols)
151153

152-
@test normalize(h, mode = :none) == h
154+
@test @inferred(normalize(h, mode = :none)) == h
153155

154156

155157
h_pdf = normalize(h, mode = :pdf)
156158
@test h_pdf.weights h.weights ./ bin_vols ./ weight_sum
157159
@test h_pdf.isdensity == true
158-
@test norm(h_pdf) 1
159-
@test normalize(h_pdf, mode = :pdf) == h_pdf
160-
@test normalize(h_pdf, mode = :density) == h_pdf
160+
@test @inferred(norm(h_pdf)) 1
161+
@test @inferred(normalize(h_pdf, mode = :pdf)) == h_pdf
162+
@test @inferred(normalize(h_pdf, mode = :density)) == h_pdf
161163

162164
h_density = normalize(h, mode = :density)
163165
@test h_density.weights h.weights ./ bin_vols
164166
@test h_density.isdensity == true
165-
@test norm(h_density) weight_sum
166-
@test normalize(h_density, mode = :pdf) ==
167+
@test @inferred(norm(h_density)) weight_sum
168+
@test @inferred(normalize(h_density, mode = :pdf)) ==
167169
Histogram(h_density.edges, h_density.weights .* (1/norm(h_density)), h_density.closed, true)
168170
@test normalize(h_density, mode = :pdf).weights h_pdf.weights
169171
@test normalize(h_density, mode = :density) == h_density
170172

173+
h_copy = deepcopy(float(h))
174+
@test @inferred(normalize!(h_copy, mode = :density)) == h_copy
175+
171176
h2 = deepcopy(float(h))
172177
mod_h2 = normalize!(h2, mode = :density)
173178
@test mod_h2 === h2 && mod_h2.weights === h2.weights

0 commit comments

Comments
 (0)