charty 0.2.3 → 0.2.4

Sign up to get free protection for your applications and to get access to all the features.
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