charty 0.2.1 → 0.2.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +56 -23
- data/.github/workflows/nmatrix.yml +67 -0
- data/.github/workflows/pycall.yml +86 -0
- data/Dockerfile.dev +9 -1
- data/Gemfile +18 -0
- data/README.md +177 -9
- data/Rakefile +4 -5
- data/charty.gemspec +10 -5
- data/examples/palette.rb +1 -1
- data/examples/sample_images/hist_gruff.png +0 -0
- data/images/penguins_body_mass_g_flipper_length_mm_scatter_plot.png +0 -0
- data/images/penguins_body_mass_g_flipper_length_mm_species_scatter_plot.png +0 -0
- data/images/penguins_body_mass_g_flipper_length_mm_species_sex_scatter_plot.png +0 -0
- data/images/penguins_species_body_mass_g_bar_plot_h.png +0 -0
- data/images/penguins_species_body_mass_g_bar_plot_v.png +0 -0
- data/images/penguins_species_body_mass_g_box_plot_h.png +0 -0
- data/images/penguins_species_body_mass_g_box_plot_v.png +0 -0
- data/images/penguins_species_body_mass_g_sex_bar_plot_v.png +0 -0
- data/images/penguins_species_body_mass_g_sex_box_plot_v.png +0 -0
- data/lib/charty.rb +9 -2
- data/lib/charty/backends.rb +1 -0
- data/lib/charty/backends/bokeh.rb +2 -2
- data/lib/charty/backends/google_charts.rb +1 -1
- data/lib/charty/backends/gruff.rb +14 -3
- data/lib/charty/backends/plotly.rb +731 -32
- data/lib/charty/backends/plotly_helpers/html_renderer.rb +203 -0
- data/lib/charty/backends/plotly_helpers/notebook_renderer.rb +86 -0
- data/lib/charty/backends/plotly_helpers/plotly_renderer.rb +121 -0
- data/lib/charty/backends/pyplot.rb +515 -67
- data/lib/charty/backends/rubyplot.rb +1 -1
- data/lib/charty/backends/unicode_plot.rb +79 -0
- data/lib/charty/cache_dir.rb +27 -0
- data/lib/charty/dash_pattern_generator.rb +57 -0
- data/lib/charty/index.rb +213 -0
- data/lib/charty/iruby_helper.rb +18 -0
- data/lib/charty/linspace.rb +1 -1
- data/lib/charty/plot_methods.rb +283 -8
- data/lib/charty/plotter.rb +2 -2
- data/lib/charty/plotters.rb +11 -0
- data/lib/charty/plotters/abstract_plotter.rb +188 -18
- data/lib/charty/plotters/bar_plotter.rb +189 -7
- data/lib/charty/plotters/box_plotter.rb +64 -11
- data/lib/charty/plotters/categorical_plotter.rb +272 -40
- data/lib/charty/plotters/count_plotter.rb +7 -0
- data/lib/charty/plotters/distribution_plotter.rb +143 -0
- data/lib/charty/plotters/estimation_support.rb +84 -0
- data/lib/charty/plotters/histogram_plotter.rb +182 -0
- data/lib/charty/plotters/line_plotter.rb +300 -0
- data/lib/charty/plotters/random_support.rb +25 -0
- data/lib/charty/plotters/relational_plotter.rb +635 -0
- data/lib/charty/plotters/scatter_plotter.rb +80 -0
- data/lib/charty/plotters/vector_plotter.rb +6 -0
- data/lib/charty/statistics.rb +96 -2
- data/lib/charty/table.rb +160 -15
- data/lib/charty/table_adapters.rb +2 -0
- data/lib/charty/table_adapters/active_record_adapter.rb +17 -9
- data/lib/charty/table_adapters/base_adapter.rb +166 -0
- data/lib/charty/table_adapters/daru_adapter.rb +39 -3
- data/lib/charty/table_adapters/datasets_adapter.rb +13 -2
- data/lib/charty/table_adapters/hash_adapter.rb +141 -16
- data/lib/charty/table_adapters/narray_adapter.rb +25 -6
- data/lib/charty/table_adapters/nmatrix_adapter.rb +15 -5
- data/lib/charty/table_adapters/pandas_adapter.rb +163 -0
- data/lib/charty/util.rb +28 -0
- data/lib/charty/vector.rb +69 -0
- data/lib/charty/vector_adapters.rb +187 -0
- data/lib/charty/vector_adapters/array_adapter.rb +101 -0
- data/lib/charty/vector_adapters/daru_adapter.rb +163 -0
- data/lib/charty/vector_adapters/narray_adapter.rb +182 -0
- data/lib/charty/vector_adapters/nmatrix_adapter.rb +37 -0
- data/lib/charty/vector_adapters/numpy_adapter.rb +168 -0
- data/lib/charty/vector_adapters/pandas_adapter.rb +199 -0
- data/lib/charty/version.rb +1 -1
- metadata +105 -24
- data/lib/charty/palette.rb +0 -235
@@ -0,0 +1,143 @@
|
|
1
|
+
module Charty
|
2
|
+
module Plotters
|
3
|
+
class DistributionPlotter < AbstractPlotter
|
4
|
+
def flat_structure
|
5
|
+
{
|
6
|
+
x: :values
|
7
|
+
}
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize(data:, variables:, **options, &block)
|
11
|
+
x, y, color = variables.values_at(:x, :y, :color)
|
12
|
+
super(x, y, color, data: data, **options, &block)
|
13
|
+
|
14
|
+
setup_variables
|
15
|
+
end
|
16
|
+
|
17
|
+
attr_reader :variables
|
18
|
+
|
19
|
+
attr_reader :color_norm
|
20
|
+
|
21
|
+
def color_norm=(val)
|
22
|
+
unless val.nil?
|
23
|
+
raise NotImplementedError,
|
24
|
+
"Specifying color_norm is not supported yet"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
attr_reader :legend
|
29
|
+
|
30
|
+
def legend=(val)
|
31
|
+
@legend = check_legend(val)
|
32
|
+
end
|
33
|
+
|
34
|
+
private def check_legend(val)
|
35
|
+
check_boolean(val, :legend)
|
36
|
+
end
|
37
|
+
|
38
|
+
attr_reader :input_format, :plot_data, :variables, :var_types
|
39
|
+
|
40
|
+
# This should be the same as one in RelationalPlotter
|
41
|
+
# TODO: move this to AbstractPlotter and refactor with CategoricalPlotter
|
42
|
+
private def setup_variables
|
43
|
+
if x.nil? && y.nil?
|
44
|
+
@input_format = :wide
|
45
|
+
setup_variables_with_wide_form_dataset
|
46
|
+
else
|
47
|
+
@input_format = :long
|
48
|
+
setup_variables_with_long_form_dataset
|
49
|
+
end
|
50
|
+
|
51
|
+
@var_types = @plot_data.columns.map { |k|
|
52
|
+
[k, variable_type(@plot_data[k], :categorical)]
|
53
|
+
}.to_h
|
54
|
+
end
|
55
|
+
|
56
|
+
private def setup_variables_with_wide_form_dataset
|
57
|
+
unless color.nil?
|
58
|
+
raise ArgumentError,
|
59
|
+
"Unable to assign the following variables in wide-form data: color"
|
60
|
+
end
|
61
|
+
|
62
|
+
if data.nil? || data.empty?
|
63
|
+
@plot_data = Charty::Table.new({})
|
64
|
+
@variables = {}
|
65
|
+
return
|
66
|
+
end
|
67
|
+
|
68
|
+
# TODO: detect flat data
|
69
|
+
flat = data.is_a?(Charty::Vector)
|
70
|
+
if flat
|
71
|
+
@plot_data = {}
|
72
|
+
@variables = {}
|
73
|
+
|
74
|
+
[:x, :y].each do |var|
|
75
|
+
case self.flat_structure[var]
|
76
|
+
when :index
|
77
|
+
@plot_data[var] = data.index.to_a
|
78
|
+
@variables[var] = data.index.name
|
79
|
+
when :values
|
80
|
+
@plot_data[var] = data.to_a
|
81
|
+
@variables[var] = data.name
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
@plot_data = Charty::Table.new(@plot_data)
|
86
|
+
else
|
87
|
+
raise NotImplementedError,
|
88
|
+
"wide-form input is not supported"
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
private def setup_variables_with_long_form_dataset
|
93
|
+
if data.nil? || data.empty?
|
94
|
+
@plot_data = Charty::Table.new({})
|
95
|
+
@variables = {}
|
96
|
+
return
|
97
|
+
end
|
98
|
+
|
99
|
+
plot_data = {}
|
100
|
+
variables = {}
|
101
|
+
|
102
|
+
{
|
103
|
+
x: self.x,
|
104
|
+
y: self.y,
|
105
|
+
color: self.color,
|
106
|
+
}.each do |key, val|
|
107
|
+
next if val.nil?
|
108
|
+
|
109
|
+
if data.column_names.include?(val)
|
110
|
+
plot_data[key] = data[val]
|
111
|
+
variables[key] = val
|
112
|
+
else
|
113
|
+
case val
|
114
|
+
when Charty::Vector
|
115
|
+
plot_data[key] = val
|
116
|
+
variables[key] = val.name
|
117
|
+
else
|
118
|
+
raise ArgumentError,
|
119
|
+
"Could not interpret value %p for parameter %p" % [val, key]
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
@plot_data = Charty::Table.new(plot_data)
|
125
|
+
@variables = variables.select do |var, name|
|
126
|
+
@plot_data[var].notnull.any?
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
private def map_color(palette: nil, order: nil, norm: nil)
|
131
|
+
@color_mapper = ColorMapper.new(self, palette, order, norm)
|
132
|
+
end
|
133
|
+
|
134
|
+
private def map_size(sizes: nil, order: nil, norm: nil)
|
135
|
+
@size_mapper = SizeMapper.new(self, sizes, order, norm)
|
136
|
+
end
|
137
|
+
|
138
|
+
private def map_style(markers: nil, dashes: nil, order: nil)
|
139
|
+
@style_mapper = StyleMapper.new(self, markers, dashes, order)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
module Charty
|
2
|
+
module Plotters
|
3
|
+
module EstimationSupport
|
4
|
+
attr_reader :estimator
|
5
|
+
|
6
|
+
def estimator=(estimator)
|
7
|
+
@estimator = check_estimator(estimator)
|
8
|
+
end
|
9
|
+
|
10
|
+
module_function def check_estimator(value)
|
11
|
+
case value
|
12
|
+
when :count, "count"
|
13
|
+
:count
|
14
|
+
when :mean, "mean"
|
15
|
+
:mean
|
16
|
+
when :median
|
17
|
+
raise NotImplementedError,
|
18
|
+
"median estimator has not been supported yet"
|
19
|
+
when Proc
|
20
|
+
raise NotImplementedError,
|
21
|
+
"a callable estimator has not been supported yet"
|
22
|
+
else
|
23
|
+
raise ArgumentError,
|
24
|
+
"invalid value for estimator (%p for :mean)" % value
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
attr_reader :ci
|
29
|
+
|
30
|
+
def ci=(ci)
|
31
|
+
@ci = check_ci(ci)
|
32
|
+
end
|
33
|
+
|
34
|
+
private def check_ci(value)
|
35
|
+
case value
|
36
|
+
when nil
|
37
|
+
nil
|
38
|
+
when :sd, "sd"
|
39
|
+
:sd
|
40
|
+
when 0..100
|
41
|
+
value
|
42
|
+
when Numeric
|
43
|
+
raise ArgumentError,
|
44
|
+
"ci must be in 0..100, but %p is given" % value
|
45
|
+
else
|
46
|
+
raise ArgumentError,
|
47
|
+
"invalid value for ci (%p for nil, :sd, or a number in 0..100)" % value
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
attr_reader :n_boot
|
52
|
+
|
53
|
+
def n_boot=(n_boot)
|
54
|
+
@n_boot = check_n_boot(n_boot)
|
55
|
+
end
|
56
|
+
|
57
|
+
private def check_n_boot(value)
|
58
|
+
case value
|
59
|
+
when Integer
|
60
|
+
if value <= 0
|
61
|
+
raise ArgumentError,
|
62
|
+
"n_boot must be larger than zero, but %p is given" % value
|
63
|
+
end
|
64
|
+
value
|
65
|
+
else
|
66
|
+
raise ArgumentError,
|
67
|
+
"invalid value for n_boot (%p for an integer > 0)" % value
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
attr_reader :units
|
72
|
+
|
73
|
+
def units=(units)
|
74
|
+
@units = check_dimension(units, :units)
|
75
|
+
unless units.nil?
|
76
|
+
raise NotImplementedError,
|
77
|
+
"Specifying units variable is not supported yet"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
include RandomSupport
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,182 @@
|
|
1
|
+
module Charty
|
2
|
+
module Plotters
|
3
|
+
class HistogramPlotter < DistributionPlotter
|
4
|
+
def univariate?
|
5
|
+
self.variables.key?(:x) != self.variables.key?(:y)
|
6
|
+
end
|
7
|
+
|
8
|
+
def univariate_variable
|
9
|
+
unless univariate?
|
10
|
+
raise TypeError, "This is not a univariate plot"
|
11
|
+
end
|
12
|
+
([:x, :y] & self.variables.keys)[0]
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_reader :weights
|
16
|
+
|
17
|
+
def weights=(val)
|
18
|
+
@weights = check_weights(val)
|
19
|
+
end
|
20
|
+
|
21
|
+
private def check_weights(val)
|
22
|
+
raise NotImplementedError, "weights is not supported yet"
|
23
|
+
end
|
24
|
+
|
25
|
+
attr_reader :stat
|
26
|
+
|
27
|
+
def stat=(val)
|
28
|
+
@stat = check_stat(val)
|
29
|
+
end
|
30
|
+
|
31
|
+
private def check_stat(val)
|
32
|
+
case val
|
33
|
+
when :count, "count"
|
34
|
+
val.to_sym
|
35
|
+
when :frequency, "frequency",
|
36
|
+
:density, "density",
|
37
|
+
:probability, "probability"
|
38
|
+
raise ArgumentError,
|
39
|
+
"%p for `stat` is not supported yet" % val,
|
40
|
+
caller
|
41
|
+
else
|
42
|
+
raise ArgumentError,
|
43
|
+
"Invalid value for `stat` (%p)" % val,
|
44
|
+
caller
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
attr_reader :bins
|
49
|
+
|
50
|
+
def bins=(val)
|
51
|
+
@bins = check_bins(val)
|
52
|
+
end
|
53
|
+
|
54
|
+
private def check_bins(val)
|
55
|
+
case val
|
56
|
+
when :auto, "auto"
|
57
|
+
val.to_sym
|
58
|
+
when Integer
|
59
|
+
val
|
60
|
+
else
|
61
|
+
raise ArgumentError,
|
62
|
+
"Invalid value for `bins` (%p)" % val,
|
63
|
+
caller
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# TODO: bin_width
|
68
|
+
# TODO: bin_range
|
69
|
+
# TODO: discrete
|
70
|
+
# TODO: cumulative
|
71
|
+
# TODO: common_bins
|
72
|
+
# TODO: common_norm
|
73
|
+
|
74
|
+
attr_reader :multiple
|
75
|
+
|
76
|
+
def multiple=(val)
|
77
|
+
@multiple = check_multiple(val)
|
78
|
+
end
|
79
|
+
|
80
|
+
private def check_multiple(val)
|
81
|
+
case val
|
82
|
+
when :layer, "layer"
|
83
|
+
val.to_sym
|
84
|
+
when :dodge, "dodge",
|
85
|
+
:stack, "stack",
|
86
|
+
:fill, "fill"
|
87
|
+
val = val.to_sym
|
88
|
+
raise NotImplementedError,
|
89
|
+
"%p for `multiple` is not supported yet" % val,
|
90
|
+
caller
|
91
|
+
else
|
92
|
+
raise ArgumentError,
|
93
|
+
"Invalid value for `multiple` (%p)" % val,
|
94
|
+
caller
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# TODO: element
|
99
|
+
# TODO: fill
|
100
|
+
# TODO: shrink
|
101
|
+
|
102
|
+
attr_reader :kde
|
103
|
+
|
104
|
+
def kde=(val)
|
105
|
+
raise NotImplementedError, "kde is not supported yet"
|
106
|
+
end
|
107
|
+
|
108
|
+
attr_reader :kde_params
|
109
|
+
|
110
|
+
def kde_params=(val)
|
111
|
+
raise NotImplementedError, "kde_params is not supported yet"
|
112
|
+
end
|
113
|
+
|
114
|
+
# TODO: thresh
|
115
|
+
# TODO: pthresh
|
116
|
+
# TODO: pmax
|
117
|
+
# TODO: cbar
|
118
|
+
# TODO: cbar_params
|
119
|
+
# TODO: x_log_scale
|
120
|
+
# TODO: y_log_scale
|
121
|
+
|
122
|
+
private def render_plot(backend, **)
|
123
|
+
draw_univariate_histogram(backend)
|
124
|
+
annotate_axes(backend)
|
125
|
+
end
|
126
|
+
|
127
|
+
private def draw_univariate_histogram(backend)
|
128
|
+
map_color(palette: palette, order: color_order, norm: color_norm)
|
129
|
+
|
130
|
+
# TODO: calculate histogram here and use bar plot to visualize
|
131
|
+
data_variable = self.univariate_variable
|
132
|
+
|
133
|
+
histograms = {}
|
134
|
+
each_subset([:color], processed: true) do |sub_vars, sub_data|
|
135
|
+
key = sub_vars.to_a
|
136
|
+
observations = sub_data[data_variable].drop_na.to_a
|
137
|
+
hist = Statistics.histogram(observations)
|
138
|
+
histograms[key] = hist
|
139
|
+
end
|
140
|
+
|
141
|
+
bin_start, bin_end, bin_size = nil
|
142
|
+
histograms.each do |_, hist|
|
143
|
+
s, e = hist.edge.minmax
|
144
|
+
z = (e - s).to_f / (hist.edge.length - 1)
|
145
|
+
bin_start = [bin_start, s].compact.min
|
146
|
+
bin_end = [bin_end, e].compact.max
|
147
|
+
bin_size = [bin_size, z].compact.min
|
148
|
+
end
|
149
|
+
|
150
|
+
if self.variables.key?(:color)
|
151
|
+
alpha = 0.5
|
152
|
+
else
|
153
|
+
alpha = 0.75
|
154
|
+
end
|
155
|
+
|
156
|
+
each_subset([:color], processed: true) do |sub_vars, sub_data|
|
157
|
+
name = sub_vars[:color]
|
158
|
+
observations = sub_data[data_variable].drop_na.to_a
|
159
|
+
|
160
|
+
backend.univariate_histogram(observations, name, data_variable, stat,
|
161
|
+
bin_start, bin_end, bin_size, alpha,
|
162
|
+
name, @color_mapper)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
private def annotate_axes(backend)
|
167
|
+
if univariate?
|
168
|
+
xlabel = self.variables[:x]
|
169
|
+
ylabel = self.variables[:y]
|
170
|
+
case self.univariate_variable
|
171
|
+
when :x
|
172
|
+
ylabel = self.stat.to_s.capitalize
|
173
|
+
else
|
174
|
+
xlabel = self.stat.to_s.capitalize
|
175
|
+
end
|
176
|
+
backend.set_ylabel(ylabel) if ylabel
|
177
|
+
backend.set_xlabel(xlabel) if xlabel
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
@@ -0,0 +1,300 @@
|
|
1
|
+
module Charty
|
2
|
+
module Plotters
|
3
|
+
class EstimateAggregator
|
4
|
+
def initialize(estimator, error_bar, n_boot, random)
|
5
|
+
@estimator = estimator
|
6
|
+
@method, @level = error_bar
|
7
|
+
@n_boot = n_boot
|
8
|
+
@random = random
|
9
|
+
end
|
10
|
+
|
11
|
+
# Perform aggregation
|
12
|
+
#
|
13
|
+
# @param data [Hash<Any, Charty::Table>]
|
14
|
+
# @param var_name [Symbol, String] A column name to be aggregated
|
15
|
+
def aggregate(data, var_name)
|
16
|
+
values = data[var_name]
|
17
|
+
estimation = case @estimator
|
18
|
+
when :count
|
19
|
+
values.length
|
20
|
+
when :mean
|
21
|
+
values.mean
|
22
|
+
end
|
23
|
+
|
24
|
+
n = values.length
|
25
|
+
case
|
26
|
+
# No error bars
|
27
|
+
when @method.nil?
|
28
|
+
err_min = err_max = Float::NAN
|
29
|
+
when n <= 1
|
30
|
+
err_min = err_max = Float::NAN
|
31
|
+
|
32
|
+
# User-defined method
|
33
|
+
when @method.respond_to?(:call)
|
34
|
+
err_min, err_max = @method.call(values)
|
35
|
+
|
36
|
+
# Parametric
|
37
|
+
when @method == :sd
|
38
|
+
err_radius = values.stdev * @level
|
39
|
+
err_min = estimation - err_radius
|
40
|
+
err_max = estimation + err_radius
|
41
|
+
when @method == :se
|
42
|
+
err_radius = values.stdev / Math.sqrt(n)
|
43
|
+
err_min = estimation - err_radius
|
44
|
+
err_max = estimation + err_radius
|
45
|
+
|
46
|
+
# Nonparametric
|
47
|
+
when @method == :pi
|
48
|
+
err_min, err_max = percentile_interval(values, @level)
|
49
|
+
when @method == :ci
|
50
|
+
# TODO: Support units
|
51
|
+
err_min, err_max =
|
52
|
+
Statistics.bootstrap_ci(values, @level, units: nil, func: @estimator,
|
53
|
+
n_boot: @n_boot, random: @random)
|
54
|
+
end
|
55
|
+
|
56
|
+
{
|
57
|
+
var_name => estimation,
|
58
|
+
"#{var_name}_min" => err_min,
|
59
|
+
"#{var_name}_max" => err_max
|
60
|
+
}
|
61
|
+
end
|
62
|
+
|
63
|
+
def percentile_interval(values, width)
|
64
|
+
q = [50 - width / 2, 50 + width / 2]
|
65
|
+
Statistics.percentile(values, q)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
class LinePlotter < RelationalPlotter
|
70
|
+
def initialize(data: nil, variables: {}, **options, &block)
|
71
|
+
x, y, color, style, size = variables.values_at(:x, :y, :color, :style, :size)
|
72
|
+
super(x, y, color, style, size, data: data, **options, &block)
|
73
|
+
|
74
|
+
@comp_data = nil
|
75
|
+
end
|
76
|
+
|
77
|
+
attr_reader :estimator
|
78
|
+
|
79
|
+
def estimator=(estimator)
|
80
|
+
@estimator = check_estimator(estimator)
|
81
|
+
end
|
82
|
+
|
83
|
+
private def check_estimator(value)
|
84
|
+
case value
|
85
|
+
when nil, false
|
86
|
+
nil
|
87
|
+
when :count, "count"
|
88
|
+
:count
|
89
|
+
when :mean, "mean"
|
90
|
+
:mean
|
91
|
+
when :median
|
92
|
+
raise NotImplementedError,
|
93
|
+
"median estimator has not been supported yet"
|
94
|
+
when Proc
|
95
|
+
raise NotImplementedError,
|
96
|
+
"a callable estimator has not been supported yet"
|
97
|
+
else
|
98
|
+
raise ArgumentError,
|
99
|
+
"invalid value for estimator (%p for :mean)" % value
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
attr_reader :n_boot
|
104
|
+
|
105
|
+
def n_boot=(n_boot)
|
106
|
+
@n_boot = check_n_boot(n_boot)
|
107
|
+
end
|
108
|
+
|
109
|
+
private def check_n_boot(value)
|
110
|
+
case value
|
111
|
+
when Integer
|
112
|
+
if value <= 0
|
113
|
+
raise ArgumentError,
|
114
|
+
"n_boot must be larger than zero, but %p is given" % value
|
115
|
+
end
|
116
|
+
value
|
117
|
+
else
|
118
|
+
raise ArgumentError,
|
119
|
+
"invalid value for n_boot (%p for an integer > 0)" % value
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
include RandomSupport
|
124
|
+
|
125
|
+
attr_reader :sort, :err_style, :err_kws, :error_bar, :x_scale, :y_scale
|
126
|
+
|
127
|
+
def sort=(val)
|
128
|
+
@sort = check_boolean(val, :sort)
|
129
|
+
end
|
130
|
+
|
131
|
+
def err_style=(val)
|
132
|
+
@err_style = check_err_style(val)
|
133
|
+
end
|
134
|
+
|
135
|
+
private def check_err_style(val)
|
136
|
+
case val
|
137
|
+
when :bars, "bars", :band, "band"
|
138
|
+
val.to_sym
|
139
|
+
else
|
140
|
+
raise ArgumentError,
|
141
|
+
"Invalid value for err_style (%p for :band or :bars)" % val
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
# parameters to draw error bars/bands
|
146
|
+
def err_params=(val)
|
147
|
+
unless val.nil?
|
148
|
+
raise NotImplementedError,
|
149
|
+
"Specifying `err_params` is not supported"
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
# The method and level to calculate error bars/bands
|
154
|
+
def error_bar=(val)
|
155
|
+
@error_bar = check_error_bar(val)
|
156
|
+
end
|
157
|
+
|
158
|
+
DEFAULT_ERROR_BAR_LEVELS = {
|
159
|
+
ci: 95,
|
160
|
+
pi: 95,
|
161
|
+
se: 1,
|
162
|
+
sd: 1
|
163
|
+
}.freeze
|
164
|
+
|
165
|
+
VALID_ERROR_BAR_METHODS = DEFAULT_ERROR_BAR_LEVELS.keys
|
166
|
+
VALID_ERROR_BAR_METHODS.concat(VALID_ERROR_BAR_METHODS.map(&:to_s))
|
167
|
+
VALID_ERROR_BAR_METHODS.freeze
|
168
|
+
|
169
|
+
private def check_error_bar(val)
|
170
|
+
case val
|
171
|
+
when nil
|
172
|
+
return [nil, nil]
|
173
|
+
when ->(x) { x.respond_to?(:call) }
|
174
|
+
return [val, nil]
|
175
|
+
when *VALID_ERROR_BAR_METHODS
|
176
|
+
method = val.to_sym
|
177
|
+
level = nil
|
178
|
+
when Array
|
179
|
+
if val.length != 2
|
180
|
+
raise ArgumentError,
|
181
|
+
"The `error_bar` array has the wrong number of items " +
|
182
|
+
"(%d for 2)" % val.length
|
183
|
+
end
|
184
|
+
method, level = *val
|
185
|
+
else
|
186
|
+
raise ArgumentError,
|
187
|
+
"Unable to recognize the value for `error_bar`: %p" % val
|
188
|
+
end
|
189
|
+
|
190
|
+
case method
|
191
|
+
when *VALID_ERROR_BAR_METHODS
|
192
|
+
method = method.to_sym
|
193
|
+
else
|
194
|
+
error_message = "The value for method in `error_bar` array must be in %p, but %p was passed" % [
|
195
|
+
DEFAULT_ERROR_BAR_LEVELS.keys,
|
196
|
+
method
|
197
|
+
]
|
198
|
+
raise ArgumentError, error_message
|
199
|
+
end
|
200
|
+
|
201
|
+
case level
|
202
|
+
when Numeric
|
203
|
+
# nothing to do
|
204
|
+
when nil
|
205
|
+
level = DEFAULT_ERROR_BAR_LEVELS[method]
|
206
|
+
else
|
207
|
+
raise ArgumentError,
|
208
|
+
"The value of level in `error_bar` array must be a number "
|
209
|
+
end
|
210
|
+
|
211
|
+
[method, level]
|
212
|
+
end
|
213
|
+
|
214
|
+
def x_scale=(val)
|
215
|
+
@x_scale = check_axis_scale(val, :x)
|
216
|
+
end
|
217
|
+
|
218
|
+
def y_scale=(val)
|
219
|
+
@y_scale = check_axis_scale(val, :y)
|
220
|
+
end
|
221
|
+
|
222
|
+
private def check_axis_scale(val, axis)
|
223
|
+
case val
|
224
|
+
when :linear, "linear", :log10, "log10"
|
225
|
+
val.to_sym
|
226
|
+
else
|
227
|
+
raise ArgumentError,
|
228
|
+
"The value of `#{axis}_scale` is worng: %p" % val,
|
229
|
+
caller
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
private def render_plot(backend, **)
|
234
|
+
draw_lines(backend)
|
235
|
+
annotate_axes(backend)
|
236
|
+
end
|
237
|
+
|
238
|
+
private def draw_lines(backend)
|
239
|
+
map_color(palette: palette, order: color_order, norm: color_norm)
|
240
|
+
map_size(sizes: sizes, order: size_order, norm: size_norm)
|
241
|
+
map_style(markers: markers, dashes: dashes, order: style_order)
|
242
|
+
|
243
|
+
aggregator = EstimateAggregator.new(estimator, error_bar, n_boot, random)
|
244
|
+
|
245
|
+
agg_var = :y
|
246
|
+
grouper = :x
|
247
|
+
grouping_vars = [:color, :size, :style]
|
248
|
+
|
249
|
+
each_subset(grouping_vars, processed: true) do |sub_vars, sub_data|
|
250
|
+
if self.sort
|
251
|
+
sort_cols = [:units, :x, :y] & self.variables.keys
|
252
|
+
sub_data = sub_data.sort_values(sort_cols)
|
253
|
+
end
|
254
|
+
|
255
|
+
unless estimator.nil?
|
256
|
+
if self.variables.include?(:units)
|
257
|
+
raise "`estimator` is must be nil when specifying `units`"
|
258
|
+
end
|
259
|
+
|
260
|
+
grouped = sub_data.group_by(grouper, sort: self.sort)
|
261
|
+
sub_data = grouped.apply(agg_var, &aggregator.method(:aggregate)).reset_index
|
262
|
+
end
|
263
|
+
|
264
|
+
# TODO: perform inverse conversion of axis scaling before plot
|
265
|
+
|
266
|
+
unit_grouping = if self.variables.include?(:units)
|
267
|
+
sub_data.group_by(:units).each_group
|
268
|
+
else
|
269
|
+
{ nil => sub_data }
|
270
|
+
end
|
271
|
+
unit_grouping.each do |_unit_value, unit_data|
|
272
|
+
ci_params = unless self.estimator.nil? || self.error_bar.nil?
|
273
|
+
{
|
274
|
+
style: self.err_style,
|
275
|
+
y_min: sub_data[:y_min],
|
276
|
+
y_max: sub_data[:y_max]
|
277
|
+
}
|
278
|
+
end
|
279
|
+
backend.line(unit_data[:x], unit_data[:y], self.variables,
|
280
|
+
color: sub_vars[:color], color_mapper: @color_mapper,
|
281
|
+
size: sub_vars[:size], size_mapper: @size_mapper,
|
282
|
+
style: sub_vars[:style], style_mapper: @style_mapper,
|
283
|
+
ci_params: ci_params)
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
if legend
|
288
|
+
backend.add_line_plot_legend(@variables, @color_mapper, @size_mapper, @style_mapper, legend)
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
private def annotate_axes(backend)
|
293
|
+
xlabel = self.variables[:x]
|
294
|
+
ylabel = self.variables[:y]
|
295
|
+
backend.set_xlabel(xlabel) unless xlabel.nil?
|
296
|
+
backend.set_ylabel(ylabel) unless ylabel.nil?
|
297
|
+
end
|
298
|
+
end
|
299
|
+
end
|
300
|
+
end
|