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
@@ -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
|