charty 0.2.4 → 0.2.9
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 +64 -15
- data/charty.gemspec +10 -3
- data/lib/charty.rb +5 -2
- data/lib/charty/backends/bokeh.rb +2 -2
- data/lib/charty/backends/google_charts.rb +1 -1
- data/lib/charty/backends/gruff.rb +1 -1
- data/lib/charty/backends/plotly.rb +434 -32
- data/lib/charty/backends/plotly_helpers/html_renderer.rb +203 -0
- data/lib/charty/backends/plotly_helpers/notebook_renderer.rb +87 -0
- data/lib/charty/backends/plotly_helpers/plotly_renderer.rb +121 -0
- data/lib/charty/backends/pyplot.rb +187 -48
- data/lib/charty/backends/rubyplot.rb +1 -1
- data/lib/charty/cache_dir.rb +27 -0
- data/lib/charty/dash_pattern_generator.rb +57 -0
- data/lib/charty/index.rb +1 -1
- data/lib/charty/iruby_helper.rb +18 -0
- data/lib/charty/plot_methods.rb +115 -3
- data/lib/charty/plotter.rb +2 -2
- data/lib/charty/plotters.rb +4 -0
- data/lib/charty/plotters/abstract_plotter.rb +106 -11
- data/lib/charty/plotters/bar_plotter.rb +1 -16
- data/lib/charty/plotters/box_plotter.rb +1 -16
- data/lib/charty/plotters/distribution_plotter.rb +150 -0
- data/lib/charty/plotters/histogram_plotter.rb +242 -0
- 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 +8 -43
- data/lib/charty/statistics.rb +11 -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 +19 -3
- data/lib/charty/table_adapters/pandas_adapter.rb +82 -0
- data/lib/charty/util.rb +28 -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 +104 -11
- data/lib/charty/missing_value_support.rb +0 -14
@@ -6,7 +6,7 @@ module Charty
|
|
6
6
|
super(x, y, color, style, size, data: data, **options, &block)
|
7
7
|
end
|
8
8
|
|
9
|
-
attr_reader :alpha
|
9
|
+
attr_reader :alpha
|
10
10
|
|
11
11
|
def alpha=(val)
|
12
12
|
case val
|
@@ -25,18 +25,6 @@ module Charty
|
|
25
25
|
end
|
26
26
|
end
|
27
27
|
|
28
|
-
def legend=(val)
|
29
|
-
case val
|
30
|
-
when :auto, :brief, :full, false
|
31
|
-
@legend = val
|
32
|
-
when "auto", "brief", "full"
|
33
|
-
@legend = val.to_sym
|
34
|
-
else
|
35
|
-
raise ArgumentError,
|
36
|
-
"invalid value of legend (%p for :auto, :brief, :full, or false)" % val
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
28
|
attr_reader :line_width, :edge_color
|
41
29
|
|
42
30
|
def line_width=(val)
|
@@ -47,26 +35,15 @@ module Charty
|
|
47
35
|
@line_width = check_color(val, :edge_color, allow_nil: true)
|
48
36
|
end
|
49
37
|
|
50
|
-
def
|
51
|
-
backend = Backends.current
|
52
|
-
backend.begin_figure
|
53
|
-
draw_points(backend)
|
54
|
-
annotate_axes(backend)
|
55
|
-
backend.show
|
56
|
-
end
|
57
|
-
|
58
|
-
def save(filename, **opts)
|
59
|
-
backend = Backends.current
|
60
|
-
backend.begin_figure
|
38
|
+
private def render_plot(backend, **)
|
61
39
|
draw_points(backend)
|
62
40
|
annotate_axes(backend)
|
63
|
-
backend.save(filename, **opts)
|
64
41
|
end
|
65
42
|
|
66
43
|
private def draw_points(backend)
|
67
44
|
map_color(palette: palette, order: color_order, norm: color_norm)
|
68
45
|
map_size(sizes: sizes, order: size_order, norm: size_norm)
|
69
|
-
map_style(markers: markers, order:
|
46
|
+
map_style(markers: markers, order: style_order)
|
70
47
|
|
71
48
|
data = @plot_data.drop_na
|
72
49
|
|
@@ -84,9 +61,12 @@ module Charty
|
|
84
61
|
x, y, @variables,
|
85
62
|
color: color, color_mapper: @color_mapper,
|
86
63
|
style: style, style_mapper: @style_mapper,
|
87
|
-
size: size, size_mapper: @size_mapper
|
88
|
-
legend: legend
|
64
|
+
size: size, size_mapper: @size_mapper
|
89
65
|
)
|
66
|
+
|
67
|
+
if legend
|
68
|
+
backend.add_scatter_plot_legend(@variables, @color_mapper, @size_mapper, @style_mapper, legend)
|
69
|
+
end
|
90
70
|
end
|
91
71
|
|
92
72
|
private def annotate_axes(backend)
|
@@ -94,21 +74,6 @@ module Charty
|
|
94
74
|
ylabel = self.variables[:y]
|
95
75
|
backend.set_xlabel(xlabel) unless xlabel.nil?
|
96
76
|
backend.set_ylabel(ylabel) unless ylabel.nil?
|
97
|
-
|
98
|
-
if legend
|
99
|
-
add_legend_data(backend)
|
100
|
-
end
|
101
|
-
end
|
102
|
-
|
103
|
-
private def add_legend_data(backend)
|
104
|
-
# TODO: Legend Support
|
105
|
-
verbosity = legend
|
106
|
-
verbosity = :auto if verbosity == true
|
107
|
-
|
108
|
-
titles = [:color, :size, :style].filter_map do |v|
|
109
|
-
variables[v] if variables.key?(v)
|
110
|
-
end
|
111
|
-
legend_title = titles.length == 1 ? titles[0] : ""
|
112
77
|
end
|
113
78
|
end
|
114
79
|
end
|
data/lib/charty/statistics.rb
CHANGED
@@ -10,6 +10,10 @@ module Charty
|
|
10
10
|
def self.stdev(enum, population: false)
|
11
11
|
enum.stdev(population: population)
|
12
12
|
end
|
13
|
+
|
14
|
+
def self.histogram(ary, *args, **kwargs)
|
15
|
+
ary.histogram(*args, **kwargs)
|
16
|
+
end
|
13
17
|
rescue LoadError
|
14
18
|
def self.mean(enum)
|
15
19
|
xs = enum.to_a
|
@@ -24,6 +28,11 @@ module Charty
|
|
24
28
|
var = xs.map {|x| (x - mean)**2 }.sum / (n - ddof)
|
25
29
|
Math.sqrt(var)
|
26
30
|
end
|
31
|
+
|
32
|
+
def self.histogram(ary, *args, **kwargs)
|
33
|
+
raise NotImplementedError,
|
34
|
+
"histogram is currently supported only with enumerable-statistics"
|
35
|
+
end
|
27
36
|
end
|
28
37
|
|
29
38
|
def self.bootstrap(vector, n_boot: 2000, func: :mean, units: nil, random: nil)
|
@@ -83,9 +92,9 @@ module Charty
|
|
83
92
|
"structured bootstrapping has not been supported yet"
|
84
93
|
end
|
85
94
|
|
86
|
-
def self.bootstrap_ci(*vectors,
|
95
|
+
def self.bootstrap_ci(*vectors, width, n_boot: 2000, func: :mean, units: nil, random: nil)
|
87
96
|
boot = bootstrap(*vectors, n_boot: n_boot, func: func, units: units, random: random)
|
88
|
-
q = [50 -
|
97
|
+
q = [50 - width / 2, 50 + width / 2]
|
89
98
|
if boot.respond_to?(:percentile)
|
90
99
|
boot.percentile(q)
|
91
100
|
else
|
data/lib/charty/table.rb
CHANGED
@@ -13,7 +13,6 @@ module Charty
|
|
13
13
|
|
14
14
|
class Table
|
15
15
|
extend Forwardable
|
16
|
-
include MissingValueSupport
|
17
16
|
|
18
17
|
def initialize(data, **kwargs)
|
19
18
|
adapter_class = TableAdapters.find_adapter_class(data)
|
@@ -34,6 +33,20 @@ module Charty
|
|
34
33
|
def_delegators :adapter, :index, :index=
|
35
34
|
|
36
35
|
def_delegator :@adapter, :column_names
|
36
|
+
|
37
|
+
def column?(name)
|
38
|
+
return true if column_names.include?(name)
|
39
|
+
|
40
|
+
case name
|
41
|
+
when String
|
42
|
+
column_names.include?(name.to_sym)
|
43
|
+
when Symbol
|
44
|
+
column_names.include?(name.to_s)
|
45
|
+
else
|
46
|
+
false
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
37
50
|
def_delegator :@adapter, :data, :raw_data
|
38
51
|
|
39
52
|
def ==(other)
|
@@ -65,6 +78,10 @@ module Charty
|
|
65
78
|
end
|
66
79
|
end
|
67
80
|
|
81
|
+
def group_by(grouper, sort: true, drop_na: true)
|
82
|
+
adapter.group_by(self, grouper, sort, drop_na)
|
83
|
+
end
|
84
|
+
|
68
85
|
def to_a(x=nil, y=nil, z=nil)
|
69
86
|
case
|
70
87
|
when defined?(Daru::DataFrame) && table.kind_of?(Daru::DataFrame)
|
@@ -99,21 +116,114 @@ module Charty
|
|
99
116
|
end
|
100
117
|
|
101
118
|
def drop_na
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
119
|
+
@adapter.drop_na || self
|
120
|
+
end
|
121
|
+
|
122
|
+
def_delegator :adapter, :sort_values
|
123
|
+
|
124
|
+
def_delegator :adapter, :reset_index
|
125
|
+
|
126
|
+
class GroupByBase
|
127
|
+
end
|
128
|
+
|
129
|
+
class HashGroupBy < GroupByBase
|
130
|
+
def initialize(table, grouper, sort, drop_na)
|
131
|
+
@table = table
|
132
|
+
@grouper = check_grouper(grouper)
|
133
|
+
init_groups(sort, drop_na)
|
134
|
+
end
|
135
|
+
|
136
|
+
private def check_grouper(grouper)
|
137
|
+
case grouper
|
138
|
+
when Symbol, String, Array
|
139
|
+
# TODO check column existence
|
140
|
+
return grouper
|
141
|
+
when Charty::Vector
|
142
|
+
if @table.length != grouper.length
|
143
|
+
raise ArgumentError,
|
144
|
+
"Wrong number of items in grouper array " +
|
145
|
+
"(%p for %p)" % [val.length, @table.length]
|
146
|
+
end
|
147
|
+
return grouper
|
148
|
+
when ->(x) { x.respond_to?(:call) }
|
149
|
+
raise NotImplementedError,
|
150
|
+
"A callable grouper is unsupported"
|
151
|
+
else
|
152
|
+
raise ArgumentError,
|
153
|
+
"Unable to recognize the value for `grouper`: %p" % val
|
106
154
|
end
|
107
155
|
end
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
156
|
+
|
157
|
+
private def init_groups(sort, drop_na)
|
158
|
+
case @grouper
|
159
|
+
when Symbol, String
|
160
|
+
column = @table[@grouper]
|
161
|
+
@indices = (0 ... @table.length).group_by do |i|
|
162
|
+
column.data[i]
|
163
|
+
end
|
164
|
+
when Array
|
165
|
+
@indices = (0 ... @table.length).group_by { |i|
|
166
|
+
@grouper.map {|j| @table[j].data[i] }
|
167
|
+
}
|
168
|
+
when Charty::Vector
|
169
|
+
@indices = (0 ... @table.length).group_by do |i|
|
170
|
+
@grouper.data[i]
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
if drop_na
|
175
|
+
case @grouper
|
176
|
+
when Array
|
177
|
+
@indices.reject! {|key, | key.any? {|k| Util.missing?(k) } }
|
178
|
+
else
|
179
|
+
@indices.reject! {|key, | Util.missing?(key) }
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
if sort
|
184
|
+
@indices = @indices.sort_by {|key, | key }.to_h
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
def indices
|
189
|
+
@indices.dup
|
190
|
+
end
|
191
|
+
|
192
|
+
def group_keys
|
193
|
+
@indices.keys
|
194
|
+
end
|
195
|
+
|
196
|
+
def each_group_key(&block)
|
197
|
+
@indices.each_key(&block)
|
198
|
+
end
|
199
|
+
|
200
|
+
def apply(*args, &block)
|
201
|
+
Charty::Table.new(
|
202
|
+
each_group.map { |_key, table|
|
203
|
+
block.call(table, *args)
|
204
|
+
},
|
205
|
+
index: Charty::Index.new(@indices.keys, name: @grouper)
|
206
|
+
)
|
207
|
+
end
|
208
|
+
|
209
|
+
def each_group
|
210
|
+
return enum_for(__method__) unless block_given?
|
211
|
+
|
212
|
+
@indices.each_key do |key|
|
213
|
+
yield(key, self[key])
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
def [](key)
|
218
|
+
return nil unless @indices.key?(key)
|
219
|
+
|
220
|
+
index = @indices[key]
|
221
|
+
Charty::Table.new(
|
222
|
+
@table.column_names.map {|col|
|
223
|
+
[col, @table[col].values_at(*index)]
|
224
|
+
}.to_h,
|
225
|
+
index: index
|
226
|
+
)
|
117
227
|
end
|
118
228
|
end
|
119
229
|
end
|
@@ -33,6 +33,10 @@ module Charty
|
|
33
33
|
end
|
34
34
|
end
|
35
35
|
|
36
|
+
def group_by(table, grouper, sort, drop_na)
|
37
|
+
Table::HashGroupBy.new(table, grouper, sort, drop_na)
|
38
|
+
end
|
39
|
+
|
36
40
|
def compare_data_equality(other)
|
37
41
|
columns.each do |name|
|
38
42
|
if self[nil, name] != other[nil, name]
|
@@ -42,6 +46,99 @@ module Charty
|
|
42
46
|
true
|
43
47
|
end
|
44
48
|
|
49
|
+
def drop_na
|
50
|
+
# TODO: Must implement this method in each adapter
|
51
|
+
missing_index = index.select do |i|
|
52
|
+
column_names.any? do |key|
|
53
|
+
Util.missing?(self[i, key])
|
54
|
+
end
|
55
|
+
end
|
56
|
+
if missing_index.empty?
|
57
|
+
nil
|
58
|
+
else
|
59
|
+
select_index = index.to_a - missing_index
|
60
|
+
new_data = column_names.map { |key|
|
61
|
+
vals = select_index.map {|i| self[i, key] }
|
62
|
+
[key, vals]
|
63
|
+
}.to_h
|
64
|
+
Charty::Table.new(new_data, index: select_index)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def sort_values(by, na_position: :last)
|
69
|
+
na_cmp_val = check_na_position(na_position)
|
70
|
+
case by
|
71
|
+
when String, Symbol
|
72
|
+
order = (0 ... length).sort do |i, j|
|
73
|
+
a = self[i, by]
|
74
|
+
b = self[j, by]
|
75
|
+
case
|
76
|
+
when Util.missing?(a) # missing > b
|
77
|
+
na_cmp_val
|
78
|
+
when Util.missing?(b) # a < missing
|
79
|
+
-na_cmp_val
|
80
|
+
else
|
81
|
+
cmp = a <=> b
|
82
|
+
if cmp == 0
|
83
|
+
i <=> j
|
84
|
+
else
|
85
|
+
cmp
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
when Array
|
90
|
+
order = (0 ... length).sort do |i, j|
|
91
|
+
cmp = 0
|
92
|
+
by.each do |key|
|
93
|
+
a = self[i, key]
|
94
|
+
b = self[j, key]
|
95
|
+
case
|
96
|
+
when Util.missing?(a) # missing > b
|
97
|
+
cmp = na_cmp_val
|
98
|
+
break
|
99
|
+
when Util.missing?(b) # a < missing
|
100
|
+
cmp = -na_cmp_val
|
101
|
+
break
|
102
|
+
else
|
103
|
+
cmp = a <=> b
|
104
|
+
break if cmp != 0
|
105
|
+
end
|
106
|
+
end
|
107
|
+
if cmp == 0
|
108
|
+
i <=> j
|
109
|
+
else
|
110
|
+
cmp
|
111
|
+
end
|
112
|
+
end
|
113
|
+
else
|
114
|
+
raise ArgumentError,
|
115
|
+
"%p is invalid value for `by`" % by
|
116
|
+
end
|
117
|
+
|
118
|
+
Charty::Table.new(
|
119
|
+
column_names.map { |name|
|
120
|
+
[
|
121
|
+
name,
|
122
|
+
self[nil, name].values_at(*order)
|
123
|
+
]
|
124
|
+
}.to_h,
|
125
|
+
index: index.to_a.values_at(*order)
|
126
|
+
)
|
127
|
+
end
|
128
|
+
|
129
|
+
private def check_na_position(val)
|
130
|
+
case val
|
131
|
+
when :first, "first"
|
132
|
+
-1
|
133
|
+
when :last, "last"
|
134
|
+
1
|
135
|
+
else
|
136
|
+
raise ArgumentError,
|
137
|
+
"`na_position` must be :first or :last",
|
138
|
+
caller
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
45
142
|
private def check_and_convert_index(values, name, expected_length)
|
46
143
|
case values
|
47
144
|
when Index, Range
|
@@ -13,12 +13,19 @@ module Charty
|
|
13
13
|
def initialize(dataset)
|
14
14
|
@table = dataset.to_table
|
15
15
|
@records = []
|
16
|
+
|
17
|
+
self.columns = self.column_names
|
18
|
+
self.index = 0 ... length
|
16
19
|
end
|
17
20
|
|
18
21
|
def data
|
19
22
|
@table
|
20
23
|
end
|
21
24
|
|
25
|
+
def column_length
|
26
|
+
column_names.length
|
27
|
+
end
|
28
|
+
|
22
29
|
def column_names
|
23
30
|
@table.column_names
|
24
31
|
end
|
@@ -20,6 +20,8 @@ module Charty
|
|
20
20
|
end
|
21
21
|
when Hash
|
22
22
|
true
|
23
|
+
when ->(x) { defined?(CSV::Table) && x.is_a?(CSV::Table) }
|
24
|
+
true
|
23
25
|
end
|
24
26
|
end
|
25
27
|
|
@@ -27,6 +29,7 @@ module Charty
|
|
27
29
|
case data
|
28
30
|
when Charty::Vector
|
29
31
|
true
|
32
|
+
# TODO: Use vector adapter to detect them:
|
30
33
|
when Array, method(:daru_vector?), method(:narray_vector?), method(:nmatrix_vector?),
|
31
34
|
method(:numpy_vector?), method(:pandas_series?)
|
32
35
|
true
|
@@ -65,14 +68,22 @@ module Charty
|
|
65
68
|
when Numeric, String, Time, Date
|
66
69
|
arrays = [data]
|
67
70
|
when Hash
|
68
|
-
|
69
|
-
|
71
|
+
columns ||= data.map(&:keys).inject(&:|)
|
72
|
+
arrays = columns.map { [] }
|
73
|
+
data.each do |record|
|
74
|
+
columns.each_with_index do |key, i|
|
75
|
+
arrays[i] << record[key]
|
76
|
+
end
|
77
|
+
end
|
70
78
|
when self.class.method(:array?)
|
71
79
|
unsupported_data_format unless data.all?(&self.class.method(:array?))
|
72
80
|
arrays = data.map(&:to_a).transpose
|
73
81
|
else
|
74
82
|
unsupported_data_format
|
75
83
|
end
|
84
|
+
when ->(x) { defined?(CSV::Table) && x.is_a?(CSV::Table) }
|
85
|
+
columns ||= data.headers
|
86
|
+
arrays = data.headers.map {|x| data[x] }
|
76
87
|
else
|
77
88
|
unsupported_data_format
|
78
89
|
end
|
@@ -88,7 +99,7 @@ module Charty
|
|
88
99
|
|
89
100
|
private def check_data(arrays, columns, index)
|
90
101
|
# NOTE: After Ruby 2.7, we can write the following code by filter_map:
|
91
|
-
# indexes =
|
102
|
+
# indexes = Util.filter_map(arrays) {|ary| ary.index if ary.is_a?(Charty::Vector) }
|
92
103
|
indexes = []
|
93
104
|
arrays.each do |array|
|
94
105
|
index = case array
|
@@ -196,6 +207,11 @@ module Charty
|
|
196
207
|
end
|
197
208
|
end
|
198
209
|
|
210
|
+
def reset_index
|
211
|
+
index_name = index.name || :index
|
212
|
+
Charty::Table.new({ index_name => index.to_a }.merge(data))
|
213
|
+
end
|
214
|
+
|
199
215
|
private def make_data_from_records(data, columns)
|
200
216
|
n_rows = data.length
|
201
217
|
n_columns = data.map(&:size).max
|