charty 0.2.5 → 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/README.md +2 -1
- data/charty.gemspec +8 -5
- data/lib/charty.rb +2 -2
- data/lib/charty/backends/plotly.rb +197 -8
- data/lib/charty/backends/pyplot.rb +135 -44
- data/lib/charty/dash_pattern_generator.rb +57 -0
- data/lib/charty/index.rb +1 -1
- data/lib/charty/plot_methods.rb +73 -3
- data/lib/charty/plotters.rb +1 -0
- data/lib/charty/plotters/abstract_plotter.rb +69 -9
- data/lib/charty/plotters/line_plotter.rb +300 -0
- data/lib/charty/plotters/relational_plotter.rb +213 -96
- data/lib/charty/plotters/scatter_plotter.rb +7 -31
- data/lib/charty/statistics.rb +2 -2
- data/lib/charty/table.rb +124 -14
- data/lib/charty/table_adapters/base_adapter.rb +97 -0
- data/lib/charty/table_adapters/daru_adapter.rb +2 -0
- data/lib/charty/table_adapters/datasets_adapter.rb +7 -0
- data/lib/charty/table_adapters/hash_adapter.rb +13 -2
- data/lib/charty/table_adapters/pandas_adapter.rb +82 -0
- data/lib/charty/util.rb +8 -0
- data/lib/charty/vector_adapters.rb +5 -1
- data/lib/charty/vector_adapters/array_adapter.rb +2 -10
- data/lib/charty/vector_adapters/daru_adapter.rb +3 -11
- data/lib/charty/vector_adapters/narray_adapter.rb +1 -6
- data/lib/charty/vector_adapters/numpy_adapter.rb +1 -1
- data/lib/charty/vector_adapters/pandas_adapter.rb +0 -1
- data/lib/charty/version.rb +1 -1
- metadata +54 -25
- data/lib/charty/missing_value_support.rb +0 -14
@@ -6,7 +6,7 @@ module Charty
|
|
6
6
|
super(x, y, color, style, size, data: data, **options, &block)
|
7
7
|
end
|
8
8
|
|
9
|
-
attr_reader :alpha
|
9
|
+
attr_reader :alpha
|
10
10
|
|
11
11
|
def alpha=(val)
|
12
12
|
case val
|
@@ -25,18 +25,6 @@ module Charty
|
|
25
25
|
end
|
26
26
|
end
|
27
27
|
|
28
|
-
def legend=(val)
|
29
|
-
case val
|
30
|
-
when :auto, :brief, :full, false
|
31
|
-
@legend = val
|
32
|
-
when "auto", "brief", "full"
|
33
|
-
@legend = val.to_sym
|
34
|
-
else
|
35
|
-
raise ArgumentError,
|
36
|
-
"invalid value of legend (%p for :auto, :brief, :full, or false)" % val
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
28
|
attr_reader :line_width, :edge_color
|
41
29
|
|
42
30
|
def line_width=(val)
|
@@ -55,7 +43,7 @@ module Charty
|
|
55
43
|
private def draw_points(backend)
|
56
44
|
map_color(palette: palette, order: color_order, norm: color_norm)
|
57
45
|
map_size(sizes: sizes, order: size_order, norm: size_norm)
|
58
|
-
map_style(markers: markers, order:
|
46
|
+
map_style(markers: markers, order: style_order)
|
59
47
|
|
60
48
|
data = @plot_data.drop_na
|
61
49
|
|
@@ -73,9 +61,12 @@ module Charty
|
|
73
61
|
x, y, @variables,
|
74
62
|
color: color, color_mapper: @color_mapper,
|
75
63
|
style: style, style_mapper: @style_mapper,
|
76
|
-
size: size, size_mapper: @size_mapper
|
77
|
-
legend: legend
|
64
|
+
size: size, size_mapper: @size_mapper
|
78
65
|
)
|
66
|
+
|
67
|
+
if legend
|
68
|
+
backend.add_scatter_plot_legend(@variables, @color_mapper, @size_mapper, @style_mapper, legend)
|
69
|
+
end
|
79
70
|
end
|
80
71
|
|
81
72
|
private def annotate_axes(backend)
|
@@ -83,21 +74,6 @@ module Charty
|
|
83
74
|
ylabel = self.variables[:y]
|
84
75
|
backend.set_xlabel(xlabel) unless xlabel.nil?
|
85
76
|
backend.set_ylabel(ylabel) unless ylabel.nil?
|
86
|
-
|
87
|
-
if legend
|
88
|
-
add_legend_data(backend)
|
89
|
-
end
|
90
|
-
end
|
91
|
-
|
92
|
-
private def add_legend_data(backend)
|
93
|
-
# TODO: Legend Support
|
94
|
-
verbosity = legend
|
95
|
-
verbosity = :auto if verbosity == true
|
96
|
-
|
97
|
-
titles = Util.filter_map([:color, :size, :style]) do |v|
|
98
|
-
variables[v] if variables.key?(v)
|
99
|
-
end
|
100
|
-
legend_title = titles.length == 1 ? titles[0] : ""
|
101
77
|
end
|
102
78
|
end
|
103
79
|
end
|
data/lib/charty/statistics.rb
CHANGED
@@ -83,9 +83,9 @@ module Charty
|
|
83
83
|
"structured bootstrapping has not been supported yet"
|
84
84
|
end
|
85
85
|
|
86
|
-
def self.bootstrap_ci(*vectors,
|
86
|
+
def self.bootstrap_ci(*vectors, width, n_boot: 2000, func: :mean, units: nil, random: nil)
|
87
87
|
boot = bootstrap(*vectors, n_boot: n_boot, func: func, units: units, random: random)
|
88
|
-
q = [50 -
|
88
|
+
q = [50 - width / 2, 50 + width / 2]
|
89
89
|
if boot.respond_to?(:percentile)
|
90
90
|
boot.percentile(q)
|
91
91
|
else
|
data/lib/charty/table.rb
CHANGED
@@ -13,7 +13,6 @@ module Charty
|
|
13
13
|
|
14
14
|
class Table
|
15
15
|
extend Forwardable
|
16
|
-
include MissingValueSupport
|
17
16
|
|
18
17
|
def initialize(data, **kwargs)
|
19
18
|
adapter_class = TableAdapters.find_adapter_class(data)
|
@@ -34,6 +33,20 @@ module Charty
|
|
34
33
|
def_delegators :adapter, :index, :index=
|
35
34
|
|
36
35
|
def_delegator :@adapter, :column_names
|
36
|
+
|
37
|
+
def column?(name)
|
38
|
+
return true if column_names.include?(name)
|
39
|
+
|
40
|
+
case name
|
41
|
+
when String
|
42
|
+
column_names.include?(name.to_sym)
|
43
|
+
when Symbol
|
44
|
+
column_names.include?(name.to_s)
|
45
|
+
else
|
46
|
+
false
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
37
50
|
def_delegator :@adapter, :data, :raw_data
|
38
51
|
|
39
52
|
def ==(other)
|
@@ -65,6 +78,10 @@ module Charty
|
|
65
78
|
end
|
66
79
|
end
|
67
80
|
|
81
|
+
def group_by(grouper, sort: true, drop_na: true)
|
82
|
+
adapter.group_by(self, grouper, sort, drop_na)
|
83
|
+
end
|
84
|
+
|
68
85
|
def to_a(x=nil, y=nil, z=nil)
|
69
86
|
case
|
70
87
|
when defined?(Daru::DataFrame) && table.kind_of?(Daru::DataFrame)
|
@@ -99,21 +116,114 @@ module Charty
|
|
99
116
|
end
|
100
117
|
|
101
118
|
def drop_na
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
119
|
+
@adapter.drop_na || self
|
120
|
+
end
|
121
|
+
|
122
|
+
def_delegator :adapter, :sort_values
|
123
|
+
|
124
|
+
def_delegator :adapter, :reset_index
|
125
|
+
|
126
|
+
class GroupByBase
|
127
|
+
end
|
128
|
+
|
129
|
+
class HashGroupBy < GroupByBase
|
130
|
+
def initialize(table, grouper, sort, drop_na)
|
131
|
+
@table = table
|
132
|
+
@grouper = check_grouper(grouper)
|
133
|
+
init_groups(sort, drop_na)
|
134
|
+
end
|
135
|
+
|
136
|
+
private def check_grouper(grouper)
|
137
|
+
case grouper
|
138
|
+
when Symbol, String, Array
|
139
|
+
# TODO check column existence
|
140
|
+
return grouper
|
141
|
+
when Charty::Vector
|
142
|
+
if @table.length != grouper.length
|
143
|
+
raise ArgumentError,
|
144
|
+
"Wrong number of items in grouper array " +
|
145
|
+
"(%p for %p)" % [val.length, @table.length]
|
146
|
+
end
|
147
|
+
return grouper
|
148
|
+
when ->(x) { x.respond_to?(:call) }
|
149
|
+
raise NotImplementedError,
|
150
|
+
"A callable grouper is unsupported"
|
151
|
+
else
|
152
|
+
raise ArgumentError,
|
153
|
+
"Unable to recognize the value for `grouper`: %p" % val
|
106
154
|
end
|
107
155
|
end
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
156
|
+
|
157
|
+
private def init_groups(sort, drop_na)
|
158
|
+
case @grouper
|
159
|
+
when Symbol, String
|
160
|
+
column = @table[@grouper]
|
161
|
+
@indices = (0 ... @table.length).group_by do |i|
|
162
|
+
column.data[i]
|
163
|
+
end
|
164
|
+
when Array
|
165
|
+
@indices = (0 ... @table.length).group_by { |i|
|
166
|
+
@grouper.map {|j| @table[j].data[i] }
|
167
|
+
}
|
168
|
+
when Charty::Vector
|
169
|
+
@indices = (0 ... @table.length).group_by do |i|
|
170
|
+
@grouper.data[i]
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
if drop_na
|
175
|
+
case @grouper
|
176
|
+
when Array
|
177
|
+
@indices.reject! {|key, | key.any? {|k| Util.missing?(k) } }
|
178
|
+
else
|
179
|
+
@indices.reject! {|key, | Util.missing?(key) }
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
if sort
|
184
|
+
@indices = @indices.sort_by {|key, | key }.to_h
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
def indices
|
189
|
+
@indices.dup
|
190
|
+
end
|
191
|
+
|
192
|
+
def group_keys
|
193
|
+
@indices.keys
|
194
|
+
end
|
195
|
+
|
196
|
+
def each_group_key(&block)
|
197
|
+
@indices.each_key(&block)
|
198
|
+
end
|
199
|
+
|
200
|
+
def apply(*args, &block)
|
201
|
+
Charty::Table.new(
|
202
|
+
each_group.map { |_key, table|
|
203
|
+
block.call(table, *args)
|
204
|
+
},
|
205
|
+
index: Charty::Index.new(@indices.keys, name: @grouper)
|
206
|
+
)
|
207
|
+
end
|
208
|
+
|
209
|
+
def each_group
|
210
|
+
return enum_for(__method__) unless block_given?
|
211
|
+
|
212
|
+
@indices.each_key do |key|
|
213
|
+
yield(key, self[key])
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
def [](key)
|
218
|
+
return nil unless @indices.key?(key)
|
219
|
+
|
220
|
+
index = @indices[key]
|
221
|
+
Charty::Table.new(
|
222
|
+
@table.column_names.map {|col|
|
223
|
+
[col, @table[col].values_at(*index)]
|
224
|
+
}.to_h,
|
225
|
+
index: index
|
226
|
+
)
|
117
227
|
end
|
118
228
|
end
|
119
229
|
end
|
@@ -33,6 +33,10 @@ module Charty
|
|
33
33
|
end
|
34
34
|
end
|
35
35
|
|
36
|
+
def group_by(table, grouper, sort, drop_na)
|
37
|
+
Table::HashGroupBy.new(table, grouper, sort, drop_na)
|
38
|
+
end
|
39
|
+
|
36
40
|
def compare_data_equality(other)
|
37
41
|
columns.each do |name|
|
38
42
|
if self[nil, name] != other[nil, name]
|
@@ -42,6 +46,99 @@ module Charty
|
|
42
46
|
true
|
43
47
|
end
|
44
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
|
+
|
45
142
|
private def check_and_convert_index(values, name, expected_length)
|
46
143
|
case values
|
47
144
|
when Index, Range
|
@@ -13,12 +13,19 @@ 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
|
16
19
|
end
|
17
20
|
|
18
21
|
def data
|
19
22
|
@table
|
20
23
|
end
|
21
24
|
|
25
|
+
def column_length
|
26
|
+
column_names.length
|
27
|
+
end
|
28
|
+
|
22
29
|
def column_names
|
23
30
|
@table.column_names
|
24
31
|
end
|
@@ -27,6 +27,7 @@ module Charty
|
|
27
27
|
case data
|
28
28
|
when Charty::Vector
|
29
29
|
true
|
30
|
+
# TODO: Use vector adapter to detect them:
|
30
31
|
when Array, method(:daru_vector?), method(:narray_vector?), method(:nmatrix_vector?),
|
31
32
|
method(:numpy_vector?), method(:pandas_series?)
|
32
33
|
true
|
@@ -65,8 +66,13 @@ module Charty
|
|
65
66
|
when Numeric, String, Time, Date
|
66
67
|
arrays = [data]
|
67
68
|
when Hash
|
68
|
-
|
69
|
-
|
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
|
70
76
|
when self.class.method(:array?)
|
71
77
|
unsupported_data_format unless data.all?(&self.class.method(:array?))
|
72
78
|
arrays = data.map(&:to_a).transpose
|
@@ -196,6 +202,11 @@ module Charty
|
|
196
202
|
end
|
197
203
|
end
|
198
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
|
+
|
199
210
|
private def make_data_from_records(data, columns)
|
200
211
|
n_rows = data.length
|
201
212
|
n_columns = data.map(&:size).max
|
@@ -21,6 +21,10 @@ module Charty
|
|
21
21
|
|
22
22
|
attr_reader :data
|
23
23
|
|
24
|
+
def length
|
25
|
+
data.shape[0]
|
26
|
+
end
|
27
|
+
|
24
28
|
def columns
|
25
29
|
PandasIndex.new(data.columns)
|
26
30
|
end
|
@@ -29,8 +33,10 @@ module Charty
|
|
29
33
|
case new_columns
|
30
34
|
when PandasIndex
|
31
35
|
data.columns = new_columns.values
|
36
|
+
data.columns.name = new_columns.name
|
32
37
|
when Index
|
33
38
|
data.columns = new_columns.to_a
|
39
|
+
data.columns.name = new_columns.name
|
34
40
|
else
|
35
41
|
data.columns = new_columns
|
36
42
|
end
|
@@ -44,8 +50,10 @@ module Charty
|
|
44
50
|
case new_index
|
45
51
|
when PandasIndex
|
46
52
|
data.index = new_index.values
|
53
|
+
data.index.name = new_index.name
|
47
54
|
when Index
|
48
55
|
data.index = new_index.to_a
|
56
|
+
data.index.name = new_index.name
|
49
57
|
else
|
50
58
|
data.index = new_index
|
51
59
|
end
|
@@ -72,10 +80,84 @@ module Charty
|
|
72
80
|
end
|
73
81
|
end
|
74
82
|
|
83
|
+
def drop_na
|
84
|
+
Charty::Table.new(@data.dropna)
|
85
|
+
end
|
86
|
+
|
87
|
+
def sort_values(by, na_position: :last)
|
88
|
+
Charty::Table.new(@data.sort_values(by, na_position: na_position, kind: :mergesort))
|
89
|
+
end
|
90
|
+
|
75
91
|
private def check_type(type, data, name)
|
76
92
|
return data if data.is_a?(type)
|
77
93
|
raise TypeError, "#{name} must be a #{type}"
|
78
94
|
end
|
95
|
+
|
96
|
+
def group_by(_table, grouper, sort, drop_na)
|
97
|
+
GroupBy.new(@data.groupby(by: grouper, sort: sort, dropna: drop_na))
|
98
|
+
end
|
99
|
+
|
100
|
+
def reset_index
|
101
|
+
Charty::Table.new(data.reset_index)
|
102
|
+
end
|
103
|
+
|
104
|
+
class GroupBy < Charty::Table::GroupByBase
|
105
|
+
def initialize(groupby)
|
106
|
+
@groupby = groupby
|
107
|
+
end
|
108
|
+
|
109
|
+
def indices
|
110
|
+
@groupby.indices.map { |k, v|
|
111
|
+
[k, v.to_a]
|
112
|
+
}.to_h
|
113
|
+
end
|
114
|
+
|
115
|
+
def group_keys
|
116
|
+
each_group_key.to_a
|
117
|
+
end
|
118
|
+
|
119
|
+
def each_group_key
|
120
|
+
return enum_for(__method__) unless block_given?
|
121
|
+
|
122
|
+
if PyCall.respond_to?(:iterable)
|
123
|
+
PyCall.iterable(@groupby).each do |key, index|
|
124
|
+
if key.class == PyCall.builtins.tuple
|
125
|
+
key = key.to_a
|
126
|
+
end
|
127
|
+
yield key
|
128
|
+
end
|
129
|
+
else # TODO: Remove this clause after the new PyCall will be released
|
130
|
+
iter = @groupby.__iter__()
|
131
|
+
while true
|
132
|
+
begin
|
133
|
+
key, sub_data = iter.__next__
|
134
|
+
if key.class == PyCall.builtins.tuple
|
135
|
+
key = key.to_a
|
136
|
+
end
|
137
|
+
yield key
|
138
|
+
rescue PyCall::PyError => error
|
139
|
+
if error.type == PyCall.builtins.StopIteration
|
140
|
+
break
|
141
|
+
else
|
142
|
+
raise error
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def apply(*args, &block)
|
150
|
+
res = @groupby.apply(->(data) {
|
151
|
+
res = block.call(Charty::Table.new(data), *args)
|
152
|
+
Pandas::Series.new(data: res)
|
153
|
+
})
|
154
|
+
Charty::Table.new(res)
|
155
|
+
end
|
156
|
+
|
157
|
+
def [](key)
|
158
|
+
Charty::Table.new(@groupby.get_group(key))
|
159
|
+
end
|
160
|
+
end
|
79
161
|
end
|
80
162
|
end
|
81
163
|
end
|