charty 0.2.9 → 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 (34) hide show
  1. checksums.yaml +4 -4
  2. data/examples/bar_plot.rb +19 -0
  3. data/examples/box_plot.rb +17 -0
  4. data/examples/scatter_plot.rb +17 -0
  5. data/images/penguins_body_mass_g_flipper_length_mm_species_scatter_plot.png +0 -0
  6. data/images/penguins_body_mass_g_flipper_length_mm_species_sex_scatter_plot.png +0 -0
  7. data/images/penguins_species_body_mass_g_bar_plot_h.png +0 -0
  8. data/images/penguins_species_body_mass_g_bar_plot_v.png +0 -0
  9. data/images/penguins_species_body_mass_g_box_plot_h.png +0 -0
  10. data/images/penguins_species_body_mass_g_box_plot_v.png +0 -0
  11. data/images/penguins_species_body_mass_g_sex_bar_plot_v.png +0 -0
  12. data/images/penguins_species_body_mass_g_sex_box_plot_v.png +0 -0
  13. data/lib/charty/backends/plotly.rb +30 -4
  14. data/lib/charty/backends/plotly_helpers/notebook_renderer.rb +3 -1
  15. data/lib/charty/backends/pyplot.rb +30 -0
  16. data/lib/charty/backends/unicode_plot.rb +9 -9
  17. data/lib/charty/plot_methods.rb +41 -7
  18. data/lib/charty/plotters/abstract_plotter.rb +41 -9
  19. data/lib/charty/plotters/bar_plotter.rb +39 -0
  20. data/lib/charty/plotters/categorical_plotter.rb +9 -1
  21. data/lib/charty/plotters/distribution_plotter.rb +36 -6
  22. data/lib/charty/plotters/histogram_plotter.rb +6 -4
  23. data/lib/charty/plotters/line_plotter.rb +38 -5
  24. data/lib/charty/plotters/scatter_plotter.rb +4 -2
  25. data/lib/charty/table.rb +30 -23
  26. data/lib/charty/table_adapters/base_adapter.rb +88 -0
  27. data/lib/charty/table_adapters/daru_adapter.rb +41 -1
  28. data/lib/charty/table_adapters/hash_adapter.rb +54 -1
  29. data/lib/charty/table_adapters/pandas_adapter.rb +49 -1
  30. data/lib/charty/vector.rb +29 -1
  31. data/lib/charty/vector_adapters.rb +16 -0
  32. data/lib/charty/vector_adapters/pandas_adapter.rb +10 -1
  33. data/lib/charty/version.rb +1 -1
  34. metadata +6 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 37ead1e86203bd6580125f4ed97039c6b512e9ae3037a0bc386743ee436f184c
4
- data.tar.gz: a94e66292a866acde67c0cfc51bd8eebd5840af68e2d4112caafce80bdd37ed2
3
+ metadata.gz: d885d476eadfadd3f76f138707ffaf6166eec006b4528fb19c3acce0b97b1c85
4
+ data.tar.gz: 0630f23f19b65177f3b6dc238717a81a019f9dfa40a40c143d71dcc5c5f760f0
5
5
  SHA512:
6
- metadata.gz: b518be17e0ae85405285fe22022bc698d0c080e08a0682f055ae70c97655ed313038e6e2899b7c961bd73c0afe1b6b0c25bdb2085bd3a66dddd57d3e6245df55
7
- data.tar.gz: 0bc460c78d178b098c65ab8737efddac0a8484abd920282d185533b72396f0abad62ea247be9e8af4286a70b7a4dc2069e88ff1675ba446af65b5c67edfb59d8
6
+ metadata.gz: 535a69b8d794ccbd30b4328f3188c8a8b50c850cfd84285f79b31162296a9d981313e561ea1089a68be3368b7eb1c83ccc3cb2ff511b8f79aef77e544e261bbe
7
+ data.tar.gz: 804de2468433db38b5381bcbccab4c28e84c7592bce5c5a98a539ce99c589449759e65ffe7f2e808bd3c5cb5e002ee2219219111561f66f590dbb0ac2c0757bc
@@ -0,0 +1,19 @@
1
+ # This example generates box_plot results in README.md
2
+
3
+ require "charty"
4
+ require "datasets"
5
+ require "matplotlib"
6
+
7
+ Charty::Backends.use(:pyplot)
8
+ Matplotlib.use(:agg)
9
+
10
+ penguins = Datasets::Penguins.new
11
+
12
+ Charty.bar_plot(data: penguins, x: :species, y: :body_mass_g)
13
+ .save("penguins_species_body_mass_g_bar_plot_v.png")
14
+
15
+ Charty.bar_plot(data: penguins, x: :body_mass_g, y: :species)
16
+ .save("penguins_species_body_mass_g_bar_plot_h.png")
17
+
18
+ Charty.bar_plot(data: penguins, x: :species, y: :body_mass_g, color: :sex)
19
+ .save("penguins_species_body_mass_g_sex_bar_plot_v.png")
@@ -0,0 +1,17 @@
1
+ require "charty"
2
+ require "datasets"
3
+ require "matplotlib"
4
+
5
+ Charty::Backends.use(:pyplot)
6
+ Matplotlib.use(:agg)
7
+
8
+ penguins = Datasets::Penguins.new
9
+
10
+ Charty.box_plot(data: penguins, x: :species, y: :body_mass_g)
11
+ .save("penguins_species_body_mass_g_box_plot_v.png")
12
+
13
+ Charty.box_plot(data: penguins, x: :body_mass_g, y: :species)
14
+ .save("penguins_species_body_mass_g_box_plot_h.png")
15
+
16
+ Charty.box_plot(data: penguins, x: :species, y: :body_mass_g, color: :sex)
17
+ .save("penguins_species_body_mass_g_sex_box_plot_v.png")
@@ -0,0 +1,17 @@
1
+ require "charty"
2
+ require "datasets"
3
+ require "matplotlib"
4
+
5
+ Charty::Backends.use(:pyplot)
6
+ Matplotlib.use(:agg)
7
+
8
+ penguins = Datasets::Penguins.new
9
+
10
+ Charty.scatter_plot(data: penguins, x: :body_mass_g, y: :flipper_length_mm)
11
+ .save("penguins_body_mass_g_flipper_length_mm_scatter_plot.png")
12
+
13
+ Charty.scatter_plot(data: penguins, x: :body_mass_g, y: :flipper_length_mm, color: :species)
14
+ .save("penguins_body_mass_g_flipper_length_mm_species_scatter_plot.png")
15
+
16
+ Charty.scatter_plot(data: penguins, x: :body_mass_g, y: :flipper_length_mm, color: :species, style: :sex)
17
+ .save("penguins_body_mass_g_flipper_length_mm_species_sex_scatter_plot.png")
@@ -134,10 +134,8 @@ module Charty
134
134
 
135
135
  if orient == :v
136
136
  x, y = bar_pos, values
137
- x = group_names unless group_names.nil?
138
137
  else
139
138
  x, y = values, bar_pos
140
- y = group_names unless group_names.nil?
141
139
  end
142
140
 
143
141
  trace = {
@@ -241,9 +239,9 @@ module Charty
241
239
  }
242
240
 
243
241
  if orient == :v
244
- trace.update(y: values, x: group_keys)
242
+ trace.update(y: values, x: group_keys.map(&:to_s))
245
243
  else
246
- trace.update(x: values, y: group_keys)
244
+ trace.update(x: values, y: group_keys.map(&:to_s))
247
245
  end
248
246
 
249
247
  trace
@@ -613,6 +611,11 @@ module Charty
613
611
  end
614
612
  end
615
613
 
614
+ def set_title(title)
615
+ @layout[:title] ||= {}
616
+ @layout[:title][:text] = title
617
+ end
618
+
616
619
  def set_xlabel(label)
617
620
  @layout[:xaxis] ||= {}
618
621
  @layout[:xaxis][:title] = label
@@ -657,6 +660,29 @@ module Charty
657
660
  @layout[:yaxis][:range] = [min, max]
658
661
  end
659
662
 
663
+ def set_xscale(scale)
664
+ scale = check_scale_type(scale, :xscale)
665
+ @layout[:xaxis] ||= {}
666
+ @layout[:xaxis][:type] = scale
667
+ end
668
+
669
+ def set_yscale(scale)
670
+ scale = check_scale_type(scale, :yscale)
671
+ @layout[:yaxis] ||= {}
672
+ @layout[:yaxis][:type] = scale
673
+ end
674
+
675
+ private def check_scale_type(val, name)
676
+ case
677
+ when :linear, :log
678
+ val
679
+ else
680
+ raise ArgumentError,
681
+ "Invalid #{name} type: %p" % val,
682
+ caller
683
+ end
684
+ end
685
+
660
686
  def disable_xaxis_grid
661
687
  # do nothing
662
688
  end
@@ -7,7 +7,7 @@ module Charty
7
7
  @initialized = false
8
8
  end
9
9
 
10
- def activate()
10
+ def activate
11
11
  return if @initialized
12
12
 
13
13
  unless IRubyHelper.iruby_notebook?
@@ -50,6 +50,8 @@ module Charty
50
50
  END_SCRIPT
51
51
  end
52
52
  IRuby.display(script, mime: "text/html")
53
+ @initialized = true
54
+ nil
53
55
  end
54
56
 
55
57
  def render(figure, element_id: nil, post_script: nil)
@@ -662,6 +662,10 @@ module Charty
662
662
  end
663
663
  end
664
664
 
665
+ def set_title(title)
666
+ @pyplot.gca.set_title(String(title))
667
+ end
668
+
665
669
  def set_xlabel(label)
666
670
  @pyplot.gca.set_xlabel(String(label))
667
671
  end
@@ -694,6 +698,27 @@ module Charty
694
698
  @pyplot.gca.set_ylim(Float(min), Float(max))
695
699
  end
696
700
 
701
+ def set_xscale(scale)
702
+ scale = check_scale_type(scale, :xscale)
703
+ @pyplot.gca.set_xscale(scale)
704
+ end
705
+
706
+ def set_yscale(scale)
707
+ scale = check_scale_type(scale, :yscale)
708
+ @pyplot.gca.set_yscale(scale)
709
+ end
710
+
711
+ private def check_scale_type(val, name)
712
+ case
713
+ when :linear, :log
714
+ val
715
+ else
716
+ raise ArgumentError,
717
+ "Invalid #{name} type: %p" % val,
718
+ caller
719
+ end
720
+ end
721
+
697
722
  def disable_xaxis_grid
698
723
  @pyplot.gca.xaxis.grid(false)
699
724
  end
@@ -715,6 +740,11 @@ module Charty
715
740
  nil
716
741
  end
717
742
 
743
+ def render_mimebundle(include: [], exclude: [])
744
+ show
745
+ {}
746
+ end
747
+
718
748
  SAVEFIG_OPTIONAL_PARAMS = [
719
749
  :dpi, :quality, :optimize, :progressive, :facecolor, :edgecolor,
720
750
  :orientation, :papertype, :transparent, :bbox_inches, :pad_inches,
@@ -16,7 +16,7 @@ module Charty
16
16
  @layout = {}
17
17
  end
18
18
 
19
- def bar(bar_pos, values, color: nil)
19
+ def bar(bar_pos, _group_names, values, colors, _orient, **kwargs)
20
20
  @figure = {
21
21
  type: :bar,
22
22
  bar_pos: bar_pos,
@@ -24,7 +24,7 @@ module Charty
24
24
  }
25
25
  end
26
26
 
27
- def box_plot(plot_data, positions, color:, gray:)
27
+ def box_plot(plot_data, positions, **kwargs)
28
28
  @figure = { type: :box, data: plot_data }
29
29
  end
30
30
 
@@ -52,13 +52,13 @@ module Charty
52
52
  # do nothing
53
53
  end
54
54
 
55
- def show
56
- case @figure[:type]
57
- when :bar
58
- plot = ::UnicodePlot.barplot(@layout[:xtick_labels], @figure[:values], xlabel: @layout[:xlabel])
59
- when :box
60
- plot = ::UnicodePlot.boxplot(@layout[:xtick_labels], @figure[:data], xlabel: @layout[:xlabel])
61
- end
55
+ def render(**kwargs)
56
+ plot = case @figure[:type]
57
+ when :bar
58
+ ::UnicodePlot.barplot(@layout[:xtick_labels], @figure[:values], xlabel: @layout[:xlabel])
59
+ when :box
60
+ ::UnicodePlot.boxplot(@layout[:xtick_labels], @figure[:data], xlabel: @layout[:xlabel])
61
+ end
62
62
  sio = StringIO.new
63
63
  class << sio
64
64
  def tty?; true; end
@@ -23,19 +23,24 @@ module Charty
23
23
  # @param cap_size Width of the caps on error bars.
24
24
  # @param dodge [true,false] If true, bar position is shifted along the
25
25
  # categorical axis for avoid overlapping when the color-dimension is used.
26
+ # @param log [true,false] Set the value-axis (e.g. Y-axis if orient is :v) to be log scale.
27
+ # @param x_label [String,Symbol,#to_str,nil] X-axis label.
28
+ # @param y_label [String,Symbol,#to_str,nil] Y-axis label.
29
+ # @param title [String,Symbol,#to_str,nil] Title text.
26
30
  def bar_plot(x: nil, y: nil, color: nil, data: nil,
27
31
  order: nil, color_order: nil,
28
32
  estimator: :mean, ci: 95, n_boot: 1000, units: nil, random: nil,
29
33
  orient: nil, key_color: nil, palette: nil, saturation: 1r,
30
34
  error_color: [0.26, 0.26, 0.26], error_width: nil, cap_size: nil,
31
- dodge: true, **options, &block)
35
+ dodge: true, log: false, x_label: nil, y_label: nil, title: nil,
36
+ **options, &block)
32
37
  Plotters::BarPlotter.new(
33
38
  data: data, variables: { x: x, y: y, color: color },
34
39
  order: order, orient: orient,
35
40
  estimator: estimator, ci: ci, n_boot: n_boot, units: units, random: random,
36
41
  color_order: color_order, key_color: key_color, palette: palette, saturation: saturation,
37
42
  error_color: error_color, error_width: error_width, cap_size: cap_size,
38
- dodge: dodge,
43
+ dodge: dodge, log: log, x_label: x_label, y_label: y_label, title: title,
39
44
  **options, &block
40
45
  )
41
46
  end
@@ -43,7 +48,8 @@ module Charty
43
48
  def count_plot(x: nil, y: nil, color: nil, data: nil,
44
49
  order: nil, color_order: nil,
45
50
  orient: nil, key_color: nil, palette: nil, saturation: 1r,
46
- dodge: true, **options, &block)
51
+ dodge: true, log: false, x_label: nil, y_label: nil, title: nil,
52
+ **options, &block)
47
53
  case
48
54
  when x.nil? && !y.nil?
49
55
  x = y
@@ -70,6 +76,9 @@ module Charty
70
76
  palette: palette,
71
77
  saturation: saturation,
72
78
  dodge: dodge,
79
+ x_label: x_label,
80
+ y_label: y_label,
81
+ title: title,
73
82
  **options
74
83
  ) do |plotter|
75
84
  plotter.value_label = "count"
@@ -106,11 +115,15 @@ module Charty
106
115
  # @param whisker Propotion of the IQR past the low and high quartiles to
107
116
  # extend the plot whiskers. Points outside of this range will be
108
117
  # treated as outliers.
118
+ # @param x_label [String,Symbol,#to_str,nil] X-axis label.
119
+ # @param y_label [String,Symbol,#to_str,nil] Y-axis label.
120
+ # @param title [String,Symbol,#to_str,nil] Title text.
109
121
  def box_plot(x: nil, y: nil, color: nil, data: nil,
110
122
  order: nil, color_order: nil,
111
123
  orient: nil, key_color: nil, palette: nil, saturation: 1r,
112
124
  width: 0.8r, dodge: true, flier_size: 5, line_width: nil,
113
- whisker: 1.5, **options, &block)
125
+ whisker: 1.5, x_label: nil, y_label: nil, title: nil,
126
+ **options, &block)
114
127
  Plotters::BoxPlotter.new(
115
128
  data: data,
116
129
  variables: { x: x, y: y, color: color },
@@ -125,6 +138,9 @@ module Charty
125
138
  flier_size: flier_size,
126
139
  line_width: line_width,
127
140
  whisker: whisker,
141
+ x_label: x_label,
142
+ y_label: y_label,
143
+ title: title,
128
144
  **options,
129
145
  &block
130
146
  )
@@ -164,13 +180,18 @@ module Charty
164
180
  # :full, every group will get an entry in the legend. If :auto,
165
181
  # choose between brief or full representation based on number of
166
182
  # levels. If false, no legend data is added and no legend is drawn.
183
+ # @param x_label [String,Symbol,#to_str,nil] X-axis label.
184
+ # @param y_label [String,Symbol,#to_str,nil] Y-axis label.
185
+ # @param title [String,Symbol,#to_str,nil] Title text.
167
186
  def line_plot(x: nil, y: nil, color: nil, style: nil, size: nil,
168
187
  data: nil, key_color: nil, palette: nil, color_order: nil,
169
188
  color_norm: nil, sizes: nil, size_order: nil, size_norm: nil,
170
189
  markers: nil, dashes: true, style_order: nil,
171
190
  units: nil, estimator: :mean, n_boot: 1000, random: nil,
172
191
  sort: true, err_style: :band, err_params: nil, error_bar: [:ci, 95],
173
- x_scale: :linear, y_scale: :linear, legend: :auto, **options, &block)
192
+ x_scale: :linear, y_scale: :linear, legend: :auto,
193
+ x_label: nil, y_label: nil, title: nil,
194
+ **options, &block)
174
195
  Plotters::LinePlotter.new(
175
196
  data: data,
176
197
  variables: { x: x, y: y, color: color, style: style, size: size },
@@ -195,6 +216,9 @@ module Charty
195
216
  x_scale: x_scale,
196
217
  y_scale: y_scale,
197
218
  legend: legend,
219
+ x_label: x_label,
220
+ y_label: y_label,
221
+ title: title,
198
222
  **options,
199
223
  &block
200
224
  )
@@ -225,11 +249,14 @@ module Charty
225
249
  # :full, every group will get an entry in the legend. If :auto,
226
250
  # choose between brief or full representation based on number of
227
251
  # levels. If false, no legend data is added and no legend is drawn.
252
+ # @param x_label [String,Symbol,#to_str,nil] X-axis label.
253
+ # @param y_label [String,Symbol,#to_str,nil] Y-axis label.
254
+ # @param title [String,Symbol,#to_str,nil] Title text.
228
255
  def scatter_plot(x: nil, y: nil, color: nil, style: nil, size: nil,
229
256
  data: nil, key_color: nil, palette: nil, color_order: nil,
230
257
  color_norm: nil, sizes: nil, size_order: nil, size_norm: nil,
231
258
  markers: true, style_order: nil, alpha: nil, legend: :auto,
232
- **options, &block)
259
+ x_label: nil, y_label: nil, title: nil, **options, &block)
233
260
  Plotters::ScatterPlotter.new(
234
261
  data: data,
235
262
  variables: { x: x, y: y, color: color, style: style, size: size },
@@ -244,6 +271,9 @@ module Charty
244
271
  style_order: style_order,
245
272
  alpha: alpha,
246
273
  legend: legend,
274
+ x_label: x_label,
275
+ y_label: y_label,
276
+ title: title,
247
277
  **options,
248
278
  &block
249
279
  )
@@ -253,7 +283,8 @@ module Charty
253
283
  stat: :count, bins: :auto,
254
284
  bin_range: nil, common_bins: true,
255
285
  key_color: nil, palette: nil, color_order: nil, color_norm: nil,
256
- legend: true, **options, &block)
286
+ legend: true, x_label: nil, y_label: nil, title: nil,
287
+ **options, &block)
257
288
  # TODO: support following arguments
258
289
  # - wiehgts
259
290
  # - binwidth
@@ -287,6 +318,9 @@ module Charty
287
318
  color_order: color_order,
288
319
  color_norm: color_norm,
289
320
  legend: legend,
321
+ x_label: x_label,
322
+ y_label: y_label,
323
+ title: title,
290
324
  **options,
291
325
  &block)
292
326
  end
@@ -35,6 +35,8 @@ module Charty
35
35
  end
36
36
 
37
37
  def data=(data)
38
+ # TODO: Convert a Charty::Vector to a Charty::Table so that
39
+ # the Charty::Vector is handled as a wide form data
38
40
  @data = case data
39
41
  when nil, Charty::Table
40
42
  data
@@ -81,6 +83,24 @@ module Charty
81
83
  end
82
84
  end
83
85
 
86
+ attr_reader :x_label
87
+
88
+ def x_label=(val)
89
+ @x_label = check_string(val, :x_label, allow_nil: true)
90
+ end
91
+
92
+ attr_reader :y_label
93
+
94
+ def y_label=(val)
95
+ @y_label = check_string(val, :y_label, allow_nil: true)
96
+ end
97
+
98
+ attr_reader :title
99
+
100
+ def title=(val)
101
+ @title = check_string(val, :title, allow_nil: true)
102
+ end
103
+
84
104
  private def substitute_options(options)
85
105
  options.each do |key, val|
86
106
  send("#{key}=", val)
@@ -138,6 +158,27 @@ module Charty
138
158
  end
139
159
  end
140
160
 
161
+ private def check_string(value, name, allow_nil: false)
162
+ case value
163
+ when Symbol
164
+ value.to_s
165
+ else
166
+ if allow_nil && value.nil?
167
+ nil
168
+ else
169
+ orig_value = value
170
+ value = String.try_convert(value)
171
+ if value.nil?
172
+ raise ArgumentError,
173
+ "`#{name}` must be convertible to String: %p" % orig_value,
174
+ caller
175
+ else
176
+ value
177
+ end
178
+ end
179
+ end
180
+ end
181
+
141
182
  private def variable_type(vector, boolean_type=:numeric)
142
183
  if vector.numeric?
143
184
  :numeric
@@ -181,15 +222,6 @@ module Charty
181
222
  data = processed ? processed_data : plot_data
182
223
  data = data.drop_na if drop_na
183
224
 
184
- levels = var_levels.dup
185
-
186
- ([:x, :y] & grouping_vars).each do |axis|
187
- levels[axis] = plot_data[axis].categorical_order()
188
- if processed
189
- # TODO: perform inverse conversion of axis scaling here
190
- end
191
- end
192
-
193
225
  if not grouping_vars.empty?
194
226
  grouped = data.group_by(grouping_vars, sort: false)
195
227
  grouped.each_group do |group_key, group_data|
@@ -42,6 +42,12 @@ module Charty
42
42
  @cap_size = check_number(cap_size, :cap_size, allow_nil: true)
43
43
  end
44
44
 
45
+ attr_reader :log
46
+
47
+ def log=(val)
48
+ @log = check_boolean(val, :log)
49
+ end
50
+
45
51
  private def render_plot(backend, **)
46
52
  draw_bars(backend)
47
53
  annotate_axes(backend)
@@ -81,6 +87,39 @@ module Charty
81
87
  end
82
88
  end
83
89
 
90
+ private def annotate_axes(backend)
91
+ super
92
+
93
+ if self.log
94
+ min_value, max_value = @estimations.minmax
95
+ if @plot_colors
96
+ unless @conf_int.empty?
97
+ min_value = [min_value, @conf_int[0]].min
98
+ max_value = [max_value, @conf_int[1]].max
99
+ end
100
+ else
101
+ ci_min = Util.filter_map(@conf_int) { |ci| ci[0] unless ci.empty? }
102
+ ci_max = Util.filter_map(@conf_int) { |ci| ci[1] unless ci.empty? }
103
+ min_value = [min_value, ci_min.min].min unless ci_min.empty?
104
+ max_value = [max_value, ci_max.max].max unless ci_max.empty?
105
+ end
106
+ if min_value > 1
107
+ min_value = 0
108
+ else
109
+ min_value = Math.log10(min_value).floor
110
+ end
111
+ max_value = Math.log10(max_value).ceil
112
+ case self.orient
113
+ when :v
114
+ backend.set_yscale(:log)
115
+ backend.set_ylim(min_value, max_value)
116
+ else
117
+ backend.set_xscale(:log)
118
+ backend.set_xlim(min_value, max_value)
119
+ end
120
+ end
121
+ end
122
+
84
123
  private def setup_estimations
85
124
  if @color_names.nil?
86
125
  setup_estimations_with_single_color_group
@@ -134,6 +134,7 @@ module Charty
134
134
  order = @order # TODO: supply order via parameter
135
135
  unless order
136
136
  order = @data.column_names.select do |cn|
137
+ # TODO: Use Charty::Vector#numeric?
137
138
  @data[cn].all? {|x| Float(x, exception: false) }
138
139
  end
139
140
  end
@@ -230,6 +231,7 @@ module Charty
230
231
  end
231
232
  return :h
232
233
  end
234
+
233
235
  case orient
234
236
  when :v
235
237
  if require_numeric && y_type != :numeric
@@ -263,7 +265,9 @@ module Charty
263
265
  private def group_long_form(vals, groups, group_order)
264
266
  grouped_vals = vals.group_by(groups)
265
267
 
266
- plot_data = group_order.map {|g| grouped_vals[g] || [] }
268
+ plot_data = group_order.map do |g|
269
+ grouped_vals[g] || Charty::Vector.new([])
270
+ end
267
271
 
268
272
  if vals.respond_to?(:name)
269
273
  value_label = vals.name
@@ -347,11 +351,15 @@ module Charty
347
351
  end
348
352
 
349
353
  private def annotate_axes(backend)
354
+ backend.set_title(self.title) if self.title
355
+
350
356
  if orient == :v
351
357
  xlabel, ylabel = @group_label, @value_label
352
358
  else
353
359
  xlabel, ylabel = @value_label, @group_label
354
360
  end
361
+ xlabel = self.x_label if self.x_label
362
+ ylabel = self.y_label if self.y_label
355
363
  backend.set_xlabel(xlabel) unless xlabel.nil?
356
364
  backend.set_ylabel(ylabel) unless ylabel.nil?
357
365
 
@@ -3,7 +3,14 @@ module Charty
3
3
  class DistributionPlotter < AbstractPlotter
4
4
  def flat_structure
5
5
  {
6
- x: :values
6
+ x: :@values
7
+ }
8
+ end
9
+
10
+ def wide_structure
11
+ {
12
+ x: :@values,
13
+ color: :@columns
7
14
  }
8
15
  end
9
16
 
@@ -71,7 +78,6 @@ module Charty
71
78
  return
72
79
  end
73
80
 
74
- # TODO: detect flat data
75
81
  flat = data.is_a?(Charty::Vector)
76
82
  if flat
77
83
  @plot_data = {}
@@ -79,10 +85,10 @@ module Charty
79
85
 
80
86
  [:x, :y].each do |var|
81
87
  case self.flat_structure[var]
82
- when :index
88
+ when :@index
83
89
  @plot_data[var] = data.index.to_a
84
90
  @variables[var] = data.index.name
85
- when :values
91
+ when :@values
86
92
  @plot_data[var] = data.to_a
87
93
  @variables[var] = data.name
88
94
  end
@@ -90,8 +96,32 @@ module Charty
90
96
 
91
97
  @plot_data = Charty::Table.new(@plot_data)
92
98
  else
93
- raise NotImplementedError,
94
- "wide-form input is not supported"
99
+ numeric_columns = @data.column_names.select do |cn|
100
+ @data[cn].numeric?
101
+ end
102
+ wide_data = @data[numeric_columns]
103
+
104
+ melt_params = {var_name: :@columns, value_name: :@values }
105
+ if self.wide_structure.include?(:index)
106
+ melt_params[:id_vars] = :@index
107
+ end
108
+
109
+ @plot_data = wide_data.melt(**melt_params)
110
+ @variables = {}
111
+ self.wide_structure.each do |var, attr|
112
+ @plot_data[var] = @plot_data[attr]
113
+
114
+ @variables[var] = case attr
115
+ when :@columns
116
+ wide_data.columns.name
117
+ when :@index
118
+ wide_data.index.name
119
+ else
120
+ nil
121
+ end
122
+ end
123
+
124
+ @plot_data = @plot_data[self.wide_structure.keys]
95
125
  end
96
126
  end
97
127
 
@@ -220,14 +220,16 @@ module Charty
220
220
  end
221
221
 
222
222
  private def annotate_axes(backend)
223
+ backend.set_title(self.title) if self.title
224
+
223
225
  if univariate?
224
- xlabel = self.variables[:x]
225
- ylabel = self.variables[:y]
226
+ xlabel = self.x_label || self.variables[:x]
227
+ ylabel = self.y_label || self.variables[:y]
226
228
  case self.univariate_variable
227
229
  when :x
228
- ylabel = self.stat.to_s.capitalize
230
+ ylabel ||= self.stat.to_s.capitalize
229
231
  else
230
- xlabel = self.stat.to_s.capitalize
232
+ xlabel ||= self.stat.to_s.capitalize
231
233
  end
232
234
  backend.set_ylabel(ylabel) if ylabel
233
235
  backend.set_xlabel(xlabel) if xlabel
@@ -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
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}"
@@ -181,6 +181,11 @@ module Charty
181
181
  @data[column][row]
182
182
  else
183
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)
184
189
  when Symbol
185
190
  sym_key = column
186
191
  str_key = column.to_s
@@ -194,10 +199,58 @@ module Charty
194
199
  else
195
200
  @data[str_key]
196
201
  end
197
- 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)
198
205
  end
199
206
  end
200
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)
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
252
+ end
253
+
201
254
  def each
202
255
  i, n = 0, shape[0]
203
256
  while i < n
@@ -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)
data/lib/charty/vector.rb CHANGED
@@ -51,6 +51,32 @@ module Charty
51
51
 
52
52
  def_delegators :adapter, :mean, :stdev
53
53
 
54
+ def_delegators :adapter, :scale, :scale_inverse
55
+
56
+ def scale(method)
57
+ case method
58
+ when :linear
59
+ self
60
+ when :log
61
+ adapter.log_scale(method)
62
+ else
63
+ raise ArgumentError,
64
+ "Invalid scaling method: %p" % method
65
+ end
66
+ end
67
+
68
+ def scale_inverse(method)
69
+ case method
70
+ when :linear
71
+ self
72
+ when :log
73
+ adapter.inverse_log_scale(method)
74
+ else
75
+ raise ArgumentError,
76
+ "Invalid scaling method: %p" % method
77
+ end
78
+ end
79
+
54
80
  # TODO: write test
55
81
  def categorical_order(order=nil)
56
82
  if order.nil?
@@ -59,7 +85,9 @@ module Charty
59
85
  order = categories
60
86
  else
61
87
  order = unique_values.compact
62
- order.sort! if numeric?
88
+ if numeric?
89
+ order.sort_by! {|x| Util.missing?(x) ? Float::INFINITY : x }
90
+ end
63
91
  end
64
92
  order.compact!
65
93
  end
@@ -99,6 +99,22 @@ module Charty
99
99
  def stdev(population: false)
100
100
  Statistics.stdev(data, population: population)
101
101
  end
102
+
103
+ def log_scale(method)
104
+ Charty::Vector.new(
105
+ self.map {|x| Math.log10(x) },
106
+ index: index,
107
+ name: name
108
+ )
109
+ end
110
+
111
+ def inverse_log_scale(method)
112
+ Charty::Vector.new(
113
+ self.map {|x| 10.0 ** x },
114
+ index: index,
115
+ name: name
116
+ )
117
+ end
102
118
  end
103
119
 
104
120
  module NameSupport
@@ -152,7 +152,8 @@ module Charty
152
152
  group_keys = grouper.unique.to_a
153
153
  groups = data.groupby(grouper)
154
154
  group_keys.map {|g|
155
- [g, Charty::Vector.new(groups.get_group(g))]
155
+ g_vals = groups.get_group(g) rescue []
156
+ [g, Charty::Vector.new(g_vals)]
156
157
  }.to_h
157
158
  when Charty::Vector
158
159
  case grouper.adapter
@@ -194,6 +195,14 @@ module Charty
194
195
  q = q.map {|x| x / 100.0 }
195
196
  data.quantile(q)
196
197
  end
198
+
199
+ def log_scale(method)
200
+ Charty::Vector.new(Numpy.log10(data))
201
+ end
202
+
203
+ def inverse_log_scale(method)
204
+ Charty::Vector.new(Numpy.power(10, data))
205
+ end
197
206
  end
198
207
  end
199
208
  end
@@ -1,5 +1,5 @@
1
1
  module Charty
2
- VERSION = "0.2.9"
2
+ VERSION = "0.2.10"
3
3
 
4
4
  module Version
5
5
  numbers, TAG = VERSION.split("-")
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: charty
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.9
4
+ version: 0.2.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - youchan
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: exe
12
12
  cert_chain: []
13
- date: 2021-06-24 00:00:00.000000000 Z
13
+ date: 2021-08-18 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: red-colors
@@ -246,6 +246,8 @@ files:
246
246
  - charty.gemspec
247
247
  - examples/Gemfile
248
248
  - examples/active_record.ipynb
249
+ - examples/bar_plot.rb
250
+ - examples/box_plot.rb
249
251
  - examples/daru.ipynb
250
252
  - examples/iris_dataset.ipynb
251
253
  - examples/nmatrix.ipynb
@@ -284,6 +286,7 @@ files:
284
286
  - examples/sample_images/subplot_pyplot.png
285
287
  - examples/sample_pyplot.ipynb
286
288
  - examples/sample_rubyplot.ipynb
289
+ - examples/scatter_plot.rb
287
290
  - images/design_concept.png
288
291
  - images/penguins_body_mass_g_flipper_length_mm_scatter_plot.png
289
292
  - images/penguins_body_mass_g_flipper_length_mm_species_scatter_plot.png
@@ -369,7 +372,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
369
372
  - !ruby/object:Gem::Version
370
373
  version: '0'
371
374
  requirements: []
372
- rubygems_version: 3.2.3
375
+ rubygems_version: 3.2.23
373
376
  signing_key:
374
377
  specification_version: 4
375
378
  summary: Visualizing your data in a simple way.