Skip to content

Commit 3953275

Browse files
committed
Fix histogram: bin on raw data, use Sturges default, delegate xscale to barplot frequency axis, and reject xscale for vertical mode
1 parent 4db7ba1 commit 3953275

1 file changed

Lines changed: 22 additions & 32 deletions

File tree

src/unicode_plot/interface/histogram.cr

Lines changed: 22 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,6 @@ module UnicodePlot
44
(Math.log2(n) + 1).ceil.to_i32
55
end
66

7-
# Scott's rule: bin_width = 3.49 * σ * n^(-1/3).
8-
private def scott_bins(data : Array(Float64)) : Int32
9-
n = data.size
10-
return 2 if n < 2
11-
mean = data.sum / n
12-
std = Math.sqrt(data.sum { |v| (v - mean) ** 2 } / n)
13-
return sturges(n) if std == 0.0
14-
bw = 3.49 * std * (n.to_f ** (-1.0 / 3.0))
15-
bins = ((data.max - data.min) / bw).ceil.to_i32
16-
Math.max(sturges(n), Math.max(2, bins))
17-
end
18-
197
# Round step up to next "nice" value ({1,2,2.5,5} × 10^k), matching Julia's histrange.
208
private def nice_hist_step(step : Float64) : Float64
219
return step if step <= 0.0
@@ -45,7 +33,7 @@ module UnicodePlot
4533
color : Symbol | UInt32 | {Int32, Int32, Int32} = :green,
4634
title : String = "",
4735
xlabel : String = "",
48-
ylabel : String = "",
36+
ylabel : String = "", # currently unused; kept for API compatibility
4937
border : Symbol = :barplot,
5038
margin : Int32 = 3,
5139
padding : Int32 = 1,
@@ -56,41 +44,40 @@ module UnicodePlot
5644
symbols : Array(Char) = ['▏', '▎', '▍', '▌', '▋', '▊', '▉', '█'],
5745
width : Int32? = nil,
5846
) : Plot
47+
if vertical
48+
unless xscale.is_a?(Symbol) && xscale == :identity
49+
raise ArgumentError.new("xscale is not supported for vertical histogram")
50+
end
51+
end
52+
5953
return barplot([] of String, [] of Float64) if data.empty?
6054

61-
scale_fn = xscale.is_a?(Symbol) ? scale_callback(xscale.as(Symbol)) : xscale.as(Proc(Float64, Float64))
62-
scaled_data = data.map { |v| scale_fn.call(v) }.select(&.finite?)
63-
return barplot([] of String, [] of Float64) if scaled_data.empty?
55+
raw_data = data.select(&.finite?)
56+
return barplot([] of String, [] of Float64) if raw_data.empty?
6457

65-
n_bins = histogram_bin_count(scaled_data, nbins, vertical)
66-
dmin, dmax = histogram_data_bounds(scaled_data)
67-
xlab = xscale.is_a?(Symbol) ? transform_name(xscale.as(Symbol), xlabel) : transform_name(scale_fn, xlabel)
58+
n_bins = histogram_bin_count(raw_data, nbins)
59+
dmin, dmax = histogram_data_bounds(raw_data)
6860

6961
if vertical
70-
histogram_vertical_plot(scaled_data, n_bins, dmin, dmax,
71-
stats: stats, xlabel: xlab,
62+
histogram_vertical_plot(raw_data, n_bins, dmin, dmax,
63+
stats: stats, xlabel: xlabel,
7264
color: color, title: title,
7365
unicode_exponent: unicode_exponent,
7466
thousands_separator: thousands_separator,
7567
width: width)
7668
else
77-
histogram_horizontal_plot(scaled_data, n_bins, dmin, dmax,
69+
histogram_horizontal_plot(raw_data, n_bins, dmin, dmax,
7870
closed: closed,
79-
color: color, title: title, xlabel: xlab,
71+
xscale: xscale,
72+
color: color, title: title, xlabel: xlabel,
8073
border: border, margin: margin, padding: padding,
8174
labels: labels, unicode_exponent: unicode_exponent,
8275
thousands_separator: thousands_separator, symbols: symbols, width: width)
8376
end
8477
end
8578

86-
private def histogram_bin_count(data : Array(Float64), nbins : Int32?, vertical : Bool) : Int32
87-
n_bins = if nbins
88-
nbins
89-
elsif vertical
90-
scott_bins(data)
91-
else
92-
Math.min(scott_bins(data), Math.max(sturges(data.size), 30))
93-
end
79+
private def histogram_bin_count(data : Array(Float64), nbins : Int32?) : Int32
80+
n_bins = nbins || sturges(data.size)
9481
Math.max(n_bins, 2)
9582
end
9683

@@ -154,6 +141,7 @@ module UnicodePlot
154141
dmax : Float64,
155142
*,
156143
closed : Symbol,
144+
xscale : Symbol | Proc(Float64, Float64),
157145
color : Symbol | UInt32 | {Int32, Int32, Int32},
158146
title : String,
159147
xlabel : String,
@@ -186,6 +174,7 @@ module UnicodePlot
186174

187175
histogram_horizontal(counts, edge_min, bin_width, n_actual,
188176
closed: closed,
177+
xscale: xscale,
189178
color: color, title: title, xlabel: xlabel,
190179
border: border, margin: margin, padding: padding,
191180
labels: labels, unicode_exponent: unicode_exponent,
@@ -274,7 +263,7 @@ module UnicodePlot
274263

275264
private def histogram_horizontal(
276265
counts : Array(Float64), dmin : Float64, bin_width : Float64, n_bins : Int32,
277-
*, closed : Symbol, color : Symbol | UInt32 | {Int32, Int32, Int32}, title : String, xlabel : String,
266+
*, closed : Symbol, xscale : Symbol | Proc(Float64, Float64), color : Symbol | UInt32 | {Int32, Int32, Int32}, title : String, xlabel : String,
278267
border : Symbol, margin : Int32, padding : Int32, labels : Bool,
279268
unicode_exponent : Bool, thousands_separator : Char,
280269
symbols : Array(Char), width : Int32?,
@@ -308,6 +297,7 @@ module UnicodePlot
308297
labels_arr, counts,
309298
color: color, title: title,
310299
xlabel: xlabel.empty? ? "Frequency" : xlabel,
300+
xscale: xscale,
311301
border: border, margin: margin, padding: padding,
312302
labels: labels, unicode_exponent: unicode_exponent,
313303
thousands_separator: thousands_separator, symbols: symbols,

0 commit comments

Comments
 (0)