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
@@ -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 = Util.filter_map(arrays) {|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