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