charty 0.1.4.dev → 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 (91) 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 +128 -9
  8. data/Rakefile +4 -5
  9. data/charty.gemspec +7 -2
  10. data/examples/Gemfile +1 -0
  11. data/examples/active_record.ipynb +34 -34
  12. data/examples/daru.ipynb +71 -29
  13. data/examples/iris_dataset.ipynb +12 -5
  14. data/examples/nmatrix.ipynb +30 -30
  15. data/examples/numo_narray.ipynb +245 -0
  16. data/examples/palette.rb +71 -0
  17. data/examples/sample.png +0 -0
  18. data/examples/sample_bokeh.ipynb +156 -0
  19. data/examples/sample_google_chart.ipynb +229 -68
  20. data/examples/sample_gruff.ipynb +148 -133
  21. data/examples/sample_images/bar_bokeh.html +85 -0
  22. data/examples/sample_images/barh_bokeh.html +85 -0
  23. data/examples/sample_images/barh_gruff.png +0 -0
  24. data/examples/sample_images/box_plot_bokeh.html +85 -0
  25. data/examples/sample_images/{boxplot_pyplot.png → box_plot_pyplot.png} +0 -0
  26. data/examples/sample_images/curve_bokeh.html +85 -0
  27. data/examples/sample_images/curve_with_function_bokeh.html +85 -0
  28. data/examples/sample_images/{errorbar_pyplot.png → error_bar_pyplot.png} +0 -0
  29. data/examples/sample_images/hist_gruff.png +0 -0
  30. data/examples/sample_images/scatter_bokeh.html +85 -0
  31. data/examples/sample_pyplot.ipynb +37 -35
  32. data/images/penguins_body_mass_g_flipper_length_mm_scatter_plot.png +0 -0
  33. data/images/penguins_body_mass_g_flipper_length_mm_species_scatter_plot.png +0 -0
  34. data/images/penguins_body_mass_g_flipper_length_mm_species_sex_scatter_plot.png +0 -0
  35. data/images/penguins_species_body_mass_g_bar_plot_h.png +0 -0
  36. data/images/penguins_species_body_mass_g_bar_plot_v.png +0 -0
  37. data/images/penguins_species_body_mass_g_box_plot_h.png +0 -0
  38. data/images/penguins_species_body_mass_g_box_plot_v.png +0 -0
  39. data/images/penguins_species_body_mass_g_sex_bar_plot_v.png +0 -0
  40. data/images/penguins_species_body_mass_g_sex_box_plot_v.png +0 -0
  41. data/lib/charty.rb +13 -7
  42. data/lib/charty/backend_methods.rb +8 -0
  43. data/lib/charty/backends.rb +80 -0
  44. data/lib/charty/backends/bokeh.rb +80 -0
  45. data/lib/charty/backends/google_charts.rb +267 -0
  46. data/lib/charty/backends/gruff.rb +104 -67
  47. data/lib/charty/backends/plotly.rb +549 -0
  48. data/lib/charty/backends/pyplot.rb +584 -86
  49. data/lib/charty/backends/rubyplot.rb +82 -74
  50. data/lib/charty/backends/unicode_plot.rb +79 -0
  51. data/lib/charty/index.rb +213 -0
  52. data/lib/charty/linspace.rb +1 -1
  53. data/lib/charty/missing_value_support.rb +14 -0
  54. data/lib/charty/plot_methods.rb +184 -0
  55. data/lib/charty/plotter.rb +57 -41
  56. data/lib/charty/plotters.rb +11 -0
  57. data/lib/charty/plotters/abstract_plotter.rb +156 -0
  58. data/lib/charty/plotters/bar_plotter.rb +216 -0
  59. data/lib/charty/plotters/box_plotter.rb +94 -0
  60. data/lib/charty/plotters/categorical_plotter.rb +380 -0
  61. data/lib/charty/plotters/count_plotter.rb +7 -0
  62. data/lib/charty/plotters/estimation_support.rb +84 -0
  63. data/lib/charty/plotters/random_support.rb +25 -0
  64. data/lib/charty/plotters/relational_plotter.rb +518 -0
  65. data/lib/charty/plotters/scatter_plotter.rb +115 -0
  66. data/lib/charty/plotters/vector_plotter.rb +6 -0
  67. data/lib/charty/statistics.rb +114 -0
  68. data/lib/charty/table.rb +82 -3
  69. data/lib/charty/table_adapters.rb +25 -0
  70. data/lib/charty/table_adapters/active_record_adapter.rb +63 -0
  71. data/lib/charty/table_adapters/base_adapter.rb +69 -0
  72. data/lib/charty/table_adapters/daru_adapter.rb +70 -0
  73. data/lib/charty/table_adapters/datasets_adapter.rb +49 -0
  74. data/lib/charty/table_adapters/hash_adapter.rb +224 -0
  75. data/lib/charty/table_adapters/narray_adapter.rb +76 -0
  76. data/lib/charty/table_adapters/nmatrix_adapter.rb +67 -0
  77. data/lib/charty/table_adapters/pandas_adapter.rb +81 -0
  78. data/lib/charty/vector.rb +69 -0
  79. data/lib/charty/vector_adapters.rb +183 -0
  80. data/lib/charty/vector_adapters/array_adapter.rb +109 -0
  81. data/lib/charty/vector_adapters/daru_adapter.rb +171 -0
  82. data/lib/charty/vector_adapters/narray_adapter.rb +187 -0
  83. data/lib/charty/vector_adapters/nmatrix_adapter.rb +37 -0
  84. data/lib/charty/vector_adapters/numpy_adapter.rb +168 -0
  85. data/lib/charty/vector_adapters/pandas_adapter.rb +200 -0
  86. data/lib/charty/version.rb +1 -1
  87. metadata +127 -13
  88. data/.travis.yml +0 -11
  89. data/examples/numo-narray.ipynb +0 -234
  90. data/lib/charty/backends/google_chart.rb +0 -167
  91. data/lib/charty/plotter_adapter.rb +0 -17
@@ -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
@@ -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,183 @@
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
+ include MissingValueSupport
26
+
27
+ def self.adapter_name
28
+ name[/:?(\w+)Adapter\z/, 1]
29
+ end
30
+
31
+ private def check_data(data)
32
+ return data if self.class.supported?(data)
33
+ raise UnsupportedVectorData, "Unsupported vector data (#{data.class})"
34
+ end
35
+
36
+ attr_reader :data
37
+
38
+ def_delegators :data, :length, :size
39
+
40
+ def ==(other)
41
+ case other.adapter
42
+ when BaseAdapter
43
+ return false if other.index != index
44
+ if respond_to?(:compare_data_equality)
45
+ compare_data_equality(other.adapter)
46
+ elsif other.adapter.respond_to?(:compare_data_equality)
47
+ other.adapter.compare_data_equality(self)
48
+ else
49
+ case other.adapter
50
+ when self.class
51
+ data == other.data
52
+ else
53
+ false
54
+ end
55
+ end
56
+ else
57
+ super
58
+ end
59
+ end
60
+
61
+ def_delegators :data, :[], :[]=
62
+ def_delegators :data, :each, :to_a, :empty?
63
+
64
+ def where_in_array(mask)
65
+ mask = check_mask_vector(mask)
66
+ masked_data = []
67
+ masked_index = []
68
+ mask.each_with_index do |f, i|
69
+ case f
70
+ when true, 1
71
+ masked_data << data[i]
72
+ masked_index << index[i]
73
+ end
74
+ end
75
+ return masked_data, masked_index
76
+ end
77
+
78
+ private def check_mask_vector(mask)
79
+ # ensure mask is boolean vector
80
+ case mask
81
+ when Charty::Vector
82
+ unless mask.boolean?
83
+ raise ArgumentError, "Unable to lookup items by a nonboolean vector"
84
+ end
85
+ mask
86
+ else
87
+ Charty::Vector.new(mask)
88
+ end
89
+ end
90
+
91
+ def mean
92
+ Statistics.mean(data)
93
+ end
94
+
95
+ def stdev(population: false)
96
+ Statistics.stdev(data, population: population)
97
+ end
98
+ end
99
+
100
+ module NameSupport
101
+ attr_reader :name
102
+
103
+ def name=(value)
104
+ @name = check_name(value)
105
+ end
106
+
107
+ private def check_name(value)
108
+ value = String.try_convert(value) || value
109
+ case value
110
+ when String, Symbol
111
+ value
112
+ else
113
+ raise ArgumentError,
114
+ "name must be a String or a Symbol (#{value.class} is given)"
115
+ end
116
+ end
117
+ end
118
+
119
+ module IndexSupport
120
+ attr_reader :index
121
+
122
+ def [](key)
123
+ case key
124
+ when Charty::Vector
125
+ where(key)
126
+ else
127
+ super(key_to_loc(key))
128
+ end
129
+ end
130
+
131
+ def []=(key, val)
132
+ super(key_to_loc(key), val)
133
+ end
134
+
135
+ private def key_to_loc(key)
136
+ loc = self.index.loc(key)
137
+ if loc.nil?
138
+ if key.respond_to?(:to_int)
139
+ loc = key.to_int
140
+ else
141
+ raise KeyError.new("key not found: %p" % key,
142
+ receiver: __method__, key: key)
143
+ end
144
+ end
145
+ loc
146
+ end
147
+
148
+ def index=(values)
149
+ @index = check_and_convert_index(values, :index, length)
150
+ end
151
+
152
+ private def check_and_convert_index(values, name, expected_length)
153
+ case values
154
+ when Index, Range
155
+ else
156
+ unless (ary = Array.try_convert(values))
157
+ raise ArgumentError, "invalid object for %s: %p" % [name, values]
158
+ end
159
+ values = ary
160
+ end
161
+ if expected_length != values.size
162
+ raise ArgumentError,
163
+ "invalid length for %s (%d for %d)" % [name, values.size, expected_length]
164
+ end
165
+ case values
166
+ when Index
167
+ values
168
+ when Range
169
+ RangeIndex.new(values)
170
+ when Array
171
+ Index.new(values)
172
+ end
173
+ end
174
+ end
175
+ end
176
+ end
177
+
178
+ require_relative "vector_adapters/array_adapter"
179
+ require_relative "vector_adapters/daru_adapter"
180
+ require_relative "vector_adapters/narray_adapter"
181
+ require_relative "vector_adapters/nmatrix_adapter"
182
+ require_relative "vector_adapters/numpy_adapter"
183
+ require_relative "vector_adapters/pandas_adapter"
@@ -0,0 +1,109 @@
1
+ require "date"
2
+
3
+ module Charty
4
+ module VectorAdapters
5
+ class ArrayAdapter < BaseAdapter
6
+ VectorAdapters.register(:array, self)
7
+
8
+ extend Forwardable
9
+ include Enumerable
10
+
11
+ def self.supported?(data)
12
+ case data
13
+ when Array
14
+ case data[0]
15
+ when Numeric, String, Time, Date, DateTime, true, false, nil
16
+ true
17
+ else
18
+ false
19
+ end
20
+ else
21
+ false
22
+ end
23
+ end
24
+
25
+ def initialize(data, index: nil)
26
+ @data = check_data(data)
27
+ self.index = index || RangeIndex.new(0 ... length)
28
+ end
29
+
30
+ include NameSupport
31
+ include IndexSupport
32
+
33
+ # TODO: Reconsider the return value type of values_at
34
+ def_delegators :data, :values_at
35
+
36
+ def where(mask)
37
+ masked_data, masked_index = where_in_array(mask)
38
+ Charty::Vector.new(masked_data, index: masked_index, name: name)
39
+ end
40
+
41
+ def first_nonnil
42
+ data.drop_while(&:nil?).first
43
+ end
44
+
45
+ def boolean?
46
+ case first_nonnil
47
+ when true, false
48
+ true
49
+ else
50
+ false
51
+ end
52
+ end
53
+
54
+ def numeric?
55
+ case first_nonnil
56
+ when Numeric
57
+ true
58
+ else
59
+ false
60
+ end
61
+ end
62
+
63
+ def categorical?
64
+ false
65
+ end
66
+
67
+ def categories
68
+ nil
69
+ end
70
+
71
+ def_delegator :data, :uniq, :unique_values
72
+
73
+ def group_by(grouper)
74
+ groups = data.each_index.group_by {|i| grouper[i] }
75
+ groups.map { |g, vals|
76
+ vals.collect! {|i| self[i] }
77
+ [g, Charty::Vector.new(vals)]
78
+ }.to_h
79
+ end
80
+
81
+ def drop_na
82
+ if numeric?
83
+ Charty::Vector.new(data.reject { |x|
84
+ case x
85
+ when Float
86
+ x.nan?
87
+ else
88
+ x.nil?
89
+ end
90
+ })
91
+ else
92
+ Charty::Vector.new(data.compact)
93
+ end
94
+ end
95
+
96
+ def eq(val)
97
+ Charty::Vector.new(data.map {|x| x == val },
98
+ index: index,
99
+ name: name)
100
+ end
101
+
102
+ def notnull
103
+ Charty::Vector.new(data.map {|x| ! missing_value?(x) },
104
+ index: index,
105
+ name: name)
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,171 @@
1
+ module Charty
2
+ module VectorAdapters
3
+ class DaruVectorAdapter < BaseAdapter
4
+ VectorAdapters.register(:daru_vector, self)
5
+
6
+ def self.supported?(data)
7
+ defined?(Daru::Vector) && data.is_a?(Daru::Vector)
8
+ end
9
+
10
+ def initialize(data)
11
+ @data = check_data(data)
12
+ end
13
+
14
+ def_delegator :data, :size, :length
15
+
16
+ def index
17
+ DaruIndex.new(data.index)
18
+ end
19
+
20
+ def index=(new_index)
21
+ case new_index
22
+ when DaruIndex
23
+ data.index = new_index.values
24
+ when Index
25
+ data.index = new_index.to_a
26
+ else
27
+ data.index = new_index
28
+ end
29
+ end
30
+
31
+ def_delegators :data, :name, :name=
32
+
33
+ def compare_data_equality(other)
34
+ case other
35
+ when DaruVectorAdapter
36
+ data == other.data
37
+ when ArrayAdapter
38
+ data.to_a == other.data
39
+ when NArrayAdapter, NMatrixAdapter, NumpyAdapter, PandasSeriesAdapter
40
+ other.compare_data_equality(self)
41
+ else
42
+ data == other.data.to_a
43
+ end
44
+ end
45
+
46
+ def [](key)
47
+ case key
48
+ when Charty::Vector
49
+ where(key)
50
+ else
51
+ data[key]
52
+ end
53
+ end
54
+
55
+ def_delegators :data, :[]=, :to_a
56
+
57
+ def values_at(*indices)
58
+ indices.map {|i| data[i] }
59
+ end
60
+
61
+ def where(mask)
62
+ masked_data, masked_index = where_in_array(mask)
63
+ Charty::Vector.new(Daru::Vector.new(masked_data, index: masked_index), name: name)
64
+ end
65
+
66
+ def where_in_array(mask)
67
+ mask = check_mask_vector(mask)
68
+ masked_data = []
69
+ masked_index = []
70
+ mask.each_with_index do |f, i|
71
+ case f
72
+ when true, 1
73
+ masked_data << data[i]
74
+ masked_index << data.index.key(i)
75
+ end
76
+ end
77
+ return masked_data, masked_index
78
+ end
79
+
80
+ def first_nonnil
81
+ data.drop_while(&:nil?).first
82
+ end
83
+
84
+ def boolean?
85
+ case
86
+ when numeric?, categorical?
87
+ false
88
+ else
89
+ case first_nonnil
90
+ when true, false
91
+ true
92
+ else
93
+ false
94
+ end
95
+ end
96
+ end
97
+
98
+ def_delegators :data, :numeric?
99
+ def_delegator :data, :category?, :categorical?
100
+
101
+ def categories
102
+ data.categories.compact if categorical?
103
+ end
104
+
105
+ def unique_values
106
+ data.uniq.to_a
107
+ end
108
+
109
+ def group_by(grouper)
110
+ case grouper
111
+ when Daru::Vector
112
+ if grouper.category?
113
+ # TODO: A categorical Daru::Vector cannot perform group_by well
114
+ grouper = Daru::Vector.new(grouper.to_a)
115
+ end
116
+ groups = grouper.group_by.groups
117
+ groups.map { |g, indices|
118
+ [g.first, Charty::Vector.new(data[*indices])]
119
+ }.to_h
120
+ when Charty::Vector
121
+ case grouper.data
122
+ when Daru::Vector
123
+ return group_by(grouper.data)
124
+ else
125
+ return group_by(Daru::Vector.new(grouper.to_a))
126
+ end
127
+ else
128
+ return group_by(Charty::Vector.new(grouper))
129
+ end
130
+ end
131
+
132
+ def drop_na
133
+ values = data.reject do |x|
134
+ case
135
+ when x.nil?,
136
+ x.respond_to?(:nan?) && x.nan?
137
+ true
138
+ else
139
+ false
140
+ end
141
+ end
142
+ Charty::Vector.new(Daru::Vector.new(values))
143
+ end
144
+
145
+ def eq(val)
146
+ Charty::Vector.new(data.eq(val).to_a,
147
+ index: data.index.to_a,
148
+ name: name)
149
+ end
150
+
151
+ def notnull
152
+ notnull_data = data.map {|x| ! missing_value?(x) }
153
+ Charty::Vector.new(notnull_data, index: data.index.to_a, name: name)
154
+ end
155
+
156
+ def_delegator :data, :mean
157
+
158
+ def stdev(population: false)
159
+ if population
160
+ data.standard_deviation_sample
161
+ else
162
+ data.standard_deviation_population
163
+ end
164
+ end
165
+
166
+ def percentile(q)
167
+ data.linear_percentile(q)
168
+ end
169
+ end
170
+ end
171
+ end