charty 0.2.6 → 0.2.10

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 (42) hide show
  1. checksums.yaml +4 -4
  2. data/charty.gemspec +2 -1
  3. data/examples/bar_plot.rb +19 -0
  4. data/examples/box_plot.rb +17 -0
  5. data/examples/scatter_plot.rb +17 -0
  6. data/images/penguins_body_mass_g_flipper_length_mm_species_scatter_plot.png +0 -0
  7. data/images/penguins_body_mass_g_flipper_length_mm_species_sex_scatter_plot.png +0 -0
  8. data/images/penguins_species_body_mass_g_bar_plot_h.png +0 -0
  9. data/images/penguins_species_body_mass_g_bar_plot_v.png +0 -0
  10. data/images/penguins_species_body_mass_g_box_plot_h.png +0 -0
  11. data/images/penguins_species_body_mass_g_box_plot_v.png +0 -0
  12. data/images/penguins_species_body_mass_g_sex_bar_plot_v.png +0 -0
  13. data/images/penguins_species_body_mass_g_sex_box_plot_v.png +0 -0
  14. data/lib/charty.rb +2 -0
  15. data/lib/charty/backends/plotly.rb +127 -24
  16. data/lib/charty/backends/plotly_helpers/html_renderer.rb +203 -0
  17. data/lib/charty/backends/plotly_helpers/notebook_renderer.rb +89 -0
  18. data/lib/charty/backends/plotly_helpers/plotly_renderer.rb +121 -0
  19. data/lib/charty/backends/pyplot.rb +74 -0
  20. data/lib/charty/backends/unicode_plot.rb +9 -9
  21. data/lib/charty/cache_dir.rb +27 -0
  22. data/lib/charty/iruby_helper.rb +18 -0
  23. data/lib/charty/plot_methods.rb +82 -6
  24. data/lib/charty/plotters.rb +3 -0
  25. data/lib/charty/plotters/abstract_plotter.rb +56 -16
  26. data/lib/charty/plotters/bar_plotter.rb +39 -0
  27. data/lib/charty/plotters/categorical_plotter.rb +9 -1
  28. data/lib/charty/plotters/distribution_plotter.rb +180 -0
  29. data/lib/charty/plotters/histogram_plotter.rb +244 -0
  30. data/lib/charty/plotters/line_plotter.rb +38 -5
  31. data/lib/charty/plotters/scatter_plotter.rb +4 -2
  32. data/lib/charty/statistics.rb +9 -0
  33. data/lib/charty/table.rb +30 -23
  34. data/lib/charty/table_adapters/base_adapter.rb +88 -0
  35. data/lib/charty/table_adapters/daru_adapter.rb +41 -1
  36. data/lib/charty/table_adapters/hash_adapter.rb +59 -1
  37. data/lib/charty/table_adapters/pandas_adapter.rb +49 -1
  38. data/lib/charty/vector.rb +29 -1
  39. data/lib/charty/vector_adapters.rb +16 -0
  40. data/lib/charty/vector_adapters/pandas_adapter.rb +10 -1
  41. data/lib/charty/version.rb +1 -1
  42. metadata +39 -15
@@ -122,7 +122,7 @@ module Charty
122
122
 
123
123
  include RandomSupport
124
124
 
125
- attr_reader :sort, :err_style, :err_kws, :error_bar, :x_scale, :y_scale
125
+ attr_reader :sort, :err_style, :err_kws, :error_bar
126
126
 
127
127
  def sort=(val)
128
128
  @sort = check_boolean(val, :sort)
@@ -211,17 +211,21 @@ module Charty
211
211
  [method, level]
212
212
  end
213
213
 
214
+ attr_reader :x_scale
215
+
214
216
  def x_scale=(val)
215
217
  @x_scale = check_axis_scale(val, :x)
216
218
  end
217
219
 
220
+ attr_reader :y_scale
221
+
218
222
  def y_scale=(val)
219
223
  @y_scale = check_axis_scale(val, :y)
220
224
  end
221
225
 
222
226
  private def check_axis_scale(val, axis)
223
227
  case val
224
- when :linear, "linear", :log10, "log10"
228
+ when :linear, "linear", :log, "log"
225
229
  val.to_sym
226
230
  else
227
231
  raise ArgumentError,
@@ -252,6 +256,15 @@ module Charty
252
256
  sub_data = sub_data.sort_values(sort_cols)
253
257
  end
254
258
 
259
+ # Perform axis scaling
260
+ if x_scale != :linear
261
+ sub_data[:x] = sub_data[:x].scale(x_scale)
262
+ end
263
+ if y_scale != :linear
264
+ sub_data[:y] = sub_data[:y].scale(x_scale)
265
+ end
266
+
267
+ # Perform estimation and error calculation
255
268
  unless estimator.nil?
256
269
  if self.variables.include?(:units)
257
270
  raise "`estimator` is must be nil when specifying `units`"
@@ -261,7 +274,22 @@ module Charty
261
274
  sub_data = grouped.apply(agg_var, &aggregator.method(:aggregate)).reset_index
262
275
  end
263
276
 
264
- # TODO: perform inverse conversion of axis scaling before plot
277
+ # Perform axis inverse scaling
278
+ if x_scale != :linear
279
+ sub_data.column_names.each do |cn|
280
+ if cn.start_with?("x")
281
+ sub_data[cn] = sub_data[cn].scale_inverse(x_scale)
282
+ end
283
+ end
284
+ end
285
+
286
+ if y_scale != :linear
287
+ sub_data.column_names.each do |cn|
288
+ if cn.start_with?("y")
289
+ sub_data[cn] = sub_data[cn].scale_inverse(x_scale)
290
+ end
291
+ end
292
+ end
265
293
 
266
294
  unit_grouping = if self.variables.include?(:units)
267
295
  sub_data.group_by(:units).each_group
@@ -290,10 +318,15 @@ module Charty
290
318
  end
291
319
 
292
320
  private def annotate_axes(backend)
293
- xlabel = self.variables[:x]
294
- ylabel = self.variables[:y]
321
+ backend.set_title(self.title) if self.title
322
+
323
+ xlabel = self.x_label || self.variables[:x]
324
+ ylabel = self.y_label || self.variables[:y]
295
325
  backend.set_xlabel(xlabel) unless xlabel.nil?
296
326
  backend.set_ylabel(ylabel) unless ylabel.nil?
327
+
328
+ backend.set_xscale(x_scale)
329
+ backend.set_yscale(y_scale)
297
330
  end
298
331
  end
299
332
  end
@@ -70,8 +70,10 @@ module Charty
70
70
  end
71
71
 
72
72
  private def annotate_axes(backend)
73
- xlabel = self.variables[:x]
74
- ylabel = self.variables[:y]
73
+ backend.set_title(self.title) if self.title
74
+
75
+ xlabel = self.x_label || self.variables[:x]
76
+ ylabel = self.y_label || self.variables[:y]
75
77
  backend.set_xlabel(xlabel) unless xlabel.nil?
76
78
  backend.set_ylabel(ylabel) unless ylabel.nil?
77
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)
data/lib/charty/table.rb CHANGED
@@ -32,20 +32,7 @@ module Charty
32
32
  def_delegators :adapter, :columns, :columns=
33
33
  def_delegators :adapter, :index, :index=
34
34
 
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
35
+ def_delegators :@adapter, :column_names, :column?
49
36
 
50
37
  def_delegator :@adapter, :data, :raw_data
51
38
 
@@ -65,17 +52,35 @@ module Charty
65
52
  end
66
53
 
67
54
  def [](key)
68
- key = case key
69
- when Symbol
70
- key
71
- else
72
- String.try_convert(key).to_sym
73
- end
74
- if @column_cache.key?(key)
75
- @column_cache[key]
55
+ case key
56
+ when Array
57
+ @adapter[nil, key]
58
+ else
59
+ key = case key
60
+ when Symbol
61
+ key
62
+ else
63
+ String.try_convert(key).to_sym
64
+ end
65
+ if @column_cache.key?(key)
66
+ @column_cache[key]
67
+ else
68
+ @column_cache[key] = @adapter[nil, key]
69
+ end
70
+ end
71
+ end
72
+
73
+ def []=(key, values)
74
+ case key
75
+ when Array
76
+ raise ArgumentError,
77
+ "Substituting multiple keys is not supported"
78
+ when Symbol
79
+ # do nothing
76
80
  else
77
- @column_cache[key] = @adapter[nil, key]
81
+ key = key.to_str.to_sym
78
82
  end
83
+ @adapter[key] = values
79
84
  end
80
85
 
81
86
  def group_by(grouper, sort: true, drop_na: true)
@@ -123,6 +128,8 @@ module Charty
123
128
 
124
129
  def_delegator :adapter, :reset_index
125
130
 
131
+ def_delegator :adapter, :melt
132
+
126
133
  class GroupByBase
127
134
  end
128
135
 
@@ -16,6 +16,19 @@ module Charty
16
16
  columns.to_a
17
17
  end
18
18
 
19
+ def column?(name)
20
+ return true if column_names.include?(name)
21
+
22
+ case name
23
+ when String
24
+ column_names.include?(name.to_sym)
25
+ when Symbol
26
+ column_names.include?(name.to_s)
27
+ else
28
+ false
29
+ end
30
+ end
31
+
19
32
  attr_reader :index
20
33
 
21
34
  def index=(values)
@@ -126,6 +139,81 @@ module Charty
126
139
  )
127
140
  end
128
141
 
142
+ def melt(id_vars: nil, value_vars: nil, var_name: nil, value_name: :value)
143
+ if column?(value_name)
144
+ raise ArgumentError,
145
+ "The value of `value_name` must not be matched to the existing column names."
146
+ end
147
+
148
+ case value_name
149
+ when Symbol
150
+ # do nothing
151
+ else
152
+ value_name = value.to_str.to_sym
153
+ end
154
+
155
+ id_vars = check_melt_vars(id_vars, :id_vars)
156
+ value_vars = check_melt_vars(value_vars, :value_vars) { self.column_names }
157
+ value_vars -= id_vars
158
+
159
+ case var_name
160
+ when nil
161
+ var_name = self.columns.name
162
+ var_name = :variable if var_name.nil?
163
+ when Symbol
164
+ # do nothing
165
+ else
166
+ var_name = var_name.to_str
167
+ end
168
+ var_name = var_name.to_sym
169
+
170
+ n_batch_rows = self.length
171
+ n_target_columns = value_vars.length
172
+ melted_data = id_vars.map { |cn|
173
+ id_values = self[nil, cn].to_a
174
+ [cn.to_sym, id_values * n_target_columns]
175
+ }.to_h
176
+
177
+ melted_data[var_name] = value_vars.map { |cn| Array.new(n_batch_rows, cn) }.flatten
178
+
179
+ melted_data[value_name] = value_vars.map { |cn| self[nil, cn].to_a }.flatten
180
+
181
+ Charty::Table.new(melted_data)
182
+ end
183
+
184
+ private def check_melt_vars(val, name)
185
+ if val.nil?
186
+ val = if block_given?
187
+ yield
188
+ else
189
+ []
190
+ end
191
+ end
192
+ case val
193
+ when nil
194
+ nil
195
+ when Array
196
+ missing = val.reject {|cn| self.column?(cn) }
197
+ if missing.empty?
198
+ val.map do |v|
199
+ case v
200
+ when Symbol
201
+ v.to_s
202
+ else
203
+ v.to_str
204
+ end
205
+ end
206
+ else
207
+ raise ArgumentError,
208
+ "Missing column names in `#{name}` (%s)" % missing.join(", ")
209
+ end
210
+ when Symbol
211
+ [val.to_s]
212
+ else
213
+ [val.to_str]
214
+ end
215
+ end
216
+
129
217
  private def check_na_position(val)
130
218
  case val
131
219
  when :first, "first"
@@ -57,12 +57,52 @@ module Charty
57
57
  column_data = if @data.has_vector?(column)
58
58
  @data[column]
59
59
  else
60
- @data[column.to_s]
60
+ case column
61
+ when String
62
+ @data[column.to_sym]
63
+ else
64
+ @data[column.to_s]
65
+ end
61
66
  end
62
67
  Vector.new(column_data)
63
68
  end
64
69
  end
65
70
 
71
+ def []=(key, values)
72
+ case key
73
+ when Symbol
74
+ sym_key = key
75
+ str_key = key.to_s
76
+ else
77
+ str_key = key.to_str
78
+ sym_key = str_key.to_sym
79
+ end
80
+ case
81
+ when @data.has_vector?(sym_key)
82
+ key = sym_key
83
+ when @data.has_vector?(str_key)
84
+ key = str_key
85
+ end
86
+
87
+ case values
88
+ when Charty::Vector
89
+ case values.adapter
90
+ when Charty::VectorAdapters::DaruVectorAdapter
91
+ @data[key] = values.adapter.data
92
+ else
93
+ @data[key] = values.to_a
94
+ end
95
+ else
96
+ orig_values = values
97
+ values = Array.try_convert(values)
98
+ if values.nil?
99
+ raise ArgumentError, "`values` must be convertible to Array"
100
+ end
101
+ @data[key] = values
102
+ end
103
+ return values
104
+ end
105
+
66
106
  private def check_type(type, data, name)
67
107
  return data if data.is_a?(type)
68
108
  raise TypeError, "#{name} must be a #{type}"
@@ -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
 
@@ -79,6 +81,9 @@ module Charty
79
81
  else
80
82
  unsupported_data_format
81
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] }
82
87
  else
83
88
  unsupported_data_format
84
89
  end
@@ -176,6 +181,11 @@ module Charty
176
181
  @data[column][row]
177
182
  else
178
183
  case column
184
+ when Array
185
+ slice_data = column.map { |cn|
186
+ [cn, self[nil, cn]]
187
+ }.to_h
188
+ return Charty::Table.new(slice_data, index: self.index)
179
189
  when Symbol
180
190
  sym_key = column
181
191
  str_key = column.to_s
@@ -189,8 +199,56 @@ module Charty
189
199
  else
190
200
  @data[str_key]
191
201
  end
192
- Vector.new(column_data, index: index, name: column)
202
+ # FIXME: Here column_data need to be dupped to
203
+ # prevent to overwrite the name of Pandas::Series
204
+ Vector.new(column_data.dup, index: index, name: column)
205
+ end
206
+ end
207
+
208
+ def []=(key, values)
209
+ case key
210
+ when Symbol
211
+ str_key = key.to_s
212
+ sym_key = key
213
+ else
214
+ str_key = key.to_str
215
+ sym_key = str_key.to_sym
216
+ end
217
+
218
+ orig_values = values
219
+ case values
220
+ when Charty::Vector
221
+ values = values.data
222
+ else
223
+ values = Array.try_convert(values)
193
224
  end
225
+ if values.nil?
226
+ raise ArgumentError,
227
+ "`values` must be convertible to Array"
228
+ end
229
+
230
+ if values.length != self.length
231
+ raise ArgumentError,
232
+ "`values` length does not match the length of the table"
233
+ end
234
+
235
+ if @data.key?(sym_key)
236
+ @data[sym_key] = values
237
+ elsif @data.key?(str_key)
238
+ @data[str_key] = values
239
+ elsif key == sym_key
240
+ @data[sym_key] = values
241
+ new_column = sym_key
242
+ else
243
+ @data[str_key] = values
244
+ new_column = sym_key
245
+ end
246
+
247
+ if new_column
248
+ self.columns = Index.new([*self.columns, new_column])
249
+ end
250
+
251
+ values
194
252
  end
195
253
 
196
254
  def each
@@ -42,6 +42,10 @@ module Charty
42
42
  end
43
43
  end
44
44
 
45
+ def column?(name)
46
+ data.__contains__(name)
47
+ end
48
+
45
49
  def index
46
50
  PandasIndex.new(data.index)
47
51
  end
@@ -76,8 +80,33 @@ module Charty
76
80
  if row
77
81
  @data[column][row]
78
82
  else
79
- Vector.new(@data[column])
83
+ case column
84
+ when Array
85
+ Table.new(@data[column])
86
+ else
87
+ Vector.new(@data[column])
88
+ end
89
+ end
90
+ end
91
+
92
+ def []=(key, values)
93
+ case values
94
+ when Charty::Vector
95
+ case values.adapter
96
+ when Charty::VectorAdapters::PandasSeriesAdapter
97
+ @data[key] = values.adapter.data
98
+ else
99
+ @data[key] = values.to_a
100
+ end
101
+ else
102
+ orig_values = values
103
+ values = Array.try_convert(values)
104
+ if values.nil?
105
+ raise ArgumentError, "`values` must be convertible to Array"
106
+ end
107
+ @data[key] = values
80
108
  end
109
+ return values
81
110
  end
82
111
 
83
112
  def drop_na
@@ -101,6 +130,15 @@ module Charty
101
130
  Charty::Table.new(data.reset_index)
102
131
  end
103
132
 
133
+ def melt(id_vars: nil, value_vars: nil, var_name: nil, value_name: :value)
134
+ id_vars = check_melt_vars(id_vars, :id_vars) { nil }
135
+ value_vars = check_melt_vars(value_vars, :value_vars) { nil }
136
+
137
+ Charty::Table.new(data.melt(id_vars: id_vars, value_vars: value_vars,
138
+ var_name: var_name, value_name: value_name,
139
+ ignore_index: true))
140
+ end
141
+
104
142
  class GroupBy < Charty::Table::GroupByBase
105
143
  def initialize(groupby)
106
144
  @groupby = groupby
@@ -116,6 +154,7 @@ module Charty
116
154
  each_group_key.to_a
117
155
  end
118
156
 
157
+ # TODO: test
119
158
  def each_group_key
120
159
  return enum_for(__method__) unless block_given?
121
160
 
@@ -146,6 +185,15 @@ module Charty
146
185
  end
147
186
  end
148
187
 
188
+ # TODO: test
189
+ def each_group
190
+ return enum_for(__method__) unless block_given?
191
+
192
+ each_group_key do |key|
193
+ yield(Array(key), self[key])
194
+ end
195
+ end
196
+
149
197
  def apply(*args, &block)
150
198
  res = @groupby.apply(->(data) {
151
199
  res = block.call(Charty::Table.new(data), *args)