charty 0.2.3 → 0.2.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +56 -23
  3. data/.github/workflows/nmatrix.yml +67 -0
  4. data/.github/workflows/pycall.yml +86 -0
  5. data/Gemfile +18 -0
  6. data/README.md +172 -4
  7. data/Rakefile +4 -5
  8. data/charty.gemspec +10 -6
  9. data/examples/sample_images/hist_gruff.png +0 -0
  10. data/images/penguins_body_mass_g_flipper_length_mm_scatter_plot.png +0 -0
  11. data/images/penguins_body_mass_g_flipper_length_mm_species_scatter_plot.png +0 -0
  12. data/images/penguins_body_mass_g_flipper_length_mm_species_sex_scatter_plot.png +0 -0
  13. data/images/penguins_species_body_mass_g_bar_plot_h.png +0 -0
  14. data/images/penguins_species_body_mass_g_bar_plot_v.png +0 -0
  15. data/images/penguins_species_body_mass_g_box_plot_h.png +0 -0
  16. data/images/penguins_species_body_mass_g_box_plot_v.png +0 -0
  17. data/images/penguins_species_body_mass_g_sex_bar_plot_v.png +0 -0
  18. data/images/penguins_species_body_mass_g_sex_box_plot_v.png +0 -0
  19. data/lib/charty.rb +8 -1
  20. data/lib/charty/backends/bokeh.rb +2 -2
  21. data/lib/charty/backends/google_charts.rb +1 -1
  22. data/lib/charty/backends/gruff.rb +14 -3
  23. data/lib/charty/backends/plotly.rb +731 -32
  24. data/lib/charty/backends/plotly_helpers/html_renderer.rb +203 -0
  25. data/lib/charty/backends/plotly_helpers/notebook_renderer.rb +87 -0
  26. data/lib/charty/backends/plotly_helpers/plotly_renderer.rb +121 -0
  27. data/lib/charty/backends/pyplot.rb +514 -66
  28. data/lib/charty/backends/rubyplot.rb +1 -1
  29. data/lib/charty/cache_dir.rb +27 -0
  30. data/lib/charty/dash_pattern_generator.rb +57 -0
  31. data/lib/charty/index.rb +213 -0
  32. data/lib/charty/iruby_helper.rb +18 -0
  33. data/lib/charty/linspace.rb +1 -1
  34. data/lib/charty/plot_methods.rb +283 -8
  35. data/lib/charty/plotter.rb +2 -2
  36. data/lib/charty/plotters.rb +11 -0
  37. data/lib/charty/plotters/abstract_plotter.rb +186 -16
  38. data/lib/charty/plotters/bar_plotter.rb +189 -7
  39. data/lib/charty/plotters/box_plotter.rb +64 -11
  40. data/lib/charty/plotters/categorical_plotter.rb +272 -40
  41. data/lib/charty/plotters/count_plotter.rb +7 -0
  42. data/lib/charty/plotters/distribution_plotter.rb +143 -0
  43. data/lib/charty/plotters/estimation_support.rb +84 -0
  44. data/lib/charty/plotters/histogram_plotter.rb +186 -0
  45. data/lib/charty/plotters/line_plotter.rb +300 -0
  46. data/lib/charty/plotters/random_support.rb +25 -0
  47. data/lib/charty/plotters/relational_plotter.rb +635 -0
  48. data/lib/charty/plotters/scatter_plotter.rb +80 -0
  49. data/lib/charty/plotters/vector_plotter.rb +6 -0
  50. data/lib/charty/statistics.rb +96 -2
  51. data/lib/charty/table.rb +160 -15
  52. data/lib/charty/table_adapters.rb +2 -0
  53. data/lib/charty/table_adapters/active_record_adapter.rb +17 -9
  54. data/lib/charty/table_adapters/base_adapter.rb +166 -0
  55. data/lib/charty/table_adapters/daru_adapter.rb +39 -3
  56. data/lib/charty/table_adapters/datasets_adapter.rb +13 -2
  57. data/lib/charty/table_adapters/hash_adapter.rb +141 -16
  58. data/lib/charty/table_adapters/narray_adapter.rb +25 -6
  59. data/lib/charty/table_adapters/nmatrix_adapter.rb +15 -5
  60. data/lib/charty/table_adapters/pandas_adapter.rb +163 -0
  61. data/lib/charty/util.rb +28 -0
  62. data/lib/charty/vector.rb +69 -0
  63. data/lib/charty/vector_adapters.rb +187 -0
  64. data/lib/charty/vector_adapters/array_adapter.rb +101 -0
  65. data/lib/charty/vector_adapters/daru_adapter.rb +163 -0
  66. data/lib/charty/vector_adapters/narray_adapter.rb +182 -0
  67. data/lib/charty/vector_adapters/nmatrix_adapter.rb +37 -0
  68. data/lib/charty/vector_adapters/numpy_adapter.rb +168 -0
  69. data/lib/charty/vector_adapters/pandas_adapter.rb +199 -0
  70. data/lib/charty/version.rb +1 -1
  71. metadata +92 -25
@@ -0,0 +1,166 @@
1
+ require "forwardable"
2
+
3
+ module Charty
4
+ module TableAdapters
5
+ class BaseAdapter
6
+ extend Forwardable
7
+ include Enumerable
8
+
9
+ attr_reader :columns
10
+
11
+ def columns=(values)
12
+ @columns = check_and_convert_index(values, :columns, column_length)
13
+ end
14
+
15
+ def column_names
16
+ columns.to_a
17
+ end
18
+
19
+ attr_reader :index
20
+
21
+ def index=(values)
22
+ @index = check_and_convert_index(values, :index, length)
23
+ end
24
+
25
+ def ==(other)
26
+ case other
27
+ when BaseAdapter
28
+ return false if columns != other.columns
29
+ return false if index != other.index
30
+ compare_data_equality(other)
31
+ else
32
+ false
33
+ end
34
+ end
35
+
36
+ def group_by(table, grouper, sort, drop_na)
37
+ Table::HashGroupBy.new(table, grouper, sort, drop_na)
38
+ end
39
+
40
+ def compare_data_equality(other)
41
+ columns.each do |name|
42
+ if self[nil, name] != other[nil, name]
43
+ return false
44
+ end
45
+ end
46
+ true
47
+ end
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
+
142
+ private def check_and_convert_index(values, name, expected_length)
143
+ case values
144
+ when Index, Range
145
+ else
146
+ unless (ary = Array.try_convert(values))
147
+ raise ArgumentError, "invalid object for %s: %p" % [name, values]
148
+ end
149
+ values = ary
150
+ end
151
+ if expected_length != values.size
152
+ raise ArgumentError,
153
+ "invalid length for %s (%d for %d)" % [name, values.size, expected_length]
154
+ end
155
+ case values
156
+ when Index
157
+ values
158
+ when Range
159
+ RangeIndex.new(values)
160
+ when Array
161
+ Index.new(values)
162
+ end
163
+ end
164
+ end
165
+ end
166
+ end
@@ -1,29 +1,65 @@
1
+ require "delegate"
2
+
1
3
  module Charty
2
4
  module TableAdapters
3
- class DaruAdapter
5
+ class DaruAdapter < BaseAdapter
4
6
  TableAdapters.register(:daru, self)
5
7
 
8
+ extend Forwardable
6
9
  include Enumerable
7
10
 
8
11
  def self.supported?(data)
9
12
  defined?(Daru::DataFrame) && data.is_a?(Daru::DataFrame)
10
13
  end
11
14
 
12
- def initialize(data)
15
+ def initialize(data, columns: nil, index: nil)
13
16
  @data = check_type(Daru::DataFrame, data, :data)
17
+
18
+ self.columns = columns unless columns.nil?
19
+ self.index = index unless index.nil?
14
20
  end
15
21
 
16
22
  attr_reader :data
17
23
 
24
+ def_delegator :data, :size, :length
25
+
26
+ def index
27
+ DaruIndex.new(data.index)
28
+ end
29
+
30
+ def_delegator :data, :index=
31
+
32
+ def columns
33
+ DaruIndex.new(data.vectors)
34
+ end
35
+
36
+ def columns=(values)
37
+ data.vectors = Daru::Index.coerce(values)
38
+ end
39
+
18
40
  def column_names
19
41
  @data.vectors.to_a
20
42
  end
21
43
 
44
+ def compare_data_equality(other)
45
+ case other
46
+ when DaruAdapter
47
+ data == other.data
48
+ else
49
+ super
50
+ end
51
+ end
52
+
22
53
  def [](row, column)
23
54
  if row
24
55
  @data[column][row]
25
56
  else
26
- @data[column]
57
+ column_data = if @data.has_vector?(column)
58
+ @data[column]
59
+ else
60
+ @data[column.to_s]
61
+ end
62
+ Vector.new(column_data)
27
63
  end
28
64
  end
29
65
 
@@ -1,6 +1,6 @@
1
1
  module Charty
2
2
  module TableAdapters
3
- class DatasetsAdapter
3
+ class DatasetsAdapter < BaseAdapter
4
4
  TableAdapters.register(:datasets, self)
5
5
 
6
6
  include Enumerable
@@ -13,16 +13,27 @@ 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
25
32
 
33
+ def length
34
+ data.n_rows
35
+ end
36
+
26
37
  def each(&block)
27
38
  return to_enum(__method__) unless block_given?
28
39
 
@@ -37,7 +48,7 @@ module Charty
37
48
  return nil if record.nil?
38
49
  record[column]
39
50
  else
40
- @table[column]
51
+ Vector.new(@table[column], index: index, name: column)
41
52
  end
42
53
  end
43
54
  end
@@ -1,14 +1,10 @@
1
1
  require 'date'
2
- require 'forwardable'
3
2
 
4
3
  module Charty
5
4
  module TableAdapters
6
- class HashAdapter
5
+ class HashAdapter < BaseAdapter
7
6
  TableAdapters.register(:hash, self)
8
7
 
9
- extend Forwardable
10
- include Enumerable
11
-
12
8
  def self.supported?(data)
13
9
  case data
14
10
  when []
@@ -29,47 +25,171 @@ module Charty
29
25
 
30
26
  def self.array?(data)
31
27
  case data
32
- when Array,
33
- ->(x) { defined?(Numo::NArray) && x.is_a?(Numo::NArray) },
34
- ->(x) { defined?(Daru::Vector) && x.is_a?(Daru::Vector) },
35
- ->(x) { defined?(NMatrix) && x.is_a?(NMatrix) }
28
+ when Charty::Vector
29
+ true
30
+ # TODO: Use vector adapter to detect them:
31
+ when Array, method(:daru_vector?), method(:narray_vector?), method(:nmatrix_vector?),
32
+ method(:numpy_vector?), method(:pandas_series?)
36
33
  true
37
34
  else
38
35
  false
39
36
  end
40
37
  end
41
38
 
42
- def initialize(data, columns: nil)
39
+ def self.daru_vector?(x)
40
+ defined?(Daru::Vector) && x.is_a?(Daru::Vector)
41
+ end
42
+
43
+ def self.narray_vector?(x)
44
+ defined?(Numo::NArray) && x.is_a?(Numo::NArray) && x.ndim == 1
45
+ end
46
+
47
+ def self.nmatrix_vector?(x)
48
+ defined?(NMatrix) && x.is_a?(NMatrix) && x.dim == 1
49
+ end
50
+
51
+ def self.numpy_vector?(x)
52
+ defined?(Numpy::NDArray) && x.is_a?(Numpy::NDArray) && x.ndim == 1
53
+ end
54
+
55
+ def self.pandas_series?(x)
56
+ defined?(Pandas::Series) && x.is_a?(Pandas::Series)
57
+ end
58
+
59
+ def initialize(data, columns: nil, index: nil)
43
60
  case data
44
61
  when Hash
45
- @data = data
62
+ arrays = data.values
63
+ columns ||= data.keys
46
64
  when Array
47
65
  case data[0]
48
66
  when Numeric, String, Time, Date
49
- data = data.map {|x| [x] }
50
- @data = make_data_from_records(data, columns)
67
+ arrays = [data]
51
68
  when Hash
52
- # TODO
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
53
76
  when self.class.method(:array?)
54
77
  unsupported_data_format unless data.all?(&self.class.method(:array?))
55
- @data = make_data_from_records(data, columns)
78
+ arrays = data.map(&:to_a).transpose
56
79
  else
57
80
  unsupported_data_format
58
81
  end
59
82
  else
60
83
  unsupported_data_format
61
84
  end
85
+
86
+ unless arrays.empty?
87
+ arrays, columns, index = check_data(arrays, columns, index)
88
+ end
89
+
90
+ @data = arrays.map.with_index {|a, i| [columns[i], a] }.to_h
91
+ self.columns = columns unless columns.nil?
92
+ self.index = index unless index.nil?
93
+ end
94
+
95
+ private def check_data(arrays, columns, index)
96
+ # NOTE: After Ruby 2.7, we can write the following code by filter_map:
97
+ # indexes = Util.filter_map(arrays) {|ary| ary.index if ary.is_a?(Charty::Vector) }
98
+ indexes = []
99
+ arrays.each do |array|
100
+ index = case array
101
+ when Charty::Vector
102
+ array.index
103
+ when ->(x) { defined?(Daru) && x.is_a?(Daru::Vector) }
104
+ Charty::DaruIndex.new(array.index)
105
+ when ->(x) { defined?(Pandas) && x.is_a?(Pandas::Series) }
106
+ Charty::PandasIndex.new(array.index)
107
+ else
108
+ if index.nil?
109
+ RangeIndex.new(0 ... array.size)
110
+ else
111
+ check_and_convert_index(index, :index, array.size)
112
+ end
113
+ end
114
+ indexes << index
115
+ end
116
+ index = union_indexes(*indexes)
117
+
118
+ arrays = arrays.map do |array|
119
+ case array
120
+ when Charty::Vector
121
+ array.data
122
+ when Hash
123
+ raise NotImplementedError
124
+ when self.class.method(:array?)
125
+ array
126
+ else
127
+ Array.try_convert(array)
128
+ end
129
+ end
130
+
131
+ columns = generate_column_names(arrays.length, columns)
132
+
133
+ return arrays, columns, index
134
+ end
135
+
136
+ private def union_indexes(*indexes)
137
+ result = nil
138
+ while result.nil? && indexes.length > 0
139
+ result = indexes.shift
140
+ end
141
+ indexes.each do |index|
142
+ next if index.nil?
143
+ result = result.union(index)
144
+ end
145
+ result
62
146
  end
63
147
 
64
148
  attr_reader :data
65
149
 
66
150
  def_delegator :@data, :keys, :column_names
67
151
 
152
+ def length
153
+ case
154
+ when column_names.empty?
155
+ 0
156
+ else
157
+ data[column_names[0]].size
158
+ end
159
+ end
160
+
161
+ def column_length
162
+ data.length
163
+ end
164
+
165
+ def compare_data_equality(other)
166
+ case other
167
+ when DaruAdapter, PandasDataFrameAdapter
168
+ other.compare_data_equality(self)
169
+ else
170
+ super
171
+ end
172
+ end
173
+
68
174
  def [](row, column)
69
175
  if row
70
176
  @data[column][row]
71
177
  else
72
- @data[column]
178
+ case column
179
+ when Symbol
180
+ sym_key = column
181
+ str_key = column.to_s
182
+ else
183
+ str_key = String.try_convert(column)
184
+ sym_key = str_key.to_sym
185
+ end
186
+
187
+ column_data = if @data.key?(sym_key)
188
+ @data[sym_key]
189
+ else
190
+ @data[str_key]
191
+ end
192
+ Vector.new(column_data, index: index, name: column)
73
193
  end
74
194
  end
75
195
 
@@ -82,6 +202,11 @@ module Charty
82
202
  end
83
203
  end
84
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
+
85
210
  private def make_data_from_records(data, columns)
86
211
  n_rows = data.length
87
212
  n_columns = data.map(&:size).max