charty 0.1.4.dev → 0.2.4
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 +71 -0
- 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 +128 -9
- data/Rakefile +4 -5
- data/charty.gemspec +7 -2
- data/examples/Gemfile +1 -0
- data/examples/active_record.ipynb +34 -34
- data/examples/daru.ipynb +71 -29
- data/examples/iris_dataset.ipynb +12 -5
- data/examples/nmatrix.ipynb +30 -30
- data/examples/numo_narray.ipynb +245 -0
- data/examples/palette.rb +71 -0
- data/examples/sample.png +0 -0
- data/examples/sample_bokeh.ipynb +156 -0
- data/examples/sample_google_chart.ipynb +229 -68
- data/examples/sample_gruff.ipynb +148 -133
- data/examples/sample_images/bar_bokeh.html +85 -0
- data/examples/sample_images/barh_bokeh.html +85 -0
- data/examples/sample_images/barh_gruff.png +0 -0
- data/examples/sample_images/box_plot_bokeh.html +85 -0
- data/examples/sample_images/{boxplot_pyplot.png → box_plot_pyplot.png} +0 -0
- data/examples/sample_images/curve_bokeh.html +85 -0
- data/examples/sample_images/curve_with_function_bokeh.html +85 -0
- data/examples/sample_images/{errorbar_pyplot.png → error_bar_pyplot.png} +0 -0
- data/examples/sample_images/hist_gruff.png +0 -0
- data/examples/sample_images/scatter_bokeh.html +85 -0
- data/examples/sample_pyplot.ipynb +37 -35
- 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 +13 -7
- data/lib/charty/backend_methods.rb +8 -0
- data/lib/charty/backends.rb +80 -0
- data/lib/charty/backends/bokeh.rb +80 -0
- data/lib/charty/backends/google_charts.rb +267 -0
- data/lib/charty/backends/gruff.rb +104 -67
- data/lib/charty/backends/plotly.rb +549 -0
- data/lib/charty/backends/pyplot.rb +584 -86
- data/lib/charty/backends/rubyplot.rb +82 -74
- data/lib/charty/backends/unicode_plot.rb +79 -0
- data/lib/charty/index.rb +213 -0
- data/lib/charty/linspace.rb +1 -1
- data/lib/charty/missing_value_support.rb +14 -0
- data/lib/charty/plot_methods.rb +184 -0
- data/lib/charty/plotter.rb +57 -41
- data/lib/charty/plotters.rb +11 -0
- data/lib/charty/plotters/abstract_plotter.rb +156 -0
- data/lib/charty/plotters/bar_plotter.rb +216 -0
- data/lib/charty/plotters/box_plotter.rb +94 -0
- data/lib/charty/plotters/categorical_plotter.rb +380 -0
- data/lib/charty/plotters/count_plotter.rb +7 -0
- data/lib/charty/plotters/estimation_support.rb +84 -0
- data/lib/charty/plotters/random_support.rb +25 -0
- data/lib/charty/plotters/relational_plotter.rb +518 -0
- data/lib/charty/plotters/scatter_plotter.rb +115 -0
- data/lib/charty/plotters/vector_plotter.rb +6 -0
- data/lib/charty/statistics.rb +114 -0
- data/lib/charty/table.rb +82 -3
- data/lib/charty/table_adapters.rb +25 -0
- data/lib/charty/table_adapters/active_record_adapter.rb +63 -0
- data/lib/charty/table_adapters/base_adapter.rb +69 -0
- data/lib/charty/table_adapters/daru_adapter.rb +70 -0
- data/lib/charty/table_adapters/datasets_adapter.rb +49 -0
- data/lib/charty/table_adapters/hash_adapter.rb +224 -0
- data/lib/charty/table_adapters/narray_adapter.rb +76 -0
- data/lib/charty/table_adapters/nmatrix_adapter.rb +67 -0
- data/lib/charty/table_adapters/pandas_adapter.rb +81 -0
- data/lib/charty/vector.rb +69 -0
- data/lib/charty/vector_adapters.rb +183 -0
- data/lib/charty/vector_adapters/array_adapter.rb +109 -0
- data/lib/charty/vector_adapters/daru_adapter.rb +171 -0
- data/lib/charty/vector_adapters/narray_adapter.rb +187 -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 +200 -0
- data/lib/charty/version.rb +1 -1
- metadata +127 -13
- data/.travis.yml +0 -11
- data/examples/numo-narray.ipynb +0 -234
- data/lib/charty/backends/google_chart.rb +0 -167
- data/lib/charty/plotter_adapter.rb +0 -17
@@ -1,94 +1,102 @@
|
|
1
|
-
require '
|
1
|
+
require 'fileutils'
|
2
2
|
|
3
3
|
module Charty
|
4
|
-
|
5
|
-
|
4
|
+
module Backends
|
5
|
+
class Rubyplot
|
6
|
+
Backends.register(:rubyplot, self)
|
6
7
|
|
7
|
-
|
8
|
-
|
9
|
-
|
8
|
+
class << self
|
9
|
+
def prepare
|
10
|
+
require 'rubyplot'
|
11
|
+
end
|
12
|
+
end
|
10
13
|
|
11
|
-
|
12
|
-
|
14
|
+
def initialize
|
15
|
+
@plot = ::Rubyplot
|
16
|
+
end
|
13
17
|
|
14
|
-
|
15
|
-
|
16
|
-
|
18
|
+
def label(x, y)
|
19
|
+
end
|
20
|
+
|
21
|
+
def series=(series)
|
22
|
+
@series = series
|
23
|
+
end
|
17
24
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
25
|
+
def render_layout(layout)
|
26
|
+
(_fig, axes) = *@plot.subplots(nrows: layout.num_rows, ncols: layout.num_cols)
|
27
|
+
layout.rows.each_with_index do |row, y|
|
28
|
+
row.each_with_index do |cel, x|
|
29
|
+
plot = layout.num_rows > 1 ? axes[y][x] : axes[x]
|
30
|
+
plot(plot, cel)
|
31
|
+
end
|
24
32
|
end
|
33
|
+
@plot.show
|
25
34
|
end
|
26
|
-
@plot.show
|
27
|
-
end
|
28
35
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
36
|
+
def render(context, filename="")
|
37
|
+
FileUtils.mkdir_p(File.dirname(filename))
|
38
|
+
plot(@plot, context).write(filename)
|
39
|
+
end
|
33
40
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
41
|
+
def plot(plot, context)
|
42
|
+
# case
|
43
|
+
# when plot.respond_to?(:xlim)
|
44
|
+
# plot.xlim(context.range_x.begin, context.range_x.end)
|
45
|
+
# plot.ylim(context.range_y.begin, context.range_y.end)
|
46
|
+
# when plot.respond_to?(:set_xlim)
|
47
|
+
# plot.set_xlim(context.range_x.begin, context.range_x.end)
|
48
|
+
# plot.set_ylim(context.range_y.begin, context.range_y.end)
|
49
|
+
# end
|
43
50
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
51
|
+
figure = ::Rubyplot::Figure.new
|
52
|
+
axes = figure.add_subplot 0,0
|
53
|
+
axes.title = context.title if context.title
|
54
|
+
axes.x_title = context.xlabel if context.xlabel
|
55
|
+
axes.y_title = context.ylabel if context.ylabel
|
49
56
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
57
|
+
case context.method
|
58
|
+
when :bar
|
59
|
+
context.series.each do |data|
|
60
|
+
axes.bar! do |p|
|
61
|
+
p.data(data.xs.to_a)
|
62
|
+
p.label = data.label
|
63
|
+
end
|
56
64
|
end
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
65
|
+
figure
|
66
|
+
when :barh
|
67
|
+
raise NotImplementedError
|
68
|
+
when :box_plot
|
69
|
+
raise NotImplementedError
|
70
|
+
when :bubble
|
71
|
+
context.series.each do |data|
|
72
|
+
axes.bubble! do |p|
|
73
|
+
p.data(data.xs.to_a, data.ys.to_a, data.zs.to_a)
|
74
|
+
p.label = data.label if data.label
|
75
|
+
end
|
68
76
|
end
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
77
|
+
figure
|
78
|
+
when :curve
|
79
|
+
context.series.each do |data|
|
80
|
+
axes.line! do |p|
|
81
|
+
p.data(data.xs.to_a, data.ys.to_a)
|
82
|
+
p.label = data.label if data.label
|
83
|
+
end
|
76
84
|
end
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
85
|
+
figure
|
86
|
+
when :scatter
|
87
|
+
context.series.each do |data|
|
88
|
+
axes.scatter! do |p|
|
89
|
+
p.data(data.xs.to_a, data.ys.to_a)
|
90
|
+
p.label = data.label if data.label
|
91
|
+
end
|
84
92
|
end
|
93
|
+
figure
|
94
|
+
when :error_bar
|
95
|
+
# refs. https://github.com/SciRuby/rubyplot/issues/26
|
96
|
+
raise NotImplementedError
|
97
|
+
when :hist
|
98
|
+
raise NotImplementedError
|
85
99
|
end
|
86
|
-
figure
|
87
|
-
when :error_bar
|
88
|
-
# refs. https://github.com/SciRuby/rubyplot/issues/26
|
89
|
-
raise NotImplementedError
|
90
|
-
when :hist
|
91
|
-
raise NotImplementedError
|
92
100
|
end
|
93
101
|
end
|
94
102
|
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'stringio'
|
2
|
+
|
3
|
+
module Charty
|
4
|
+
module Backends
|
5
|
+
class UnicodePlot
|
6
|
+
Backends.register(:unicode_plot, self)
|
7
|
+
|
8
|
+
class << self
|
9
|
+
def prepare
|
10
|
+
require 'unicode_plot'
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def begin_figure
|
15
|
+
@figure = nil
|
16
|
+
@layout = {}
|
17
|
+
end
|
18
|
+
|
19
|
+
def bar(bar_pos, values, color: nil)
|
20
|
+
@figure = {
|
21
|
+
type: :bar,
|
22
|
+
bar_pos: bar_pos,
|
23
|
+
values: values
|
24
|
+
}
|
25
|
+
end
|
26
|
+
|
27
|
+
def box_plot(plot_data, positions, color:, gray:)
|
28
|
+
@figure = { type: :box, data: plot_data }
|
29
|
+
end
|
30
|
+
|
31
|
+
def set_xlabel(label)
|
32
|
+
@layout[:xlabel] = label
|
33
|
+
end
|
34
|
+
|
35
|
+
def set_ylabel(label)
|
36
|
+
@layout[:ylabel] = label
|
37
|
+
end
|
38
|
+
|
39
|
+
def set_xticks(values)
|
40
|
+
@layout[:xticks] = values
|
41
|
+
end
|
42
|
+
|
43
|
+
def set_xtick_labels(values)
|
44
|
+
@layout[:xtick_labels] = values
|
45
|
+
end
|
46
|
+
|
47
|
+
def set_xlim(min, max)
|
48
|
+
@layout[:xlim] = [min, max]
|
49
|
+
end
|
50
|
+
|
51
|
+
def disable_xaxis_grid
|
52
|
+
# do nothing
|
53
|
+
end
|
54
|
+
|
55
|
+
def show
|
56
|
+
case @figure[:type]
|
57
|
+
when :bar
|
58
|
+
plot = ::UnicodePlot.barplot(@layout[:xtick_labels], @figure[:values], xlabel: @layout[:xlabel])
|
59
|
+
when :box
|
60
|
+
plot = ::UnicodePlot.boxplot(@layout[:xtick_labels], @figure[:data], xlabel: @layout[:xlabel])
|
61
|
+
end
|
62
|
+
sio = StringIO.new
|
63
|
+
class << sio
|
64
|
+
def tty?; true; end
|
65
|
+
end
|
66
|
+
plot.render(sio)
|
67
|
+
sio.string
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
def show_bar(sio, figure, i)
|
73
|
+
end
|
74
|
+
|
75
|
+
def show_box(sio, figure, i)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
data/lib/charty/index.rb
ADDED
@@ -0,0 +1,213 @@
|
|
1
|
+
require "forwardable"
|
2
|
+
|
3
|
+
module Charty
|
4
|
+
class Index
|
5
|
+
extend Forwardable
|
6
|
+
include Enumerable
|
7
|
+
|
8
|
+
def initialize(values, name: nil)
|
9
|
+
@values = values
|
10
|
+
@name = name
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_reader :values
|
14
|
+
attr_accessor :name
|
15
|
+
|
16
|
+
def_delegators :values, :length, :size, :each, :to_a
|
17
|
+
|
18
|
+
def ==(other)
|
19
|
+
case other
|
20
|
+
when DaruIndex, PandasIndex
|
21
|
+
return false if length != other.length
|
22
|
+
to_a == other.to_a
|
23
|
+
when Index
|
24
|
+
return false if length != other.length
|
25
|
+
return true if values == other.values
|
26
|
+
to_a == other.to_a
|
27
|
+
else
|
28
|
+
super
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def [](i)
|
33
|
+
case i
|
34
|
+
when 0 ... length
|
35
|
+
values[i]
|
36
|
+
else
|
37
|
+
raise IndexError, "index out of range"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def loc(key)
|
42
|
+
values.index(key)
|
43
|
+
end
|
44
|
+
|
45
|
+
def union(other)
|
46
|
+
case other
|
47
|
+
when PandasIndex
|
48
|
+
index = PandasIndex.try_convert(self)
|
49
|
+
return index.union(other) if index
|
50
|
+
end
|
51
|
+
|
52
|
+
Index.new(to_a.union(other.to_a))
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
class RangeIndex < Index
|
57
|
+
def initialize(values, name: nil)
|
58
|
+
if values.is_a?(Range) && values.begin.is_a?(Integer) && values.end.is_a?(Integer)
|
59
|
+
super
|
60
|
+
else
|
61
|
+
raise ArgumentError, "values must be an integer range"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def length
|
66
|
+
size
|
67
|
+
end
|
68
|
+
|
69
|
+
def [](i)
|
70
|
+
case i
|
71
|
+
when 0 ... length
|
72
|
+
values.begin + i
|
73
|
+
else
|
74
|
+
raise IndexError, "index out of range (#{i} for 0 ... #{length})"
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def loc(key)
|
79
|
+
case key
|
80
|
+
when Integer
|
81
|
+
if values.cover?(key)
|
82
|
+
return key - values.begin
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def union(other)
|
88
|
+
case other
|
89
|
+
when RangeIndex
|
90
|
+
return union(other.values)
|
91
|
+
when Range
|
92
|
+
if disjoint_range?(values, other)
|
93
|
+
return Index.new(values.to_a.union(other.to_a))
|
94
|
+
end
|
95
|
+
new_beg = [values.begin, other.begin].min
|
96
|
+
new_end = [values.end, other.end ].max
|
97
|
+
new_range = if values.end < new_end
|
98
|
+
if other.exclude_end?
|
99
|
+
new_beg ... new_end
|
100
|
+
else
|
101
|
+
new_beg .. new_end
|
102
|
+
end
|
103
|
+
elsif other.end < new_end
|
104
|
+
if values.exclude_end?
|
105
|
+
new_beg ... new_end
|
106
|
+
else
|
107
|
+
new_beg .. new_end
|
108
|
+
end
|
109
|
+
else
|
110
|
+
if values.exclude_end? && other.exclude_end?
|
111
|
+
new_beg ... new_end
|
112
|
+
else
|
113
|
+
new_beg .. new_end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
RangeIndex.new(new_range)
|
117
|
+
else
|
118
|
+
super
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
private def disjoint_range?(r1, r2)
|
123
|
+
r1.end < r2.begin || r2.end < r1.begin
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
class DaruIndex < Index
|
128
|
+
def_delegators :values, :name, :name=
|
129
|
+
|
130
|
+
def length
|
131
|
+
size
|
132
|
+
end
|
133
|
+
|
134
|
+
def ==(other)
|
135
|
+
case other
|
136
|
+
when DaruIndex
|
137
|
+
values == other.values
|
138
|
+
else
|
139
|
+
super
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
class PandasIndex < Index
|
145
|
+
def self.try_convert(obj)
|
146
|
+
case obj
|
147
|
+
when PandasIndex
|
148
|
+
obj
|
149
|
+
when ->(x) { defined?(Pandas) && x.is_a?(Pandas::Index) }
|
150
|
+
PandasIndex.new(obj)
|
151
|
+
when RangeIndex, Range
|
152
|
+
obj = obj.values if obj.is_a?(RangeIndex)
|
153
|
+
stop = if obj.exclude_end?
|
154
|
+
obj.end
|
155
|
+
else
|
156
|
+
obj.end + 1
|
157
|
+
end
|
158
|
+
PandasIndex.new(Pandas.RangeIndex.new(obj.begin, stop))
|
159
|
+
when ->(x) { defined?(Enumerator::ArithmeticSequence) && x.is_a?(Enumerator::ArithmeticSequence) }
|
160
|
+
stop = if obj.exclude_end?
|
161
|
+
obj.end
|
162
|
+
else
|
163
|
+
obj.end + 1
|
164
|
+
end
|
165
|
+
PandasIndex.new(Pandas::RangeIndex.new(obj.begin, stop, obj.step))
|
166
|
+
when Index, Array, DaruIndex, ->(x) { defined?(Daru) && x.is_a?(Daru::Index) }
|
167
|
+
obj = obj.values if obj.is_a?(Index)
|
168
|
+
PandasIndex.new(Pandas::Index.new(obj.to_a))
|
169
|
+
else
|
170
|
+
nil
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
def_delegators :values, :name, :name=
|
175
|
+
|
176
|
+
def length
|
177
|
+
size
|
178
|
+
end
|
179
|
+
|
180
|
+
def ==(other)
|
181
|
+
case other
|
182
|
+
when PandasIndex
|
183
|
+
Numpy.all(values == other.values)
|
184
|
+
when Index
|
185
|
+
return false if length != other.length
|
186
|
+
Numpy.all(values == other.values.to_a)
|
187
|
+
else
|
188
|
+
super
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
def each(&block)
|
193
|
+
return enum_for(__method__) unless block_given?
|
194
|
+
|
195
|
+
i, n = 0, length
|
196
|
+
while i < n
|
197
|
+
yield self[i]
|
198
|
+
i += 1
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
def union(other)
|
203
|
+
other = PandasIndex.try_convert(other)
|
204
|
+
# NOTE: Using `sort=False` in pandas.Index#union does not produce pandas.RangeIndex.
|
205
|
+
# TODO: Reconsider to use `sort=True` here.
|
206
|
+
PandasIndex.new(values.union(other.values, sort: false))
|
207
|
+
end
|
208
|
+
|
209
|
+
private def arithmetic_sequence?(x)
|
210
|
+
defined?(Enumerator::ArithmeticSequence) && x.is_a?(Enumerator::ArithmeticSequence)
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|