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
@@ -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
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
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,163 @@
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 length
25
+ data.shape[0]
26
+ end
27
+
28
+ def columns
29
+ PandasIndex.new(data.columns)
30
+ end
31
+
32
+ def columns=(new_columns)
33
+ case new_columns
34
+ when PandasIndex
35
+ data.columns = new_columns.values
36
+ data.columns.name = new_columns.name
37
+ when Index
38
+ data.columns = new_columns.to_a
39
+ data.columns.name = new_columns.name
40
+ else
41
+ data.columns = new_columns
42
+ end
43
+ end
44
+
45
+ def index
46
+ PandasIndex.new(data.index)
47
+ end
48
+
49
+ def index=(new_index)
50
+ case new_index
51
+ when PandasIndex
52
+ data.index = new_index.values
53
+ data.index.name = new_index.name
54
+ when Index
55
+ data.index = new_index.to_a
56
+ data.index.name = new_index.name
57
+ else
58
+ data.index = new_index
59
+ end
60
+ end
61
+
62
+ def column_names
63
+ @data.columns.to_a
64
+ end
65
+
66
+ def compare_data_equality(other)
67
+ case other
68
+ when PandasDataFrameAdapter
69
+ data.equals(other.data)
70
+ else
71
+ super
72
+ end
73
+ end
74
+
75
+ def [](row, column)
76
+ if row
77
+ @data[column][row]
78
+ else
79
+ Vector.new(@data[column])
80
+ end
81
+ end
82
+
83
+ def drop_na
84
+ Charty::Table.new(@data.dropna)
85
+ end
86
+
87
+ def sort_values(by, na_position: :last)
88
+ Charty::Table.new(@data.sort_values(by, na_position: na_position, kind: :mergesort))
89
+ end
90
+
91
+ private def check_type(type, data, name)
92
+ return data if data.is_a?(type)
93
+ raise TypeError, "#{name} must be a #{type}"
94
+ end
95
+
96
+ def group_by(_table, grouper, sort, drop_na)
97
+ GroupBy.new(@data.groupby(by: grouper, sort: sort, dropna: drop_na))
98
+ end
99
+
100
+ def reset_index
101
+ Charty::Table.new(data.reset_index)
102
+ end
103
+
104
+ class GroupBy < Charty::Table::GroupByBase
105
+ def initialize(groupby)
106
+ @groupby = groupby
107
+ end
108
+
109
+ def indices
110
+ @groupby.indices.map { |k, v|
111
+ [k, v.to_a]
112
+ }.to_h
113
+ end
114
+
115
+ def group_keys
116
+ each_group_key.to_a
117
+ end
118
+
119
+ def each_group_key
120
+ return enum_for(__method__) unless block_given?
121
+
122
+ if PyCall.respond_to?(:iterable)
123
+ PyCall.iterable(@groupby).each do |key, index|
124
+ if key.class == PyCall.builtins.tuple
125
+ key = key.to_a
126
+ end
127
+ yield key
128
+ end
129
+ else # TODO: Remove this clause after the new PyCall will be released
130
+ iter = @groupby.__iter__()
131
+ while true
132
+ begin
133
+ key, sub_data = iter.__next__
134
+ if key.class == PyCall.builtins.tuple
135
+ key = key.to_a
136
+ end
137
+ yield key
138
+ rescue PyCall::PyError => error
139
+ if error.type == PyCall.builtins.StopIteration
140
+ break
141
+ else
142
+ raise error
143
+ end
144
+ end
145
+ end
146
+ end
147
+ end
148
+
149
+ def apply(*args, &block)
150
+ res = @groupby.apply(->(data) {
151
+ res = block.call(Charty::Table.new(data), *args)
152
+ Pandas::Series.new(data: res)
153
+ })
154
+ Charty::Table.new(res)
155
+ end
156
+
157
+ def [](key)
158
+ Charty::Table.new(@groupby.get_group(key))
159
+ end
160
+ end
161
+ end
162
+ end
163
+ end
@@ -0,0 +1,28 @@
1
+ module Charty
2
+ module Util
3
+ if [].respond_to?(:filter_map)
4
+ module_function def filter_map(enum, &block)
5
+ enum.filter_map(&block)
6
+ end
7
+ else
8
+ module_function def filter_map(enum, &block)
9
+ enum.inject([]) do |acc, x|
10
+ y = block.call(x)
11
+ if y
12
+ acc.push(y)
13
+ else
14
+ acc
15
+ end
16
+ end
17
+ end
18
+ end
19
+
20
+ module_function def missing?(val)
21
+ val.nil? || nan?(val)
22
+ end
23
+
24
+ module_function def nan?(val)
25
+ val.respond_to?(:nan?) && val.nan?
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,69 @@
1
+ require "forwardable"
2
+
3
+ module Charty
4
+ class Vector
5
+ extend Forwardable
6
+ include Enumerable
7
+
8
+ def self.try_convert(obj)
9
+ case obj
10
+ when self
11
+ obj
12
+ else
13
+ if VectorAdapters.find_adapter_class(obj, exception: false)
14
+ new(obj)
15
+ end
16
+ end
17
+ end
18
+
19
+ def initialize(data, index: nil, name: nil)
20
+ adapter_class = VectorAdapters.find_adapter_class(data)
21
+ @adapter = adapter_class.new(data)
22
+ self.index = index unless index.nil?
23
+ self.name = name unless name.nil?
24
+ end
25
+
26
+ attr_reader :adapter
27
+
28
+ def_delegators :adapter, :data
29
+ def_delegators :adapter, :index, :index=
30
+ def_delegators :adapter, :==, :[], :[]=
31
+
32
+ def_delegators :adapter, :length
33
+ def_delegators :adapter, :name, :name=
34
+
35
+ alias size length
36
+
37
+ def_delegators :adapter, :to_a
38
+ def_delegators :adapter, :each
39
+ def_delegators :adapter, :empty?
40
+
41
+ def_delegators :adapter, :boolean?, :numeric?, :categorical?
42
+ def_delegators :adapter, :categories
43
+ def_delegators :adapter, :unique_values
44
+ def_delegators :adapter, :group_by
45
+ def_delegators :adapter, :drop_na
46
+ def_delegators :adapter, :values_at
47
+
48
+ def_delegators :adapter, :eq, :notnull
49
+
50
+ alias completecases notnull
51
+
52
+ def_delegators :adapter, :mean, :stdev
53
+
54
+ # TODO: write test
55
+ def categorical_order(order=nil)
56
+ if order.nil?
57
+ case
58
+ when categorical?
59
+ order = categories
60
+ else
61
+ order = unique_values.compact
62
+ order.sort! if numeric?
63
+ end
64
+ order.compact!
65
+ end
66
+ order
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,187 @@
1
+ require "forwardable"
2
+
3
+ module Charty
4
+ module VectorAdapters
5
+ class UnsupportedVectorData < StandardError; end
6
+
7
+ @adapters = {}
8
+
9
+ def self.register(name, adapter_class)
10
+ @adapters[name] = adapter_class
11
+ end
12
+
13
+ def self.find_adapter_class(data, exception: true)
14
+ @adapters.each_value do |adapter_class|
15
+ return adapter_class if adapter_class.supported?(data)
16
+ end
17
+ if exception
18
+ raise UnsupportedVectorData, "Unsupported vector data (#{data.class})"
19
+ end
20
+ end
21
+
22
+ class BaseAdapter
23
+ extend Forwardable
24
+ include Enumerable
25
+
26
+ def self.adapter_name
27
+ name[/:?(\w+)Adapter\z/, 1]
28
+ end
29
+
30
+ private def check_data(data)
31
+ return data if self.class.supported?(data)
32
+ raise UnsupportedVectorData, "Unsupported vector data (#{data.class})"
33
+ end
34
+
35
+ attr_reader :data
36
+
37
+ def_delegators :data, :length, :size
38
+
39
+ def ==(other)
40
+ case other.adapter
41
+ when BaseAdapter
42
+ return false if other.index != index
43
+ if respond_to?(:compare_data_equality)
44
+ compare_data_equality(other.adapter)
45
+ elsif other.adapter.respond_to?(:compare_data_equality)
46
+ other.adapter.compare_data_equality(self)
47
+ else
48
+ case other.adapter
49
+ when self.class
50
+ data == other.data
51
+ else
52
+ false
53
+ end
54
+ end
55
+ else
56
+ super
57
+ end
58
+ end
59
+
60
+ def_delegators :data, :[], :[]=
61
+ def_delegators :data, :each, :to_a, :empty?
62
+
63
+ # Take values at the given positional indices (without indexing)
64
+ def values_at(*indices)
65
+ indices.map {|i| data[i] }
66
+ end
67
+
68
+ def where_in_array(mask)
69
+ mask = check_mask_vector(mask)
70
+ masked_data = []
71
+ masked_index = []
72
+ mask.each_with_index do |f, i|
73
+ case f
74
+ when true, 1
75
+ masked_data << data[i]
76
+ masked_index << index[i]
77
+ end
78
+ end
79
+ return masked_data, masked_index
80
+ end
81
+
82
+ private def check_mask_vector(mask)
83
+ # ensure mask is boolean vector
84
+ case mask
85
+ when Charty::Vector
86
+ unless mask.boolean?
87
+ raise ArgumentError, "Unable to lookup items by a nonboolean vector"
88
+ end
89
+ mask
90
+ else
91
+ Charty::Vector.new(mask)
92
+ end
93
+ end
94
+
95
+ def mean
96
+ Statistics.mean(data)
97
+ end
98
+
99
+ def stdev(population: false)
100
+ Statistics.stdev(data, population: population)
101
+ end
102
+ end
103
+
104
+ module NameSupport
105
+ attr_reader :name
106
+
107
+ def name=(value)
108
+ @name = check_name(value)
109
+ end
110
+
111
+ private def check_name(value)
112
+ value = String.try_convert(value) || value
113
+ case value
114
+ when String, Symbol
115
+ value
116
+ else
117
+ raise ArgumentError,
118
+ "name must be a String or a Symbol (#{value.class} is given)"
119
+ end
120
+ end
121
+ end
122
+
123
+ module IndexSupport
124
+ attr_reader :index
125
+
126
+ def [](key)
127
+ case key
128
+ when Charty::Vector
129
+ where(key)
130
+ else
131
+ super(key_to_loc(key))
132
+ end
133
+ end
134
+
135
+ def []=(key, val)
136
+ super(key_to_loc(key), val)
137
+ end
138
+
139
+ private def key_to_loc(key)
140
+ loc = self.index.loc(key)
141
+ if loc.nil?
142
+ if key.respond_to?(:to_int)
143
+ loc = key.to_int
144
+ else
145
+ raise KeyError.new("key not found: %p" % key,
146
+ receiver: __method__, key: key)
147
+ end
148
+ end
149
+ loc
150
+ end
151
+
152
+ def index=(values)
153
+ @index = check_and_convert_index(values, :index, length)
154
+ end
155
+
156
+ private def check_and_convert_index(values, name, expected_length)
157
+ case values
158
+ when Index, Range
159
+ else
160
+ unless (ary = Array.try_convert(values))
161
+ raise ArgumentError, "invalid object for %s: %p" % [name, values]
162
+ end
163
+ values = ary
164
+ end
165
+ if expected_length != values.size
166
+ raise ArgumentError,
167
+ "invalid length for %s (%d for %d)" % [name, values.size, expected_length]
168
+ end
169
+ case values
170
+ when Index
171
+ values
172
+ when Range
173
+ RangeIndex.new(values)
174
+ when Array
175
+ Index.new(values)
176
+ end
177
+ end
178
+ end
179
+ end
180
+ end
181
+
182
+ require_relative "vector_adapters/array_adapter"
183
+ require_relative "vector_adapters/daru_adapter"
184
+ require_relative "vector_adapters/narray_adapter"
185
+ require_relative "vector_adapters/nmatrix_adapter"
186
+ require_relative "vector_adapters/numpy_adapter"
187
+ require_relative "vector_adapters/pandas_adapter"