charty 0.2.0 → 0.2.6
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 +177 -9
- data/Rakefile +4 -5
- data/charty.gemspec +10 -4
- data/examples/Gemfile +1 -0
- data/examples/active_record.ipynb +1 -1
- data/examples/daru.ipynb +1 -1
- data/examples/iris_dataset.ipynb +1 -1
- data/examples/nmatrix.ipynb +1 -1
- data/examples/{numo-narray.ipynb → numo_narray.ipynb} +1 -1
- data/examples/palette.rb +71 -0
- data/examples/sample.png +0 -0
- data/examples/sample_images/hist_gruff.png +0 -0
- data/examples/sample_pyplot.ipynb +40 -38
- 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 -1
- data/lib/charty/backend_methods.rb +8 -0
- data/lib/charty/backends.rb +26 -1
- data/lib/charty/backends/bokeh.rb +31 -31
- data/lib/charty/backends/{google_chart.rb → google_charts.rb} +75 -33
- data/lib/charty/backends/gruff.rb +14 -3
- data/lib/charty/backends/plotly.rb +774 -9
- data/lib/charty/backends/pyplot.rb +611 -34
- data/lib/charty/backends/rubyplot.rb +2 -2
- data/lib/charty/backends/unicode_plot.rb +79 -0
- data/lib/charty/dash_pattern_generator.rb +57 -0
- data/lib/charty/index.rb +213 -0
- data/lib/charty/linspace.rb +1 -1
- data/lib/charty/plot_methods.rb +254 -0
- data/lib/charty/plotter.rb +10 -10
- data/lib/charty/plotters.rb +12 -0
- data/lib/charty/plotters/abstract_plotter.rb +243 -0
- data/lib/charty/plotters/bar_plotter.rb +201 -0
- data/lib/charty/plotters/box_plotter.rb +79 -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/line_plotter.rb +300 -0
- data/lib/charty/plotters/random_support.rb +25 -0
- data/lib/charty/plotters/relational_plotter.rb +635 -0
- data/lib/charty/plotters/scatter_plotter.rb +80 -0
- data/lib/charty/plotters/vector_plotter.rb +6 -0
- data/lib/charty/statistics.rb +114 -0
- data/lib/charty/table.rb +161 -15
- data/lib/charty/table_adapters.rb +2 -0
- data/lib/charty/table_adapters/active_record_adapter.rb +17 -9
- data/lib/charty/table_adapters/base_adapter.rb +166 -0
- data/lib/charty/table_adapters/daru_adapter.rb +41 -3
- data/lib/charty/table_adapters/datasets_adapter.rb +17 -2
- data/lib/charty/table_adapters/hash_adapter.rb +143 -16
- data/lib/charty/table_adapters/narray_adapter.rb +25 -6
- data/lib/charty/table_adapters/nmatrix_adapter.rb +15 -5
- data/lib/charty/table_adapters/pandas_adapter.rb +163 -0
- data/lib/charty/util.rb +28 -0
- data/lib/charty/vector.rb +69 -0
- data/lib/charty/vector_adapters.rb +187 -0
- data/lib/charty/vector_adapters/array_adapter.rb +101 -0
- data/lib/charty/vector_adapters/daru_adapter.rb +163 -0
- data/lib/charty/vector_adapters/narray_adapter.rb +182 -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 +199 -0
- data/lib/charty/version.rb +1 -1
- metadata +121 -22
- data/.travis.yml +0 -10
@@ -0,0 +1,166 @@
|
|
1
|
+
require "forwardable"
|
2
|
+
|
3
|
+
module Charty
|
4
|
+
module TableAdapters
|
5
|
+
class BaseAdapter
|
6
|
+
extend Forwardable
|
7
|
+
include Enumerable
|
8
|
+
|
9
|
+
attr_reader :columns
|
10
|
+
|
11
|
+
def columns=(values)
|
12
|
+
@columns = check_and_convert_index(values, :columns, column_length)
|
13
|
+
end
|
14
|
+
|
15
|
+
def column_names
|
16
|
+
columns.to_a
|
17
|
+
end
|
18
|
+
|
19
|
+
attr_reader :index
|
20
|
+
|
21
|
+
def index=(values)
|
22
|
+
@index = check_and_convert_index(values, :index, length)
|
23
|
+
end
|
24
|
+
|
25
|
+
def ==(other)
|
26
|
+
case other
|
27
|
+
when BaseAdapter
|
28
|
+
return false if columns != other.columns
|
29
|
+
return false if index != other.index
|
30
|
+
compare_data_equality(other)
|
31
|
+
else
|
32
|
+
false
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def group_by(table, grouper, sort, drop_na)
|
37
|
+
Table::HashGroupBy.new(table, grouper, sort, drop_na)
|
38
|
+
end
|
39
|
+
|
40
|
+
def compare_data_equality(other)
|
41
|
+
columns.each do |name|
|
42
|
+
if self[nil, name] != other[nil, name]
|
43
|
+
return false
|
44
|
+
end
|
45
|
+
end
|
46
|
+
true
|
47
|
+
end
|
48
|
+
|
49
|
+
def drop_na
|
50
|
+
# TODO: Must implement this method in each adapter
|
51
|
+
missing_index = index.select do |i|
|
52
|
+
column_names.any? do |key|
|
53
|
+
Util.missing?(self[i, key])
|
54
|
+
end
|
55
|
+
end
|
56
|
+
if missing_index.empty?
|
57
|
+
nil
|
58
|
+
else
|
59
|
+
select_index = index.to_a - missing_index
|
60
|
+
new_data = column_names.map { |key|
|
61
|
+
vals = select_index.map {|i| self[i, key] }
|
62
|
+
[key, vals]
|
63
|
+
}.to_h
|
64
|
+
Charty::Table.new(new_data, index: select_index)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def sort_values(by, na_position: :last)
|
69
|
+
na_cmp_val = check_na_position(na_position)
|
70
|
+
case by
|
71
|
+
when String, Symbol
|
72
|
+
order = (0 ... length).sort do |i, j|
|
73
|
+
a = self[i, by]
|
74
|
+
b = self[j, by]
|
75
|
+
case
|
76
|
+
when Util.missing?(a) # missing > b
|
77
|
+
na_cmp_val
|
78
|
+
when Util.missing?(b) # a < missing
|
79
|
+
-na_cmp_val
|
80
|
+
else
|
81
|
+
cmp = a <=> b
|
82
|
+
if cmp == 0
|
83
|
+
i <=> j
|
84
|
+
else
|
85
|
+
cmp
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
when Array
|
90
|
+
order = (0 ... length).sort do |i, j|
|
91
|
+
cmp = 0
|
92
|
+
by.each do |key|
|
93
|
+
a = self[i, key]
|
94
|
+
b = self[j, key]
|
95
|
+
case
|
96
|
+
when Util.missing?(a) # missing > b
|
97
|
+
cmp = na_cmp_val
|
98
|
+
break
|
99
|
+
when Util.missing?(b) # a < missing
|
100
|
+
cmp = -na_cmp_val
|
101
|
+
break
|
102
|
+
else
|
103
|
+
cmp = a <=> b
|
104
|
+
break if cmp != 0
|
105
|
+
end
|
106
|
+
end
|
107
|
+
if cmp == 0
|
108
|
+
i <=> j
|
109
|
+
else
|
110
|
+
cmp
|
111
|
+
end
|
112
|
+
end
|
113
|
+
else
|
114
|
+
raise ArgumentError,
|
115
|
+
"%p is invalid value for `by`" % by
|
116
|
+
end
|
117
|
+
|
118
|
+
Charty::Table.new(
|
119
|
+
column_names.map { |name|
|
120
|
+
[
|
121
|
+
name,
|
122
|
+
self[nil, name].values_at(*order)
|
123
|
+
]
|
124
|
+
}.to_h,
|
125
|
+
index: index.to_a.values_at(*order)
|
126
|
+
)
|
127
|
+
end
|
128
|
+
|
129
|
+
private def check_na_position(val)
|
130
|
+
case val
|
131
|
+
when :first, "first"
|
132
|
+
-1
|
133
|
+
when :last, "last"
|
134
|
+
1
|
135
|
+
else
|
136
|
+
raise ArgumentError,
|
137
|
+
"`na_position` must be :first or :last",
|
138
|
+
caller
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
private def check_and_convert_index(values, name, expected_length)
|
143
|
+
case values
|
144
|
+
when Index, Range
|
145
|
+
else
|
146
|
+
unless (ary = Array.try_convert(values))
|
147
|
+
raise ArgumentError, "invalid object for %s: %p" % [name, values]
|
148
|
+
end
|
149
|
+
values = ary
|
150
|
+
end
|
151
|
+
if expected_length != values.size
|
152
|
+
raise ArgumentError,
|
153
|
+
"invalid length for %s (%d for %d)" % [name, values.size, expected_length]
|
154
|
+
end
|
155
|
+
case values
|
156
|
+
when Index
|
157
|
+
values
|
158
|
+
when Range
|
159
|
+
RangeIndex.new(values)
|
160
|
+
when Array
|
161
|
+
Index.new(values)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
@@ -1,27 +1,65 @@
|
|
1
|
+
require "delegate"
|
2
|
+
|
1
3
|
module Charty
|
2
4
|
module TableAdapters
|
3
|
-
class DaruAdapter
|
5
|
+
class DaruAdapter < BaseAdapter
|
4
6
|
TableAdapters.register(:daru, self)
|
5
7
|
|
8
|
+
extend Forwardable
|
6
9
|
include Enumerable
|
7
10
|
|
8
11
|
def self.supported?(data)
|
9
12
|
defined?(Daru::DataFrame) && data.is_a?(Daru::DataFrame)
|
10
13
|
end
|
11
14
|
|
12
|
-
def initialize(data)
|
15
|
+
def initialize(data, columns: nil, index: nil)
|
13
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_delegator :data, :size, :length
|
25
|
+
|
26
|
+
def index
|
27
|
+
DaruIndex.new(data.index)
|
28
|
+
end
|
29
|
+
|
30
|
+
def_delegator :data, :index=
|
31
|
+
|
32
|
+
def columns
|
33
|
+
DaruIndex.new(data.vectors)
|
34
|
+
end
|
35
|
+
|
36
|
+
def columns=(values)
|
37
|
+
data.vectors = Daru::Index.coerce(values)
|
14
38
|
end
|
15
39
|
|
16
40
|
def column_names
|
17
41
|
@data.vectors.to_a
|
18
42
|
end
|
19
43
|
|
44
|
+
def compare_data_equality(other)
|
45
|
+
case other
|
46
|
+
when DaruAdapter
|
47
|
+
data == other.data
|
48
|
+
else
|
49
|
+
super
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
20
53
|
def [](row, column)
|
21
54
|
if row
|
22
55
|
@data[column][row]
|
23
56
|
else
|
24
|
-
@data
|
57
|
+
column_data = if @data.has_vector?(column)
|
58
|
+
@data[column]
|
59
|
+
else
|
60
|
+
@data[column.to_s]
|
61
|
+
end
|
62
|
+
Vector.new(column_data)
|
25
63
|
end
|
26
64
|
end
|
27
65
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
module Charty
|
2
2
|
module TableAdapters
|
3
|
-
class DatasetsAdapter
|
3
|
+
class DatasetsAdapter < BaseAdapter
|
4
4
|
TableAdapters.register(:datasets, self)
|
5
5
|
|
6
6
|
include Enumerable
|
@@ -13,12 +13,27 @@ module Charty
|
|
13
13
|
def initialize(dataset)
|
14
14
|
@table = dataset.to_table
|
15
15
|
@records = []
|
16
|
+
|
17
|
+
self.columns = self.column_names
|
18
|
+
self.index = 0 ... length
|
19
|
+
end
|
20
|
+
|
21
|
+
def data
|
22
|
+
@table
|
23
|
+
end
|
24
|
+
|
25
|
+
def column_length
|
26
|
+
column_names.length
|
16
27
|
end
|
17
28
|
|
18
29
|
def column_names
|
19
30
|
@table.column_names
|
20
31
|
end
|
21
32
|
|
33
|
+
def length
|
34
|
+
data.n_rows
|
35
|
+
end
|
36
|
+
|
22
37
|
def each(&block)
|
23
38
|
return to_enum(__method__) unless block_given?
|
24
39
|
|
@@ -33,7 +48,7 @@ module Charty
|
|
33
48
|
return nil if record.nil?
|
34
49
|
record[column]
|
35
50
|
else
|
36
|
-
@table[column]
|
51
|
+
Vector.new(@table[column], index: index, name: column)
|
37
52
|
end
|
38
53
|
end
|
39
54
|
end
|
@@ -1,14 +1,10 @@
|
|
1
1
|
require 'date'
|
2
|
-
require 'forwardable'
|
3
2
|
|
4
3
|
module Charty
|
5
4
|
module TableAdapters
|
6
|
-
class HashAdapter
|
5
|
+
class HashAdapter < BaseAdapter
|
7
6
|
TableAdapters.register(:hash, self)
|
8
7
|
|
9
|
-
extend Forwardable
|
10
|
-
include Enumerable
|
11
|
-
|
12
8
|
def self.supported?(data)
|
13
9
|
case data
|
14
10
|
when []
|
@@ -29,45 +25,171 @@ module Charty
|
|
29
25
|
|
30
26
|
def self.array?(data)
|
31
27
|
case data
|
32
|
-
when
|
33
|
-
|
34
|
-
|
35
|
-
|
28
|
+
when Charty::Vector
|
29
|
+
true
|
30
|
+
# TODO: Use vector adapter to detect them:
|
31
|
+
when Array, method(:daru_vector?), method(:narray_vector?), method(:nmatrix_vector?),
|
32
|
+
method(:numpy_vector?), method(:pandas_series?)
|
36
33
|
true
|
37
34
|
else
|
38
35
|
false
|
39
36
|
end
|
40
37
|
end
|
41
38
|
|
42
|
-
def
|
39
|
+
def self.daru_vector?(x)
|
40
|
+
defined?(Daru::Vector) && x.is_a?(Daru::Vector)
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.narray_vector?(x)
|
44
|
+
defined?(Numo::NArray) && x.is_a?(Numo::NArray) && x.ndim == 1
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.nmatrix_vector?(x)
|
48
|
+
defined?(NMatrix) && x.is_a?(NMatrix) && x.dim == 1
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.numpy_vector?(x)
|
52
|
+
defined?(Numpy::NDArray) && x.is_a?(Numpy::NDArray) && x.ndim == 1
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.pandas_series?(x)
|
56
|
+
defined?(Pandas::Series) && x.is_a?(Pandas::Series)
|
57
|
+
end
|
58
|
+
|
59
|
+
def initialize(data, columns: nil, index: nil)
|
43
60
|
case data
|
44
61
|
when Hash
|
45
|
-
|
62
|
+
arrays = data.values
|
63
|
+
columns ||= data.keys
|
46
64
|
when Array
|
47
65
|
case data[0]
|
48
66
|
when Numeric, String, Time, Date
|
49
|
-
|
50
|
-
@data = make_data_from_records(data, columns)
|
67
|
+
arrays = [data]
|
51
68
|
when Hash
|
52
|
-
|
69
|
+
columns ||= data.map(&:keys).inject(&:|)
|
70
|
+
arrays = columns.map { [] }
|
71
|
+
data.each do |record|
|
72
|
+
columns.each_with_index do |key, i|
|
73
|
+
arrays[i] << record[key]
|
74
|
+
end
|
75
|
+
end
|
53
76
|
when self.class.method(:array?)
|
54
77
|
unsupported_data_format unless data.all?(&self.class.method(:array?))
|
55
|
-
|
78
|
+
arrays = data.map(&:to_a).transpose
|
56
79
|
else
|
57
80
|
unsupported_data_format
|
58
81
|
end
|
59
82
|
else
|
60
83
|
unsupported_data_format
|
61
84
|
end
|
85
|
+
|
86
|
+
unless arrays.empty?
|
87
|
+
arrays, columns, index = check_data(arrays, columns, index)
|
88
|
+
end
|
89
|
+
|
90
|
+
@data = arrays.map.with_index {|a, i| [columns[i], a] }.to_h
|
91
|
+
self.columns = columns unless columns.nil?
|
92
|
+
self.index = index unless index.nil?
|
93
|
+
end
|
94
|
+
|
95
|
+
private def check_data(arrays, columns, index)
|
96
|
+
# NOTE: After Ruby 2.7, we can write the following code by filter_map:
|
97
|
+
# indexes = Util.filter_map(arrays) {|ary| ary.index if ary.is_a?(Charty::Vector) }
|
98
|
+
indexes = []
|
99
|
+
arrays.each do |array|
|
100
|
+
index = case array
|
101
|
+
when Charty::Vector
|
102
|
+
array.index
|
103
|
+
when ->(x) { defined?(Daru) && x.is_a?(Daru::Vector) }
|
104
|
+
Charty::DaruIndex.new(array.index)
|
105
|
+
when ->(x) { defined?(Pandas) && x.is_a?(Pandas::Series) }
|
106
|
+
Charty::PandasIndex.new(array.index)
|
107
|
+
else
|
108
|
+
if index.nil?
|
109
|
+
RangeIndex.new(0 ... array.size)
|
110
|
+
else
|
111
|
+
check_and_convert_index(index, :index, array.size)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
indexes << index
|
115
|
+
end
|
116
|
+
index = union_indexes(*indexes)
|
117
|
+
|
118
|
+
arrays = arrays.map do |array|
|
119
|
+
case array
|
120
|
+
when Charty::Vector
|
121
|
+
array.data
|
122
|
+
when Hash
|
123
|
+
raise NotImplementedError
|
124
|
+
when self.class.method(:array?)
|
125
|
+
array
|
126
|
+
else
|
127
|
+
Array.try_convert(array)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
columns = generate_column_names(arrays.length, columns)
|
132
|
+
|
133
|
+
return arrays, columns, index
|
134
|
+
end
|
135
|
+
|
136
|
+
private def union_indexes(*indexes)
|
137
|
+
result = nil
|
138
|
+
while result.nil? && indexes.length > 0
|
139
|
+
result = indexes.shift
|
140
|
+
end
|
141
|
+
indexes.each do |index|
|
142
|
+
next if index.nil?
|
143
|
+
result = result.union(index)
|
144
|
+
end
|
145
|
+
result
|
62
146
|
end
|
63
147
|
|
148
|
+
attr_reader :data
|
149
|
+
|
64
150
|
def_delegator :@data, :keys, :column_names
|
65
151
|
|
152
|
+
def length
|
153
|
+
case
|
154
|
+
when column_names.empty?
|
155
|
+
0
|
156
|
+
else
|
157
|
+
data[column_names[0]].size
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def column_length
|
162
|
+
data.length
|
163
|
+
end
|
164
|
+
|
165
|
+
def compare_data_equality(other)
|
166
|
+
case other
|
167
|
+
when DaruAdapter, PandasDataFrameAdapter
|
168
|
+
other.compare_data_equality(self)
|
169
|
+
else
|
170
|
+
super
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
66
174
|
def [](row, column)
|
67
175
|
if row
|
68
176
|
@data[column][row]
|
69
177
|
else
|
70
|
-
|
178
|
+
case column
|
179
|
+
when Symbol
|
180
|
+
sym_key = column
|
181
|
+
str_key = column.to_s
|
182
|
+
else
|
183
|
+
str_key = String.try_convert(column)
|
184
|
+
sym_key = str_key.to_sym
|
185
|
+
end
|
186
|
+
|
187
|
+
column_data = if @data.key?(sym_key)
|
188
|
+
@data[sym_key]
|
189
|
+
else
|
190
|
+
@data[str_key]
|
191
|
+
end
|
192
|
+
Vector.new(column_data, index: index, name: column)
|
71
193
|
end
|
72
194
|
end
|
73
195
|
|
@@ -80,6 +202,11 @@ module Charty
|
|
80
202
|
end
|
81
203
|
end
|
82
204
|
|
205
|
+
def reset_index
|
206
|
+
index_name = index.name || :index
|
207
|
+
Charty::Table.new({ index_name => index.to_a }.merge(data))
|
208
|
+
end
|
209
|
+
|
83
210
|
private def make_data_from_records(data, columns)
|
84
211
|
n_rows = data.length
|
85
212
|
n_columns = data.map(&:size).max
|