charty 0.2.5 → 0.2.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -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