charty 0.2.0 → 0.2.6

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 (78) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +71 -0
  3. data/.github/workflows/nmatrix.yml +67 -0
  4. data/.github/workflows/pycall.yml +86 -0
  5. data/Dockerfile.dev +9 -1
  6. data/Gemfile +18 -0
  7. data/README.md +177 -9
  8. data/Rakefile +4 -5
  9. data/charty.gemspec +10 -4
  10. data/examples/Gemfile +1 -0
  11. data/examples/active_record.ipynb +1 -1
  12. data/examples/daru.ipynb +1 -1
  13. data/examples/iris_dataset.ipynb +1 -1
  14. data/examples/nmatrix.ipynb +1 -1
  15. data/examples/{numo-narray.ipynb → numo_narray.ipynb} +1 -1
  16. data/examples/palette.rb +71 -0
  17. data/examples/sample.png +0 -0
  18. data/examples/sample_images/hist_gruff.png +0 -0
  19. data/examples/sample_pyplot.ipynb +40 -38
  20. data/images/penguins_body_mass_g_flipper_length_mm_scatter_plot.png +0 -0
  21. data/images/penguins_body_mass_g_flipper_length_mm_species_scatter_plot.png +0 -0
  22. data/images/penguins_body_mass_g_flipper_length_mm_species_sex_scatter_plot.png +0 -0
  23. data/images/penguins_species_body_mass_g_bar_plot_h.png +0 -0
  24. data/images/penguins_species_body_mass_g_bar_plot_v.png +0 -0
  25. data/images/penguins_species_body_mass_g_box_plot_h.png +0 -0
  26. data/images/penguins_species_body_mass_g_box_plot_v.png +0 -0
  27. data/images/penguins_species_body_mass_g_sex_bar_plot_v.png +0 -0
  28. data/images/penguins_species_body_mass_g_sex_box_plot_v.png +0 -0
  29. data/lib/charty.rb +13 -1
  30. data/lib/charty/backend_methods.rb +8 -0
  31. data/lib/charty/backends.rb +26 -1
  32. data/lib/charty/backends/bokeh.rb +31 -31
  33. data/lib/charty/backends/{google_chart.rb → google_charts.rb} +75 -33
  34. data/lib/charty/backends/gruff.rb +14 -3
  35. data/lib/charty/backends/plotly.rb +774 -9
  36. data/lib/charty/backends/pyplot.rb +611 -34
  37. data/lib/charty/backends/rubyplot.rb +2 -2
  38. data/lib/charty/backends/unicode_plot.rb +79 -0
  39. data/lib/charty/dash_pattern_generator.rb +57 -0
  40. data/lib/charty/index.rb +213 -0
  41. data/lib/charty/linspace.rb +1 -1
  42. data/lib/charty/plot_methods.rb +254 -0
  43. data/lib/charty/plotter.rb +10 -10
  44. data/lib/charty/plotters.rb +12 -0
  45. data/lib/charty/plotters/abstract_plotter.rb +243 -0
  46. data/lib/charty/plotters/bar_plotter.rb +201 -0
  47. data/lib/charty/plotters/box_plotter.rb +79 -0
  48. data/lib/charty/plotters/categorical_plotter.rb +380 -0
  49. data/lib/charty/plotters/count_plotter.rb +7 -0
  50. data/lib/charty/plotters/estimation_support.rb +84 -0
  51. data/lib/charty/plotters/line_plotter.rb +300 -0
  52. data/lib/charty/plotters/random_support.rb +25 -0
  53. data/lib/charty/plotters/relational_plotter.rb +635 -0
  54. data/lib/charty/plotters/scatter_plotter.rb +80 -0
  55. data/lib/charty/plotters/vector_plotter.rb +6 -0
  56. data/lib/charty/statistics.rb +114 -0
  57. data/lib/charty/table.rb +161 -15
  58. data/lib/charty/table_adapters.rb +2 -0
  59. data/lib/charty/table_adapters/active_record_adapter.rb +17 -9
  60. data/lib/charty/table_adapters/base_adapter.rb +166 -0
  61. data/lib/charty/table_adapters/daru_adapter.rb +41 -3
  62. data/lib/charty/table_adapters/datasets_adapter.rb +17 -2
  63. data/lib/charty/table_adapters/hash_adapter.rb +143 -16
  64. data/lib/charty/table_adapters/narray_adapter.rb +25 -6
  65. data/lib/charty/table_adapters/nmatrix_adapter.rb +15 -5
  66. data/lib/charty/table_adapters/pandas_adapter.rb +163 -0
  67. data/lib/charty/util.rb +28 -0
  68. data/lib/charty/vector.rb +69 -0
  69. data/lib/charty/vector_adapters.rb +187 -0
  70. data/lib/charty/vector_adapters/array_adapter.rb +101 -0
  71. data/lib/charty/vector_adapters/daru_adapter.rb +163 -0
  72. data/lib/charty/vector_adapters/narray_adapter.rb +182 -0
  73. data/lib/charty/vector_adapters/nmatrix_adapter.rb +37 -0
  74. data/lib/charty/vector_adapters/numpy_adapter.rb +168 -0
  75. data/lib/charty/vector_adapters/pandas_adapter.rb +199 -0
  76. data/lib/charty/version.rb +1 -1
  77. metadata +121 -22
  78. data/.travis.yml +0 -10
@@ -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,27 +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?
20
+ end
21
+
22
+ attr_reader :data
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)
14
38
  end
15
39
 
16
40
  def column_names
17
41
  @data.vectors.to_a
18
42
  end
19
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
+
20
53
  def [](row, column)
21
54
  if row
22
55
  @data[column][row]
23
56
  else
24
- @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)
25
63
  end
26
64
  end
27
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,12 +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
19
+ end
20
+
21
+ def data
22
+ @table
23
+ end
24
+
25
+ def column_length
26
+ column_names.length
16
27
  end
17
28
 
18
29
  def column_names
19
30
  @table.column_names
20
31
  end
21
32
 
33
+ def length
34
+ data.n_rows
35
+ end
36
+
22
37
  def each(&block)
23
38
  return to_enum(__method__) unless block_given?
24
39
 
@@ -33,7 +48,7 @@ module Charty
33
48
  return nil if record.nil?
34
49
  record[column]
35
50
  else
36
- @table[column]
51
+ Vector.new(@table[column], index: index, name: column)
37
52
  end
38
53
  end
39
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,45 +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
 
148
+ attr_reader :data
149
+
64
150
  def_delegator :@data, :keys, :column_names
65
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
+
66
174
  def [](row, column)
67
175
  if row
68
176
  @data[column][row]
69
177
  else
70
- @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)
71
193
  end
72
194
  end
73
195
 
@@ -80,6 +202,11 @@ module Charty
80
202
  end
81
203
  end
82
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
+
83
210
  private def make_data_from_records(data, columns)
84
211
  n_rows = data.length
85
212
  n_columns = data.map(&:size).max