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.
Files changed (91) 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 +128 -9
  8. data/Rakefile +4 -5
  9. data/charty.gemspec +7 -2
  10. data/examples/Gemfile +1 -0
  11. data/examples/active_record.ipynb +34 -34
  12. data/examples/daru.ipynb +71 -29
  13. data/examples/iris_dataset.ipynb +12 -5
  14. data/examples/nmatrix.ipynb +30 -30
  15. data/examples/numo_narray.ipynb +245 -0
  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_gruff.ipynb +148 -133
  21. data/examples/sample_images/bar_bokeh.html +85 -0
  22. data/examples/sample_images/barh_bokeh.html +85 -0
  23. data/examples/sample_images/barh_gruff.png +0 -0
  24. data/examples/sample_images/box_plot_bokeh.html +85 -0
  25. data/examples/sample_images/{boxplot_pyplot.png → box_plot_pyplot.png} +0 -0
  26. data/examples/sample_images/curve_bokeh.html +85 -0
  27. data/examples/sample_images/curve_with_function_bokeh.html +85 -0
  28. data/examples/sample_images/{errorbar_pyplot.png → error_bar_pyplot.png} +0 -0
  29. data/examples/sample_images/hist_gruff.png +0 -0
  30. data/examples/sample_images/scatter_bokeh.html +85 -0
  31. data/examples/sample_pyplot.ipynb +37 -35
  32. data/images/penguins_body_mass_g_flipper_length_mm_scatter_plot.png +0 -0
  33. data/images/penguins_body_mass_g_flipper_length_mm_species_scatter_plot.png +0 -0
  34. data/images/penguins_body_mass_g_flipper_length_mm_species_sex_scatter_plot.png +0 -0
  35. data/images/penguins_species_body_mass_g_bar_plot_h.png +0 -0
  36. data/images/penguins_species_body_mass_g_bar_plot_v.png +0 -0
  37. data/images/penguins_species_body_mass_g_box_plot_h.png +0 -0
  38. data/images/penguins_species_body_mass_g_box_plot_v.png +0 -0
  39. data/images/penguins_species_body_mass_g_sex_bar_plot_v.png +0 -0
  40. data/images/penguins_species_body_mass_g_sex_box_plot_v.png +0 -0
  41. data/lib/charty.rb +13 -7
  42. data/lib/charty/backend_methods.rb +8 -0
  43. data/lib/charty/backends.rb +80 -0
  44. data/lib/charty/backends/bokeh.rb +80 -0
  45. data/lib/charty/backends/google_charts.rb +267 -0
  46. data/lib/charty/backends/gruff.rb +104 -67
  47. data/lib/charty/backends/plotly.rb +549 -0
  48. data/lib/charty/backends/pyplot.rb +584 -86
  49. data/lib/charty/backends/rubyplot.rb +82 -74
  50. data/lib/charty/backends/unicode_plot.rb +79 -0
  51. data/lib/charty/index.rb +213 -0
  52. data/lib/charty/linspace.rb +1 -1
  53. data/lib/charty/missing_value_support.rb +14 -0
  54. data/lib/charty/plot_methods.rb +184 -0
  55. data/lib/charty/plotter.rb +57 -41
  56. data/lib/charty/plotters.rb +11 -0
  57. data/lib/charty/plotters/abstract_plotter.rb +156 -0
  58. data/lib/charty/plotters/bar_plotter.rb +216 -0
  59. data/lib/charty/plotters/box_plotter.rb +94 -0
  60. data/lib/charty/plotters/categorical_plotter.rb +380 -0
  61. data/lib/charty/plotters/count_plotter.rb +7 -0
  62. data/lib/charty/plotters/estimation_support.rb +84 -0
  63. data/lib/charty/plotters/random_support.rb +25 -0
  64. data/lib/charty/plotters/relational_plotter.rb +518 -0
  65. data/lib/charty/plotters/scatter_plotter.rb +115 -0
  66. data/lib/charty/plotters/vector_plotter.rb +6 -0
  67. data/lib/charty/statistics.rb +114 -0
  68. data/lib/charty/table.rb +82 -3
  69. data/lib/charty/table_adapters.rb +25 -0
  70. data/lib/charty/table_adapters/active_record_adapter.rb +63 -0
  71. data/lib/charty/table_adapters/base_adapter.rb +69 -0
  72. data/lib/charty/table_adapters/daru_adapter.rb +70 -0
  73. data/lib/charty/table_adapters/datasets_adapter.rb +49 -0
  74. data/lib/charty/table_adapters/hash_adapter.rb +224 -0
  75. data/lib/charty/table_adapters/narray_adapter.rb +76 -0
  76. data/lib/charty/table_adapters/nmatrix_adapter.rb +67 -0
  77. data/lib/charty/table_adapters/pandas_adapter.rb +81 -0
  78. data/lib/charty/vector.rb +69 -0
  79. data/lib/charty/vector_adapters.rb +183 -0
  80. data/lib/charty/vector_adapters/array_adapter.rb +109 -0
  81. data/lib/charty/vector_adapters/daru_adapter.rb +171 -0
  82. data/lib/charty/vector_adapters/narray_adapter.rb +187 -0
  83. data/lib/charty/vector_adapters/nmatrix_adapter.rb +37 -0
  84. data/lib/charty/vector_adapters/numpy_adapter.rb +168 -0
  85. data/lib/charty/vector_adapters/pandas_adapter.rb +200 -0
  86. data/lib/charty/version.rb +1 -1
  87. metadata +127 -13
  88. data/.travis.yml +0 -11
  89. data/examples/numo-narray.ipynb +0 -234
  90. data/lib/charty/backends/google_chart.rb +0 -167
  91. 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 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