charty 0.2.4 → 0.2.9

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.
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