charty 0.2.4 → 0.2.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +64 -15
  3. data/charty.gemspec +10 -3
  4. data/lib/charty.rb +5 -2
  5. data/lib/charty/backends/bokeh.rb +2 -2
  6. data/lib/charty/backends/google_charts.rb +1 -1
  7. data/lib/charty/backends/gruff.rb +1 -1
  8. data/lib/charty/backends/plotly.rb +434 -32
  9. data/lib/charty/backends/plotly_helpers/html_renderer.rb +203 -0
  10. data/lib/charty/backends/plotly_helpers/notebook_renderer.rb +87 -0
  11. data/lib/charty/backends/plotly_helpers/plotly_renderer.rb +121 -0
  12. data/lib/charty/backends/pyplot.rb +187 -48
  13. data/lib/charty/backends/rubyplot.rb +1 -1
  14. data/lib/charty/cache_dir.rb +27 -0
  15. data/lib/charty/dash_pattern_generator.rb +57 -0
  16. data/lib/charty/index.rb +1 -1
  17. data/lib/charty/iruby_helper.rb +18 -0
  18. data/lib/charty/plot_methods.rb +115 -3
  19. data/lib/charty/plotter.rb +2 -2
  20. data/lib/charty/plotters.rb +4 -0
  21. data/lib/charty/plotters/abstract_plotter.rb +106 -11
  22. data/lib/charty/plotters/bar_plotter.rb +1 -16
  23. data/lib/charty/plotters/box_plotter.rb +1 -16
  24. data/lib/charty/plotters/distribution_plotter.rb +150 -0
  25. data/lib/charty/plotters/histogram_plotter.rb +242 -0
  26. data/lib/charty/plotters/line_plotter.rb +300 -0
  27. data/lib/charty/plotters/relational_plotter.rb +213 -96
  28. data/lib/charty/plotters/scatter_plotter.rb +8 -43
  29. data/lib/charty/statistics.rb +11 -2
  30. data/lib/charty/table.rb +124 -14
  31. data/lib/charty/table_adapters/base_adapter.rb +97 -0
  32. data/lib/charty/table_adapters/daru_adapter.rb +2 -0
  33. data/lib/charty/table_adapters/datasets_adapter.rb +7 -0
  34. data/lib/charty/table_adapters/hash_adapter.rb +19 -3
  35. data/lib/charty/table_adapters/pandas_adapter.rb +82 -0
  36. data/lib/charty/util.rb +28 -0
  37. data/lib/charty/vector_adapters.rb +5 -1
  38. data/lib/charty/vector_adapters/array_adapter.rb +2 -10
  39. data/lib/charty/vector_adapters/daru_adapter.rb +3 -11
  40. data/lib/charty/vector_adapters/narray_adapter.rb +1 -6
  41. data/lib/charty/vector_adapters/numpy_adapter.rb +1 -1
  42. data/lib/charty/vector_adapters/pandas_adapter.rb +0 -1
  43. data/lib/charty/version.rb +1 -1
  44. metadata +104 -11
  45. 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, :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)
@@ -47,26 +35,15 @@ module Charty
47
35
  @line_width = check_color(val, :edge_color, allow_nil: true)
48
36
  end
49
37
 
50
- def render
51
- backend = Backends.current
52
- backend.begin_figure
53
- draw_points(backend)
54
- annotate_axes(backend)
55
- backend.show
56
- end
57
-
58
- def save(filename, **opts)
59
- backend = Backends.current
60
- backend.begin_figure
38
+ private def render_plot(backend, **)
61
39
  draw_points(backend)
62
40
  annotate_axes(backend)
63
- backend.save(filename, **opts)
64
41
  end
65
42
 
66
43
  private def draw_points(backend)
67
44
  map_color(palette: palette, order: color_order, norm: color_norm)
68
45
  map_size(sizes: sizes, order: size_order, norm: size_norm)
69
- map_style(markers: markers, order: marker_order)
46
+ map_style(markers: markers, order: style_order)
70
47
 
71
48
  data = @plot_data.drop_na
72
49
 
@@ -84,9 +61,12 @@ module Charty
84
61
  x, y, @variables,
85
62
  color: color, color_mapper: @color_mapper,
86
63
  style: style, style_mapper: @style_mapper,
87
- size: size, size_mapper: @size_mapper,
88
- legend: legend
64
+ size: size, size_mapper: @size_mapper
89
65
  )
66
+
67
+ if legend
68
+ backend.add_scatter_plot_legend(@variables, @color_mapper, @size_mapper, @style_mapper, legend)
69
+ end
90
70
  end
91
71
 
92
72
  private def annotate_axes(backend)
@@ -94,21 +74,6 @@ module Charty
94
74
  ylabel = self.variables[:y]
95
75
  backend.set_xlabel(xlabel) unless xlabel.nil?
96
76
  backend.set_ylabel(ylabel) unless ylabel.nil?
97
-
98
- if legend
99
- add_legend_data(backend)
100
- end
101
- end
102
-
103
- private def add_legend_data(backend)
104
- # TODO: Legend Support
105
- verbosity = legend
106
- verbosity = :auto if verbosity == true
107
-
108
- titles = [:color, :size, :style].filter_map do |v|
109
- variables[v] if variables.key?(v)
110
- end
111
- legend_title = titles.length == 1 ? titles[0] : ""
112
77
  end
113
78
  end
114
79
  end
@@ -10,6 +10,10 @@ module Charty
10
10
  def self.stdev(enum, population: false)
11
11
  enum.stdev(population: population)
12
12
  end
13
+
14
+ def self.histogram(ary, *args, **kwargs)
15
+ ary.histogram(*args, **kwargs)
16
+ end
13
17
  rescue LoadError
14
18
  def self.mean(enum)
15
19
  xs = enum.to_a
@@ -24,6 +28,11 @@ module Charty
24
28
  var = xs.map {|x| (x - mean)**2 }.sum / (n - ddof)
25
29
  Math.sqrt(var)
26
30
  end
31
+
32
+ def self.histogram(ary, *args, **kwargs)
33
+ raise NotImplementedError,
34
+ "histogram is currently supported only with enumerable-statistics"
35
+ end
27
36
  end
28
37
 
29
38
  def self.bootstrap(vector, n_boot: 2000, func: :mean, units: nil, random: nil)
@@ -83,9 +92,9 @@ module Charty
83
92
  "structured bootstrapping has not been supported yet"
84
93
  end
85
94
 
86
- def self.bootstrap_ci(*vectors, which, n_boot: 2000, func: :mean, units: nil, random: nil)
95
+ def self.bootstrap_ci(*vectors, width, n_boot: 2000, func: :mean, units: nil, random: nil)
87
96
  boot = bootstrap(*vectors, n_boot: n_boot, func: func, units: units, random: random)
88
- q = [50 - which / 2, 50 + which / 2]
97
+ q = [50 - width / 2, 50 + width / 2]
89
98
  if boot.respond_to?(:percentile)
90
99
  boot.percentile(q)
91
100
  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
@@ -20,6 +20,8 @@ module Charty
20
20
  end
21
21
  when Hash
22
22
  true
23
+ when ->(x) { defined?(CSV::Table) && x.is_a?(CSV::Table) }
24
+ true
23
25
  end
24
26
  end
25
27
 
@@ -27,6 +29,7 @@ module Charty
27
29
  case data
28
30
  when Charty::Vector
29
31
  true
32
+ # TODO: Use vector adapter to detect them:
30
33
  when Array, method(:daru_vector?), method(:narray_vector?), method(:nmatrix_vector?),
31
34
  method(:numpy_vector?), method(:pandas_series?)
32
35
  true
@@ -65,14 +68,22 @@ module Charty
65
68
  when Numeric, String, Time, Date
66
69
  arrays = [data]
67
70
  when Hash
68
- raise NotImplementedError,
69
- "an array of records is not supported"
71
+ columns ||= data.map(&:keys).inject(&:|)
72
+ arrays = columns.map { [] }
73
+ data.each do |record|
74
+ columns.each_with_index do |key, i|
75
+ arrays[i] << record[key]
76
+ end
77
+ end
70
78
  when self.class.method(:array?)
71
79
  unsupported_data_format unless data.all?(&self.class.method(:array?))
72
80
  arrays = data.map(&:to_a).transpose
73
81
  else
74
82
  unsupported_data_format
75
83
  end
84
+ when ->(x) { defined?(CSV::Table) && x.is_a?(CSV::Table) }
85
+ columns ||= data.headers
86
+ arrays = data.headers.map {|x| data[x] }
76
87
  else
77
88
  unsupported_data_format
78
89
  end
@@ -88,7 +99,7 @@ module Charty
88
99
 
89
100
  private def check_data(arrays, columns, index)
90
101
  # 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) }
102
+ # indexes = Util.filter_map(arrays) {|ary| ary.index if ary.is_a?(Charty::Vector) }
92
103
  indexes = []
93
104
  arrays.each do |array|
94
105
  index = case array
@@ -196,6 +207,11 @@ module Charty
196
207
  end
197
208
  end
198
209
 
210
+ def reset_index
211
+ index_name = index.name || :index
212
+ Charty::Table.new({ index_name => index.to_a }.merge(data))
213
+ end
214
+
199
215
  private def make_data_from_records(data, columns)
200
216
  n_rows = data.length
201
217
  n_columns = data.map(&:size).max