charty 0.1.4.dev → 0.2.4

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