charty 0.1.5.dev → 0.2.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (87) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +71 -0
  3. data/.github/workflows/nmatrix.yml +67 -0
  4. data/.github/workflows/pycall.yml +86 -0
  5. data/Dockerfile.dev +9 -1
  6. data/Gemfile +18 -0
  7. data/README.md +176 -9
  8. data/Rakefile +4 -5
  9. data/charty.gemspec +10 -1
  10. data/examples/Gemfile +1 -0
  11. data/examples/active_record.ipynb +1 -1
  12. data/examples/daru.ipynb +1 -1
  13. data/examples/iris_dataset.ipynb +1 -1
  14. data/examples/nmatrix.ipynb +1 -1
  15. data/examples/{numo-narray.ipynb → numo_narray.ipynb} +1 -1
  16. data/examples/palette.rb +71 -0
  17. data/examples/sample.png +0 -0
  18. data/examples/sample_bokeh.ipynb +156 -0
  19. data/examples/sample_google_chart.ipynb +229 -68
  20. data/examples/sample_images/bar_bokeh.html +85 -0
  21. data/examples/sample_images/barh_bokeh.html +85 -0
  22. data/examples/sample_images/box_plot_bokeh.html +85 -0
  23. data/examples/sample_images/curve_bokeh.html +85 -0
  24. data/examples/sample_images/curve_with_function_bokeh.html +85 -0
  25. data/examples/sample_images/hist_gruff.png +0 -0
  26. data/examples/sample_images/scatter_bokeh.html +85 -0
  27. data/examples/sample_pyplot.ipynb +40 -38
  28. data/images/penguins_body_mass_g_flipper_length_mm_scatter_plot.png +0 -0
  29. data/images/penguins_body_mass_g_flipper_length_mm_species_scatter_plot.png +0 -0
  30. data/images/penguins_body_mass_g_flipper_length_mm_species_sex_scatter_plot.png +0 -0
  31. data/images/penguins_species_body_mass_g_bar_plot_h.png +0 -0
  32. data/images/penguins_species_body_mass_g_bar_plot_v.png +0 -0
  33. data/images/penguins_species_body_mass_g_box_plot_h.png +0 -0
  34. data/images/penguins_species_body_mass_g_box_plot_v.png +0 -0
  35. data/images/penguins_species_body_mass_g_sex_bar_plot_v.png +0 -0
  36. data/images/penguins_species_body_mass_g_sex_box_plot_v.png +0 -0
  37. data/lib/charty.rb +14 -1
  38. data/lib/charty/backend_methods.rb +8 -0
  39. data/lib/charty/backends.rb +80 -0
  40. data/lib/charty/backends/bokeh.rb +32 -26
  41. data/lib/charty/backends/google_charts.rb +267 -0
  42. data/lib/charty/backends/gruff.rb +102 -83
  43. data/lib/charty/backends/plotly.rb +685 -0
  44. data/lib/charty/backends/pyplot.rb +586 -92
  45. data/lib/charty/backends/rubyplot.rb +82 -74
  46. data/lib/charty/backends/unicode_plot.rb +79 -0
  47. data/lib/charty/index.rb +213 -0
  48. data/lib/charty/linspace.rb +1 -1
  49. data/lib/charty/missing_value_support.rb +14 -0
  50. data/lib/charty/plot_methods.rb +184 -0
  51. data/lib/charty/plotter.rb +48 -40
  52. data/lib/charty/plotters.rb +11 -0
  53. data/lib/charty/plotters/abstract_plotter.rb +183 -0
  54. data/lib/charty/plotters/bar_plotter.rb +201 -0
  55. data/lib/charty/plotters/box_plotter.rb +79 -0
  56. data/lib/charty/plotters/categorical_plotter.rb +380 -0
  57. data/lib/charty/plotters/count_plotter.rb +7 -0
  58. data/lib/charty/plotters/estimation_support.rb +84 -0
  59. data/lib/charty/plotters/random_support.rb +25 -0
  60. data/lib/charty/plotters/relational_plotter.rb +518 -0
  61. data/lib/charty/plotters/scatter_plotter.rb +104 -0
  62. data/lib/charty/plotters/vector_plotter.rb +6 -0
  63. data/lib/charty/statistics.rb +114 -0
  64. data/lib/charty/table.rb +80 -3
  65. data/lib/charty/table_adapters.rb +25 -0
  66. data/lib/charty/table_adapters/active_record_adapter.rb +63 -0
  67. data/lib/charty/table_adapters/base_adapter.rb +69 -0
  68. data/lib/charty/table_adapters/daru_adapter.rb +70 -0
  69. data/lib/charty/table_adapters/datasets_adapter.rb +49 -0
  70. data/lib/charty/table_adapters/hash_adapter.rb +224 -0
  71. data/lib/charty/table_adapters/narray_adapter.rb +76 -0
  72. data/lib/charty/table_adapters/nmatrix_adapter.rb +67 -0
  73. data/lib/charty/table_adapters/pandas_adapter.rb +81 -0
  74. data/lib/charty/util.rb +20 -0
  75. data/lib/charty/vector.rb +69 -0
  76. data/lib/charty/vector_adapters.rb +183 -0
  77. data/lib/charty/vector_adapters/array_adapter.rb +109 -0
  78. data/lib/charty/vector_adapters/daru_adapter.rb +171 -0
  79. data/lib/charty/vector_adapters/narray_adapter.rb +187 -0
  80. data/lib/charty/vector_adapters/nmatrix_adapter.rb +37 -0
  81. data/lib/charty/vector_adapters/numpy_adapter.rb +168 -0
  82. data/lib/charty/vector_adapters/pandas_adapter.rb +200 -0
  83. data/lib/charty/version.rb +1 -1
  84. metadata +179 -10
  85. data/.travis.yml +0 -11
  86. data/lib/charty/backends/google_chart.rb +0 -167
  87. data/lib/charty/plotter_adapter.rb +0 -17
@@ -1,94 +1,102 @@
1
- require 'rubyplot'
1
+ require 'fileutils'
2
2
 
3
3
  module Charty
4
- class Rubyplot < PlotterAdapter
5
- Name = "rubyplot"
4
+ module Backends
5
+ class Rubyplot
6
+ Backends.register(:rubyplot, self)
6
7
 
7
- def initialize
8
- @plot = ::Rubyplot
9
- end
8
+ class << self
9
+ def prepare
10
+ require 'rubyplot'
11
+ end
12
+ end
10
13
 
11
- def label(x, y)
12
- end
14
+ def initialize
15
+ @plot = ::Rubyplot
16
+ end
13
17
 
14
- def series=(series)
15
- @series = series
16
- end
18
+ def label(x, y)
19
+ end
20
+
21
+ def series=(series)
22
+ @series = series
23
+ end
17
24
 
18
- def render_layout(layout)
19
- (fig, axes) = *@plot.subplots(nrows: layout.num_rows, ncols: layout.num_cols)
20
- layout.rows.each_with_index do |row, y|
21
- row.each_with_index do |cel, x|
22
- plot = layout.num_rows > 1 ? axes[y][x] : axes[x]
23
- plot(plot, cel)
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
- def render(context, filename="")
30
- FileUtils.mkdir_p(File.dirname(filename))
31
- plot(@plot, context).write(filename)
32
- end
36
+ def old_style_render(context, filename="")
37
+ FileUtils.mkdir_p(File.dirname(filename))
38
+ plot(@plot, context).write(filename)
39
+ end
33
40
 
34
- def plot(plot, context)
35
- # case
36
- # when plot.respond_to?(:xlim)
37
- # plot.xlim(context.range_x.begin, context.range_x.end)
38
- # plot.ylim(context.range_y.begin, context.range_y.end)
39
- # when plot.respond_to?(:set_xlim)
40
- # plot.set_xlim(context.range_x.begin, context.range_x.end)
41
- # plot.set_ylim(context.range_y.begin, context.range_y.end)
42
- # end
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
- figure = ::Rubyplot::Figure.new
45
- axes = figure.add_subplot 0,0
46
- axes.title = context.title if context.title
47
- axes.x_title = context.xlabel if context.xlabel
48
- axes.y_title = context.ylabel if context.ylabel
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
- case context.method
51
- when :bar
52
- context.series.each do |data|
53
- axes.bar! do |p|
54
- p.data(data.xs.to_a)
55
- p.label = data.label
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
- end
58
- figure
59
- when :barh
60
- raise NotImplementedError
61
- when :box_plot
62
- raise NotImplementedError
63
- when :bubble
64
- context.series.each do |data|
65
- axes.bubble! do |p|
66
- p.data(data.xs.to_a, data.ys.to_a, data.zs.to_a)
67
- p.label = data.label if data.label
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
- end
70
- figure
71
- when :curve
72
- context.series.each do |data|
73
- axes.line! do |p|
74
- p.data(data.xs.to_a, data.ys.to_a)
75
- p.label = data.label if data.label
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
- end
78
- figure
79
- when :scatter
80
- context.series.each do |data|
81
- axes.scatter! do |p|
82
- p.data(data.xs.to_a, data.ys.to_a)
83
- p.label = data.label if data.label
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
@@ -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