charty 0.2.5 → 0.2.6
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/README.md +2 -1
- data/charty.gemspec +8 -5
- data/lib/charty.rb +2 -2
- data/lib/charty/backends/plotly.rb +197 -8
- data/lib/charty/backends/pyplot.rb +135 -44
- data/lib/charty/dash_pattern_generator.rb +57 -0
- data/lib/charty/index.rb +1 -1
- data/lib/charty/plot_methods.rb +73 -3
- data/lib/charty/plotters.rb +1 -0
- data/lib/charty/plotters/abstract_plotter.rb +69 -9
- data/lib/charty/plotters/line_plotter.rb +300 -0
- data/lib/charty/plotters/relational_plotter.rb +213 -96
- data/lib/charty/plotters/scatter_plotter.rb +7 -31
- data/lib/charty/statistics.rb +2 -2
- data/lib/charty/table.rb +124 -14
- data/lib/charty/table_adapters/base_adapter.rb +97 -0
- data/lib/charty/table_adapters/daru_adapter.rb +2 -0
- data/lib/charty/table_adapters/datasets_adapter.rb +7 -0
- data/lib/charty/table_adapters/hash_adapter.rb +13 -2
- data/lib/charty/table_adapters/pandas_adapter.rb +82 -0
- data/lib/charty/util.rb +8 -0
- data/lib/charty/vector_adapters.rb +5 -1
- data/lib/charty/vector_adapters/array_adapter.rb +2 -10
- data/lib/charty/vector_adapters/daru_adapter.rb +3 -11
- data/lib/charty/vector_adapters/narray_adapter.rb +1 -6
- data/lib/charty/vector_adapters/numpy_adapter.rb +1 -1
- data/lib/charty/vector_adapters/pandas_adapter.rb +0 -1
- data/lib/charty/version.rb +1 -1
- metadata +54 -25
- data/lib/charty/missing_value_support.rb +0 -14
@@ -0,0 +1,57 @@
|
|
1
|
+
module Charty
|
2
|
+
module DashPatternGenerator
|
3
|
+
NAMED_PATTERNS = {
|
4
|
+
solid: "",
|
5
|
+
dash: [4, 1.5],
|
6
|
+
dot: [1, 1],
|
7
|
+
dashdot: [3, 1.25, 1.5, 1.25],
|
8
|
+
longdashdot: [5, 1, 1, 1],
|
9
|
+
}.freeze
|
10
|
+
|
11
|
+
def self.valid_name?(name)
|
12
|
+
name = case name
|
13
|
+
when Symbol, String
|
14
|
+
name.to_sym
|
15
|
+
else
|
16
|
+
name.to_str.to_sym
|
17
|
+
end
|
18
|
+
NAMED_PATTERNS.key?(name)
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.pattern_to_name(pattern)
|
22
|
+
NAMED_PATTERNS.each do |key, val|
|
23
|
+
return key if pattern == val
|
24
|
+
end
|
25
|
+
nil
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.each
|
29
|
+
return enum_for(__method__) unless block_given?
|
30
|
+
|
31
|
+
NAMED_PATTERNS.each_value do |pattern|
|
32
|
+
yield pattern
|
33
|
+
end
|
34
|
+
|
35
|
+
m = 3
|
36
|
+
while true
|
37
|
+
# Long and short dash combinations
|
38
|
+
a = [3, 1.25].repeated_combination(m).to_a[1..-2].reverse
|
39
|
+
b = [4, 1].repeated_combination(m).to_a[1..-2]
|
40
|
+
|
41
|
+
# Interleave these combinations
|
42
|
+
segment_list = a.zip(b).flatten(1)
|
43
|
+
|
44
|
+
# Insert the gaps
|
45
|
+
segment_list.each do |segment|
|
46
|
+
gap = segment.min
|
47
|
+
pattern = segment.map {|seg| [seg, gap] }.flatten
|
48
|
+
yield pattern
|
49
|
+
end
|
50
|
+
|
51
|
+
m += 1
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
extend Enumerable
|
56
|
+
end
|
57
|
+
end
|
data/lib/charty/index.rb
CHANGED
data/lib/charty/plot_methods.rb
CHANGED
@@ -130,6 +130,76 @@ module Charty
|
|
130
130
|
)
|
131
131
|
end
|
132
132
|
|
133
|
+
# Line plot
|
134
|
+
#
|
135
|
+
# @param x [vector-like object, key in data]
|
136
|
+
# @param y [vector-like object, key in data]
|
137
|
+
# @param color [vector-like object, key in data]
|
138
|
+
# @param style [vector-like object, key in data]
|
139
|
+
# @param size [vector-like object, key in data]
|
140
|
+
# @param data [table-like object]
|
141
|
+
# @param key_color [color object]
|
142
|
+
# @param palette [String,Array<Numeric>,Palette]
|
143
|
+
# @param color_order [Array<String>,Array<Symbol>]
|
144
|
+
# @param color_norm
|
145
|
+
# @param sizes [Array, Hash]
|
146
|
+
# @param size_order [Array]
|
147
|
+
# @param size_norm
|
148
|
+
# @param dashes [true, false, Array, Hash]
|
149
|
+
# @param markers [true, false, Array, Hash]
|
150
|
+
# @param style_order [Array]
|
151
|
+
# @param units [vector-like object, key in data]
|
152
|
+
# @param estimator [:mean]
|
153
|
+
# @param n_boot [Integer]
|
154
|
+
# @param random [Integer, Random, nil]
|
155
|
+
# @param sort [true, false]
|
156
|
+
# @param err_style [:band, :bars]
|
157
|
+
# @param err_params [Hash]
|
158
|
+
# @param error_bar
|
159
|
+
# @param x_scale [:linear, :log10]
|
160
|
+
# @param y_scale [:linear, :log10]
|
161
|
+
# @param legend [:auto, :brief, :full, false]
|
162
|
+
# How to draw legend. If :brief, numeric color and size variables
|
163
|
+
# will be represented with a sample of evenly spaced values. If
|
164
|
+
# :full, every group will get an entry in the legend. If :auto,
|
165
|
+
# choose between brief or full representation based on number of
|
166
|
+
# levels. If false, no legend data is added and no legend is drawn.
|
167
|
+
def line_plot(x: nil, y: nil, color: nil, style: nil, size: nil,
|
168
|
+
data: nil, key_color: nil, palette: nil, color_order: nil,
|
169
|
+
color_norm: nil, sizes: nil, size_order: nil, size_norm: nil,
|
170
|
+
markers: nil, dashes: true, style_order: nil,
|
171
|
+
units: nil, estimator: :mean, n_boot: 1000, random: nil,
|
172
|
+
sort: true, err_style: :band, err_params: nil, error_bar: [:ci, 95],
|
173
|
+
x_scale: :linear, y_scale: :linear, legend: :auto, **options, &block)
|
174
|
+
Plotters::LinePlotter.new(
|
175
|
+
data: data,
|
176
|
+
variables: { x: x, y: y, color: color, style: style, size: size },
|
177
|
+
key_color: key_color,
|
178
|
+
palette: palette,
|
179
|
+
color_order: color_order,
|
180
|
+
color_norm: color_norm,
|
181
|
+
sizes: sizes,
|
182
|
+
size_order: size_order,
|
183
|
+
size_norm: size_norm,
|
184
|
+
markers: markers,
|
185
|
+
dashes: dashes,
|
186
|
+
style_order: style_order,
|
187
|
+
units: units,
|
188
|
+
estimator: estimator,
|
189
|
+
n_boot: n_boot,
|
190
|
+
random: random,
|
191
|
+
sort: sort,
|
192
|
+
err_style: err_style,
|
193
|
+
err_params: err_params,
|
194
|
+
error_bar: error_bar,
|
195
|
+
x_scale: x_scale,
|
196
|
+
y_scale: y_scale,
|
197
|
+
legend: legend,
|
198
|
+
**options,
|
199
|
+
&block
|
200
|
+
)
|
201
|
+
end
|
202
|
+
|
133
203
|
# Scatter plot
|
134
204
|
#
|
135
205
|
# @param x [vector-like object, key in data]
|
@@ -146,7 +216,7 @@ module Charty
|
|
146
216
|
# @param size_order [Array]
|
147
217
|
# @param size_norm
|
148
218
|
# @param markers [true, false, Array, Hash]
|
149
|
-
# @param
|
219
|
+
# @param style_order [Array]
|
150
220
|
# @param alpha [scalar number]
|
151
221
|
# Propotional opacity of the points.
|
152
222
|
# @param legend [:auto, :brief, :full, false]
|
@@ -158,7 +228,7 @@ module Charty
|
|
158
228
|
def scatter_plot(x: nil, y: nil, color: nil, style: nil, size: nil,
|
159
229
|
data: nil, key_color: nil, palette: nil, color_order: nil,
|
160
230
|
color_norm: nil, sizes: nil, size_order: nil, size_norm: nil,
|
161
|
-
markers: true,
|
231
|
+
markers: true, style_order: nil, alpha: nil, legend: :auto,
|
162
232
|
**options, &block)
|
163
233
|
Plotters::ScatterPlotter.new(
|
164
234
|
data: data,
|
@@ -171,7 +241,7 @@ module Charty
|
|
171
241
|
size_order: size_order,
|
172
242
|
size_norm: size_norm,
|
173
243
|
markers: markers,
|
174
|
-
|
244
|
+
style_order: style_order,
|
175
245
|
alpha: alpha,
|
176
246
|
legend: legend,
|
177
247
|
**options,
|
data/lib/charty/plotters.rb
CHANGED
@@ -8,12 +8,28 @@ module Charty
|
|
8
8
|
self.data = data
|
9
9
|
self.palette = palette
|
10
10
|
substitute_options(options)
|
11
|
+
|
12
|
+
@var_levels = {}
|
13
|
+
@var_ordered = {x: false, y: false}
|
14
|
+
|
11
15
|
yield self if block_given?
|
12
16
|
end
|
13
17
|
|
14
18
|
attr_reader :data, :x, :y, :color
|
15
19
|
attr_reader :color_order, :key_color, :palette
|
16
20
|
|
21
|
+
def var_levels
|
22
|
+
variables.each_key do |var|
|
23
|
+
# TODO: Move mappers from RelationalPlotter to here,
|
24
|
+
# and remove the use of instance_variable_get
|
25
|
+
if instance_variable_defined?(:"@#{var}_mapper")
|
26
|
+
mapper = instance_variable_get(:"@#{var}_mapper")
|
27
|
+
@var_levels[var] = mapper.levels
|
28
|
+
end
|
29
|
+
end
|
30
|
+
@var_levels
|
31
|
+
end
|
32
|
+
|
17
33
|
def inspect
|
18
34
|
"#<#{self.class}:0x%016x>" % self.object_id
|
19
35
|
end
|
@@ -22,6 +38,8 @@ module Charty
|
|
22
38
|
@data = case data
|
23
39
|
when nil, Charty::Table
|
24
40
|
data
|
41
|
+
when method(:array?)
|
42
|
+
Charty::Vector.new(data)
|
25
43
|
else
|
26
44
|
Charty::Table.new(data)
|
27
45
|
end
|
@@ -40,11 +58,7 @@ module Charty
|
|
40
58
|
end
|
41
59
|
|
42
60
|
def color_order=(color_order)
|
43
|
-
|
44
|
-
unless color_order.nil?
|
45
|
-
raise NotImplementedError,
|
46
|
-
"Specifying color_order is not supported yet"
|
47
|
-
end
|
61
|
+
@color_order = color_order
|
48
62
|
end
|
49
63
|
|
50
64
|
# TODO: move to categorical_plotter
|
@@ -144,13 +158,59 @@ module Charty
|
|
144
158
|
end
|
145
159
|
|
146
160
|
private def remove_na!(ary)
|
147
|
-
ary.reject!
|
148
|
-
next true if x.nil?
|
149
|
-
x.respond_to?(:nan?) && x.nan?
|
150
|
-
end
|
161
|
+
ary.reject! {|x| Util.missing?(x) }
|
151
162
|
ary
|
152
163
|
end
|
153
164
|
|
165
|
+
private def each_subset(grouping_vars, reverse: false, processed: false, by_facet: true, allow_empty: false, drop_na: true)
|
166
|
+
case grouping_vars
|
167
|
+
when nil
|
168
|
+
grouping_vars = []
|
169
|
+
when String, Symbol
|
170
|
+
grouping_vars = [grouping_vars.to_sym]
|
171
|
+
end
|
172
|
+
|
173
|
+
if by_facet
|
174
|
+
[:col, :row].each do |facet_var|
|
175
|
+
grouping_vars << facet_var if variables.key?(facet_var)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
grouping_vars = grouping_vars.select {|var| variables.key?(var) }
|
180
|
+
|
181
|
+
data = processed ? processed_data : plot_data
|
182
|
+
data = data.drop_na if drop_na
|
183
|
+
|
184
|
+
levels = var_levels.dup
|
185
|
+
|
186
|
+
[:x, :y].each do |axis|
|
187
|
+
levels[axis] = plot_data[axis].categorical_order()
|
188
|
+
if processed
|
189
|
+
# TODO: perform inverse conversion of axis scaling here
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
if not grouping_vars.empty?
|
194
|
+
grouped = data.group_by(grouping_vars, sort: false)
|
195
|
+
grouped.each_group do |group_key, group_data|
|
196
|
+
next if group_data.empty? && !allow_empty
|
197
|
+
|
198
|
+
yield(grouping_vars.zip(group_key).to_h, group_data)
|
199
|
+
end
|
200
|
+
else
|
201
|
+
yield({}, data.dup)
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
def processed_data
|
206
|
+
@processed_data ||= calculate_processed_data
|
207
|
+
end
|
208
|
+
|
209
|
+
private def calculate_processed_data
|
210
|
+
# TODO: axis scaling support
|
211
|
+
plot_data
|
212
|
+
end
|
213
|
+
|
154
214
|
def save(filename, **kwargs)
|
155
215
|
backend = Backends.current
|
156
216
|
backend.begin_figure
|
@@ -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
|