charty 0.2.5 → 0.2.6
Sign up to get free protection for your applications and to get access to all the features.
- 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
|