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
@@ -0,0 +1,70 @@
1
+ require "delegate"
2
+
3
+ module Charty
4
+ module TableAdapters
5
+ class DaruAdapter < BaseAdapter
6
+ TableAdapters.register(:daru, self)
7
+
8
+ extend Forwardable
9
+ include Enumerable
10
+
11
+ def self.supported?(data)
12
+ defined?(Daru::DataFrame) && data.is_a?(Daru::DataFrame)
13
+ end
14
+
15
+ def initialize(data, columns: nil, index: nil)
16
+ @data = check_type(Daru::DataFrame, data, :data)
17
+
18
+ self.columns = columns unless columns.nil?
19
+ self.index = index unless index.nil?
20
+ end
21
+
22
+ attr_reader :data
23
+
24
+ def index
25
+ DaruIndex.new(data.index)
26
+ end
27
+
28
+ def_delegator :data, :index=
29
+
30
+ def columns
31
+ DaruIndex.new(data.vectors)
32
+ end
33
+
34
+ def columns=(values)
35
+ data.vectors = Daru::Index.coerce(values)
36
+ end
37
+
38
+ def column_names
39
+ @data.vectors.to_a
40
+ end
41
+
42
+ def compare_data_equality(other)
43
+ case other
44
+ when DaruAdapter
45
+ data == other.data
46
+ else
47
+ super
48
+ end
49
+ end
50
+
51
+ def [](row, column)
52
+ if row
53
+ @data[column][row]
54
+ else
55
+ column_data = if @data.has_vector?(column)
56
+ @data[column]
57
+ else
58
+ @data[column.to_s]
59
+ end
60
+ Vector.new(column_data)
61
+ end
62
+ end
63
+
64
+ private def check_type(type, data, name)
65
+ return data if data.is_a?(type)
66
+ raise TypeError, "#{name} must be a #{type}"
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,49 @@
1
+ module Charty
2
+ module TableAdapters
3
+ class DatasetsAdapter < BaseAdapter
4
+ TableAdapters.register(:datasets, self)
5
+
6
+ include Enumerable
7
+
8
+ def self.supported?(data)
9
+ defined?(Datasets::Dataset) &&
10
+ data.is_a?(Datasets::Dataset)
11
+ end
12
+
13
+ def initialize(dataset)
14
+ @table = dataset.to_table
15
+ @records = []
16
+ end
17
+
18
+ def data
19
+ @table
20
+ end
21
+
22
+ def column_names
23
+ @table.column_names
24
+ end
25
+
26
+ def length
27
+ data.n_rows
28
+ end
29
+
30
+ def each(&block)
31
+ return to_enum(__method__) unless block_given?
32
+
33
+ @table.each_record(&block)
34
+ end
35
+
36
+ # @param [Integer] row Row index
37
+ # @param [Symbol,String,Integer] column Column index
38
+ def [](row, column)
39
+ if row
40
+ record = @table.find_record(row)
41
+ return nil if record.nil?
42
+ record[column]
43
+ else
44
+ Vector.new(@table[column], index: index, name: column)
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,224 @@
1
+ require 'date'
2
+
3
+ module Charty
4
+ module TableAdapters
5
+ class HashAdapter < BaseAdapter
6
+ TableAdapters.register(:hash, self)
7
+
8
+ def self.supported?(data)
9
+ case data
10
+ when []
11
+ true
12
+ when Array
13
+ case data[0]
14
+ when Numeric, String, Time, Date
15
+ true
16
+ when Hash
17
+ data.all? {|el| el.is_a? Hash }
18
+ when method(:array?)
19
+ data.all?(&method(:array?))
20
+ end
21
+ when Hash
22
+ true
23
+ end
24
+ end
25
+
26
+ def self.array?(data)
27
+ case data
28
+ when Charty::Vector
29
+ true
30
+ when Array, method(:daru_vector?), method(:narray_vector?), method(:nmatrix_vector?),
31
+ method(:numpy_vector?), method(:pandas_series?)
32
+ true
33
+ else
34
+ false
35
+ end
36
+ end
37
+
38
+ def self.daru_vector?(x)
39
+ defined?(Daru::Vector) && x.is_a?(Daru::Vector)
40
+ end
41
+
42
+ def self.narray_vector?(x)
43
+ defined?(Numo::NArray) && x.is_a?(Numo::NArray) && x.ndim == 1
44
+ end
45
+
46
+ def self.nmatrix_vector?(x)
47
+ defined?(NMatrix) && x.is_a?(NMatrix) && x.dim == 1
48
+ end
49
+
50
+ def self.numpy_vector?(x)
51
+ defined?(Numpy::NDArray) && x.is_a?(Numpy::NDArray) && x.ndim == 1
52
+ end
53
+
54
+ def self.pandas_series?(x)
55
+ defined?(Pandas::Series) && x.is_a?(Pandas::Series)
56
+ end
57
+
58
+ def initialize(data, columns: nil, index: nil)
59
+ case data
60
+ when Hash
61
+ arrays = data.values
62
+ columns ||= data.keys
63
+ when Array
64
+ case data[0]
65
+ when Numeric, String, Time, Date
66
+ arrays = [data]
67
+ when Hash
68
+ raise NotImplementedError,
69
+ "an array of records is not supported"
70
+ when self.class.method(:array?)
71
+ unsupported_data_format unless data.all?(&self.class.method(:array?))
72
+ arrays = data.map(&:to_a).transpose
73
+ else
74
+ unsupported_data_format
75
+ end
76
+ else
77
+ unsupported_data_format
78
+ end
79
+
80
+ unless arrays.empty?
81
+ arrays, columns, index = check_data(arrays, columns, index)
82
+ end
83
+
84
+ @data = arrays.map.with_index {|a, i| [columns[i], a] }.to_h
85
+ self.columns = columns unless columns.nil?
86
+ self.index = index unless index.nil?
87
+ end
88
+
89
+ private def check_data(arrays, columns, index)
90
+ # NOTE: After Ruby 2.7, we can write the following code by filter_map:
91
+ # indexes = arrays.filter_map {|ary| ary.index if ary.is_a?(Charty::Vector) }
92
+ indexes = []
93
+ arrays.each do |array|
94
+ index = case array
95
+ when Charty::Vector
96
+ array.index
97
+ when ->(x) { defined?(Daru) && x.is_a?(Daru::Vector) }
98
+ Charty::DaruIndex.new(array.index)
99
+ when ->(x) { defined?(Pandas) && x.is_a?(Pandas::Series) }
100
+ Charty::PandasIndex.new(array.index)
101
+ else
102
+ if index.nil?
103
+ RangeIndex.new(0 ... array.size)
104
+ else
105
+ check_and_convert_index(index, :index, array.size)
106
+ end
107
+ end
108
+ indexes << index
109
+ end
110
+ index = union_indexes(*indexes)
111
+
112
+ arrays = arrays.map do |array|
113
+ case array
114
+ when Charty::Vector
115
+ array.data
116
+ when Hash
117
+ raise NotImplementedError
118
+ when self.class.method(:array?)
119
+ array
120
+ else
121
+ Array.try_convert(array)
122
+ end
123
+ end
124
+
125
+ columns = generate_column_names(arrays.length, columns)
126
+
127
+ return arrays, columns, index
128
+ end
129
+
130
+ private def union_indexes(*indexes)
131
+ result = nil
132
+ while result.nil? && indexes.length > 0
133
+ result = indexes.shift
134
+ end
135
+ indexes.each do |index|
136
+ next if index.nil?
137
+ result = result.union(index)
138
+ end
139
+ result
140
+ end
141
+
142
+ attr_reader :data
143
+
144
+ def_delegator :@data, :keys, :column_names
145
+
146
+ def length
147
+ case
148
+ when column_names.empty?
149
+ 0
150
+ else
151
+ data[column_names[0]].size
152
+ end
153
+ end
154
+
155
+ def column_length
156
+ data.length
157
+ end
158
+
159
+ def compare_data_equality(other)
160
+ case other
161
+ when DaruAdapter, PandasDataFrameAdapter
162
+ other.compare_data_equality(self)
163
+ else
164
+ super
165
+ end
166
+ end
167
+
168
+ def [](row, column)
169
+ if row
170
+ @data[column][row]
171
+ else
172
+ case column
173
+ when Symbol
174
+ sym_key = column
175
+ str_key = column.to_s
176
+ else
177
+ str_key = String.try_convert(column)
178
+ sym_key = str_key.to_sym
179
+ end
180
+
181
+ column_data = if @data.key?(sym_key)
182
+ @data[sym_key]
183
+ else
184
+ @data[str_key]
185
+ end
186
+ Vector.new(column_data, index: index, name: column)
187
+ end
188
+ end
189
+
190
+ def each
191
+ i, n = 0, shape[0]
192
+ while i < n
193
+ record = @data.map {|k, v| v[i] }
194
+ yield record
195
+ i += 1
196
+ end
197
+ end
198
+
199
+ private def make_data_from_records(data, columns)
200
+ n_rows = data.length
201
+ n_columns = data.map(&:size).max
202
+ columns = generate_column_names(n_columns, columns)
203
+ columns.map.with_index { |key, j|
204
+ values = n_rows.times.map {|i| data[i][j] }
205
+ [key, values]
206
+ }.to_h
207
+ end
208
+
209
+ private def generate_column_names(n_columns, columns)
210
+ # FIXME: this is the same as NArrayAdapter#generate_column_names
211
+ columns ||= []
212
+ if columns.length >= n_columns
213
+ columns[0, n_columns]
214
+ else
215
+ columns + columns.length.upto(n_columns - 1).map {|i| "X#{i}" }
216
+ end
217
+ end
218
+
219
+ private def unsupported_data_format
220
+ raise ArgumentError, "Unsupported data format"
221
+ end
222
+ end
223
+ end
224
+ end
@@ -0,0 +1,76 @@
1
+ module Charty
2
+ module TableAdapters
3
+ class NArrayAdapter < BaseAdapter
4
+ TableAdapters.register(:narray, self)
5
+
6
+ def self.supported?(data)
7
+ defined?(Numo::NArray) && data.is_a?(Numo::NArray) && data.ndim <= 2
8
+ end
9
+
10
+ def initialize(data, columns: nil, index: nil)
11
+ case data.ndim
12
+ when 1
13
+ data = data.reshape(data.length, 1)
14
+ when 2
15
+ # do nothing
16
+ else
17
+ raise ArgumentError, "Unsupported data format"
18
+ end
19
+ @data = data
20
+ self.columns = Index.new(generate_column_names(data.shape[1], columns))
21
+ self.index = index || RangeIndex.new(0 ... length)
22
+ end
23
+
24
+ attr_reader :data
25
+
26
+ def length
27
+ data.shape[0]
28
+ end
29
+
30
+ def column_length
31
+ data.shape[1]
32
+ end
33
+
34
+ def compare_data_equality(other)
35
+ case other
36
+ when NArrayAdapter
37
+ data == other.data
38
+ else
39
+ super
40
+ end
41
+ end
42
+
43
+ def [](row, column)
44
+ if row
45
+ @data[row, resolve_column_index(column)]
46
+ else
47
+ column_data = @data[true, resolve_column_index(column)]
48
+ Charty::Vector.new(column_data, index: index, name: column)
49
+ end
50
+ end
51
+
52
+ private def resolve_column_index(column)
53
+ case column
54
+ when String, Symbol
55
+ index = column_names.index(column.to_sym) || column_names.index(column.to_s)
56
+ return index if index
57
+ raise IndexError, "invalid column name: #{column}"
58
+ when Integer
59
+ column
60
+ else
61
+ message = "column must be String or Integer: #{column.inspect}"
62
+ raise ArgumentError, message
63
+ end
64
+ end
65
+
66
+ private def generate_column_names(n_columns, columns)
67
+ columns ||= []
68
+ if columns.length >= n_columns
69
+ columns[0, n_columns]
70
+ else
71
+ columns + columns.length.upto(n_columns - 1).map {|i| "X#{i}" }
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,67 @@
1
+ module Charty
2
+ module TableAdapters
3
+ class NMatrixAdapter < BaseAdapter
4
+ TableAdapters.register(:nmatrix, self)
5
+
6
+ def self.supported?(data)
7
+ defined?(NMatrix) && data.is_a?(NMatrix) && data.shape.length <= 2
8
+ end
9
+
10
+ def initialize(data, columns: nil)
11
+ case data.shape.length
12
+ when 1
13
+ data = data.reshape(data.size, 1)
14
+ when 2
15
+ # do nothing
16
+ else
17
+ raise ArgumentError, "Unsupported data format"
18
+ end
19
+ @data = data
20
+ self.columns = Index.new(generate_column_names(data.shape[1], columns))
21
+ self.index = index || RangeIndex.new(0 ... length)
22
+ end
23
+
24
+ attr_reader :data
25
+
26
+ def length
27
+ data.shape[0]
28
+ end
29
+
30
+ def column_length
31
+ data.shape[1]
32
+ end
33
+
34
+ def [](row, column)
35
+ if row
36
+ @data[row, resolve_column_index(column)]
37
+ else
38
+ column_data = @data[:*, resolve_column_index(column)].reshape([@data.shape[0]])
39
+ Charty::Vector.new(column_data, index: index, name: column)
40
+ end
41
+ end
42
+
43
+ private def resolve_column_index(column)
44
+ case column
45
+ when String, Symbol
46
+ index = column_names.index(column.to_sym) || column_names.index(column.to_s)
47
+ return index if index
48
+ raise IndexError, "invalid column name: #{column}"
49
+ when Integer
50
+ column
51
+ else
52
+ message = "column must be String or Integer: #{column.inspect}"
53
+ raise ArgumentError, message
54
+ end
55
+ end
56
+
57
+ private def generate_column_names(n_columns, columns)
58
+ columns ||= []
59
+ if columns.length >= n_columns
60
+ columns[0, n_columns]
61
+ else
62
+ columns + columns.length.upto(n_columns - 1).map {|i| "X#{i}" }
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end