charty 0.2.3 → 0.2.4

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 (58) 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 +123 -4
  7. data/Rakefile +4 -5
  8. data/charty.gemspec +1 -3
  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 +4 -0
  20. data/lib/charty/backends/gruff.rb +13 -2
  21. data/lib/charty/backends/plotly.rb +322 -20
  22. data/lib/charty/backends/pyplot.rb +416 -64
  23. data/lib/charty/index.rb +213 -0
  24. data/lib/charty/linspace.rb +1 -1
  25. data/lib/charty/missing_value_support.rb +14 -0
  26. data/lib/charty/plot_methods.rb +173 -8
  27. data/lib/charty/plotters.rb +7 -0
  28. data/lib/charty/plotters/abstract_plotter.rb +87 -12
  29. data/lib/charty/plotters/bar_plotter.rb +200 -3
  30. data/lib/charty/plotters/box_plotter.rb +75 -7
  31. data/lib/charty/plotters/categorical_plotter.rb +272 -40
  32. data/lib/charty/plotters/count_plotter.rb +7 -0
  33. data/lib/charty/plotters/estimation_support.rb +84 -0
  34. data/lib/charty/plotters/random_support.rb +25 -0
  35. data/lib/charty/plotters/relational_plotter.rb +518 -0
  36. data/lib/charty/plotters/scatter_plotter.rb +115 -0
  37. data/lib/charty/plotters/vector_plotter.rb +6 -0
  38. data/lib/charty/statistics.rb +87 -2
  39. data/lib/charty/table.rb +50 -15
  40. data/lib/charty/table_adapters.rb +2 -0
  41. data/lib/charty/table_adapters/active_record_adapter.rb +17 -9
  42. data/lib/charty/table_adapters/base_adapter.rb +69 -0
  43. data/lib/charty/table_adapters/daru_adapter.rb +37 -3
  44. data/lib/charty/table_adapters/datasets_adapter.rb +6 -2
  45. data/lib/charty/table_adapters/hash_adapter.rb +130 -16
  46. data/lib/charty/table_adapters/narray_adapter.rb +25 -6
  47. data/lib/charty/table_adapters/nmatrix_adapter.rb +15 -5
  48. data/lib/charty/table_adapters/pandas_adapter.rb +81 -0
  49. data/lib/charty/vector.rb +69 -0
  50. data/lib/charty/vector_adapters.rb +183 -0
  51. data/lib/charty/vector_adapters/array_adapter.rb +109 -0
  52. data/lib/charty/vector_adapters/daru_adapter.rb +171 -0
  53. data/lib/charty/vector_adapters/narray_adapter.rb +187 -0
  54. data/lib/charty/vector_adapters/nmatrix_adapter.rb +37 -0
  55. data/lib/charty/vector_adapters/numpy_adapter.rb +168 -0
  56. data/lib/charty/vector_adapters/pandas_adapter.rb +200 -0
  57. data/lib/charty/version.rb +1 -1
  58. metadata +33 -45
@@ -1,29 +1,63 @@
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 index
25
+ DaruIndex.new(data.index)
26
+ end
27
+
28
+ def_delegator :data, :index=
29
+
30
+ def columns
31
+ DaruIndex.new(data.vectors)
32
+ end
33
+
34
+ def columns=(values)
35
+ data.vectors = Daru::Index.coerce(values)
36
+ end
37
+
18
38
  def column_names
19
39
  @data.vectors.to_a
20
40
  end
21
41
 
42
+ def compare_data_equality(other)
43
+ case other
44
+ when DaruAdapter
45
+ data == other.data
46
+ else
47
+ super
48
+ end
49
+ end
50
+
22
51
  def [](row, column)
23
52
  if row
24
53
  @data[column][row]
25
54
  else
26
- @data[column]
55
+ column_data = if @data.has_vector?(column)
56
+ @data[column]
57
+ else
58
+ @data[column.to_s]
59
+ end
60
+ Vector.new(column_data)
27
61
  end
28
62
  end
29
63
 
@@ -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
@@ -23,6 +23,10 @@ module Charty
23
23
  @table.column_names
24
24
  end
25
25
 
26
+ def length
27
+ data.n_rows
28
+ end
29
+
26
30
  def each(&block)
27
31
  return to_enum(__method__) unless block_given?
28
32
 
@@ -37,7 +41,7 @@ module Charty
37
41
  return nil if record.nil?
38
42
  record[column]
39
43
  else
40
- @table[column]
44
+ Vector.new(@table[column], index: index, name: column)
41
45
  end
42
46
  end
43
47
  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,165 @@ 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
+ when Array, method(:daru_vector?), method(:narray_vector?), method(:nmatrix_vector?),
31
+ method(:numpy_vector?), method(:pandas_series?)
36
32
  true
37
33
  else
38
34
  false
39
35
  end
40
36
  end
41
37
 
42
- def initialize(data, columns: nil)
38
+ def self.daru_vector?(x)
39
+ defined?(Daru::Vector) && x.is_a?(Daru::Vector)
40
+ end
41
+
42
+ def self.narray_vector?(x)
43
+ defined?(Numo::NArray) && x.is_a?(Numo::NArray) && x.ndim == 1
44
+ end
45
+
46
+ def self.nmatrix_vector?(x)
47
+ defined?(NMatrix) && x.is_a?(NMatrix) && x.dim == 1
48
+ end
49
+
50
+ def self.numpy_vector?(x)
51
+ defined?(Numpy::NDArray) && x.is_a?(Numpy::NDArray) && x.ndim == 1
52
+ end
53
+
54
+ def self.pandas_series?(x)
55
+ defined?(Pandas::Series) && x.is_a?(Pandas::Series)
56
+ end
57
+
58
+ def initialize(data, columns: nil, index: nil)
43
59
  case data
44
60
  when Hash
45
- @data = data
61
+ arrays = data.values
62
+ columns ||= data.keys
46
63
  when Array
47
64
  case data[0]
48
65
  when Numeric, String, Time, Date
49
- data = data.map {|x| [x] }
50
- @data = make_data_from_records(data, columns)
66
+ arrays = [data]
51
67
  when Hash
52
- # TODO
68
+ raise NotImplementedError,
69
+ "an array of records is not supported"
53
70
  when self.class.method(:array?)
54
71
  unsupported_data_format unless data.all?(&self.class.method(:array?))
55
- @data = make_data_from_records(data, columns)
72
+ arrays = data.map(&:to_a).transpose
56
73
  else
57
74
  unsupported_data_format
58
75
  end
59
76
  else
60
77
  unsupported_data_format
61
78
  end
79
+
80
+ unless arrays.empty?
81
+ arrays, columns, index = check_data(arrays, columns, index)
82
+ end
83
+
84
+ @data = arrays.map.with_index {|a, i| [columns[i], a] }.to_h
85
+ self.columns = columns unless columns.nil?
86
+ self.index = index unless index.nil?
87
+ end
88
+
89
+ private def check_data(arrays, columns, index)
90
+ # 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) }
92
+ indexes = []
93
+ arrays.each do |array|
94
+ index = case array
95
+ when Charty::Vector
96
+ array.index
97
+ when ->(x) { defined?(Daru) && x.is_a?(Daru::Vector) }
98
+ Charty::DaruIndex.new(array.index)
99
+ when ->(x) { defined?(Pandas) && x.is_a?(Pandas::Series) }
100
+ Charty::PandasIndex.new(array.index)
101
+ else
102
+ if index.nil?
103
+ RangeIndex.new(0 ... array.size)
104
+ else
105
+ check_and_convert_index(index, :index, array.size)
106
+ end
107
+ end
108
+ indexes << index
109
+ end
110
+ index = union_indexes(*indexes)
111
+
112
+ arrays = arrays.map do |array|
113
+ case array
114
+ when Charty::Vector
115
+ array.data
116
+ when Hash
117
+ raise NotImplementedError
118
+ when self.class.method(:array?)
119
+ array
120
+ else
121
+ Array.try_convert(array)
122
+ end
123
+ end
124
+
125
+ columns = generate_column_names(arrays.length, columns)
126
+
127
+ return arrays, columns, index
128
+ end
129
+
130
+ private def union_indexes(*indexes)
131
+ result = nil
132
+ while result.nil? && indexes.length > 0
133
+ result = indexes.shift
134
+ end
135
+ indexes.each do |index|
136
+ next if index.nil?
137
+ result = result.union(index)
138
+ end
139
+ result
62
140
  end
63
141
 
64
142
  attr_reader :data
65
143
 
66
144
  def_delegator :@data, :keys, :column_names
67
145
 
146
+ def length
147
+ case
148
+ when column_names.empty?
149
+ 0
150
+ else
151
+ data[column_names[0]].size
152
+ end
153
+ end
154
+
155
+ def column_length
156
+ data.length
157
+ end
158
+
159
+ def compare_data_equality(other)
160
+ case other
161
+ when DaruAdapter, PandasDataFrameAdapter
162
+ other.compare_data_equality(self)
163
+ else
164
+ super
165
+ end
166
+ end
167
+
68
168
  def [](row, column)
69
169
  if row
70
170
  @data[column][row]
71
171
  else
72
- @data[column]
172
+ case column
173
+ when Symbol
174
+ sym_key = column
175
+ str_key = column.to_s
176
+ else
177
+ str_key = String.try_convert(column)
178
+ sym_key = str_key.to_sym
179
+ end
180
+
181
+ column_data = if @data.key?(sym_key)
182
+ @data[sym_key]
183
+ else
184
+ @data[str_key]
185
+ end
186
+ Vector.new(column_data, index: index, name: column)
73
187
  end
74
188
  end
75
189
 
@@ -1,13 +1,13 @@
1
1
  module Charty
2
2
  module TableAdapters
3
- class NArrayAdapter
3
+ class NArrayAdapter < BaseAdapter
4
4
  TableAdapters.register(:narray, self)
5
5
 
6
6
  def self.supported?(data)
7
7
  defined?(Numo::NArray) && data.is_a?(Numo::NArray) && data.ndim <= 2
8
8
  end
9
9
 
10
- def initialize(data, columns: nil)
10
+ def initialize(data, columns: nil, index: nil)
11
11
  case data.ndim
12
12
  when 1
13
13
  data = data.reshape(data.length, 1)
@@ -17,23 +17,42 @@ module Charty
17
17
  raise ArgumentError, "Unsupported data format"
18
18
  end
19
19
  @data = data
20
- @column_names = generate_column_names(data.shape[1], columns)
20
+ self.columns = Index.new(generate_column_names(data.shape[1], columns))
21
+ self.index = index || RangeIndex.new(0 ... length)
21
22
  end
22
23
 
23
- attr_reader :column_names, :data
24
+ attr_reader :data
25
+
26
+ def length
27
+ data.shape[0]
28
+ end
29
+
30
+ def column_length
31
+ data.shape[1]
32
+ end
33
+
34
+ def compare_data_equality(other)
35
+ case other
36
+ when NArrayAdapter
37
+ data == other.data
38
+ else
39
+ super
40
+ end
41
+ end
24
42
 
25
43
  def [](row, column)
26
44
  if row
27
45
  @data[row, resolve_column_index(column)]
28
46
  else
29
- @data[true, resolve_column_index(column)]
47
+ column_data = @data[true, resolve_column_index(column)]
48
+ Charty::Vector.new(column_data, index: index, name: column)
30
49
  end
31
50
  end
32
51
 
33
52
  private def resolve_column_index(column)
34
53
  case column
35
54
  when String, Symbol
36
- index = column_names.index(column.to_s)
55
+ index = column_names.index(column.to_sym) || column_names.index(column.to_s)
37
56
  return index if index
38
57
  raise IndexError, "invalid column name: #{column}"
39
58
  when Integer
@@ -1,6 +1,6 @@
1
1
  module Charty
2
2
  module TableAdapters
3
- class NMatrixAdapter
3
+ class NMatrixAdapter < BaseAdapter
4
4
  TableAdapters.register(:nmatrix, self)
5
5
 
6
6
  def self.supported?(data)
@@ -17,23 +17,33 @@ module Charty
17
17
  raise ArgumentError, "Unsupported data format"
18
18
  end
19
19
  @data = data
20
- @column_names = generate_column_names(data.shape[1], columns)
20
+ self.columns = Index.new(generate_column_names(data.shape[1], columns))
21
+ self.index = index || RangeIndex.new(0 ... length)
21
22
  end
22
23
 
23
- attr_reader :column_names, :data
24
+ attr_reader :data
25
+
26
+ def length
27
+ data.shape[0]
28
+ end
29
+
30
+ def column_length
31
+ data.shape[1]
32
+ end
24
33
 
25
34
  def [](row, column)
26
35
  if row
27
36
  @data[row, resolve_column_index(column)]
28
37
  else
29
- @data[:*, resolve_column_index(column)].reshape([@data.shape[0]])
38
+ column_data = @data[:*, resolve_column_index(column)].reshape([@data.shape[0]])
39
+ Charty::Vector.new(column_data, index: index, name: column)
30
40
  end
31
41
  end
32
42
 
33
43
  private def resolve_column_index(column)
34
44
  case column
35
45
  when String, Symbol
36
- index = column_names.index(column.to_s)
46
+ index = column_names.index(column.to_sym) || column_names.index(column.to_s)
37
47
  return index if index
38
48
  raise IndexError, "invalid column name: #{column}"
39
49
  when Integer
@@ -0,0 +1,81 @@
1
+ require "forwardable"
2
+
3
+ module Charty
4
+ module TableAdapters
5
+ class PandasDataFrameAdapter < BaseAdapter
6
+ TableAdapters.register(:pandas_data_frame, self)
7
+
8
+ extend Forwardable
9
+ include Enumerable
10
+
11
+ def self.supported?(data)
12
+ defined?(Pandas::DataFrame) && data.is_a?(Pandas::DataFrame)
13
+ end
14
+
15
+ def initialize(data, columns: nil, index: nil)
16
+ @data = check_type(Pandas::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 columns
25
+ PandasIndex.new(data.columns)
26
+ end
27
+
28
+ def columns=(new_columns)
29
+ case new_columns
30
+ when PandasIndex
31
+ data.columns = new_columns.values
32
+ when Index
33
+ data.columns = new_columns.to_a
34
+ else
35
+ data.columns = new_columns
36
+ end
37
+ end
38
+
39
+ def index
40
+ PandasIndex.new(data.index)
41
+ end
42
+
43
+ def index=(new_index)
44
+ case new_index
45
+ when PandasIndex
46
+ data.index = new_index.values
47
+ when Index
48
+ data.index = new_index.to_a
49
+ else
50
+ data.index = new_index
51
+ end
52
+ end
53
+
54
+ def column_names
55
+ @data.columns.to_a
56
+ end
57
+
58
+ def compare_data_equality(other)
59
+ case other
60
+ when PandasDataFrameAdapter
61
+ data.equals(other.data)
62
+ else
63
+ super
64
+ end
65
+ end
66
+
67
+ def [](row, column)
68
+ if row
69
+ @data[column][row]
70
+ else
71
+ Vector.new(@data[column])
72
+ end
73
+ end
74
+
75
+ private def check_type(type, data, name)
76
+ return data if data.is_a?(type)
77
+ raise TypeError, "#{name} must be a #{type}"
78
+ end
79
+ end
80
+ end
81
+ end