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.
@@ -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, :legend
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: marker_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
@@ -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, which, n_boot: 2000, func: :mean, units: nil, random: nil)
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 - which / 2, 50 + which / 2]
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
- # TODO: Must implement this method in each adapter
103
- missing_index = index.select do |i|
104
- column_names.any? do |key|
105
- missing_value?(self[key][i])
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
- if missing_index.empty?
109
- self
110
- else
111
- select_index = index.to_a - missing_index
112
- new_data = column_names.map { |key|
113
- vals = select_index.map {|i| self[key][i] }
114
- [key, vals]
115
- }.to_h
116
- Charty::Table.new(new_data, index: select_index)
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
@@ -21,6 +21,8 @@ module Charty
21
21
 
22
22
  attr_reader :data
23
23
 
24
+ def_delegator :data, :size, :length
25
+
24
26
  def index
25
27
  DaruIndex.new(data.index)
26
28
  end
@@ -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
- raise NotImplementedError,
69
- "an array of records is not supported"
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