charty 0.1.5.dev → 0.2.5

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 (87) 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 +176 -9
  8. data/Rakefile +4 -5
  9. data/charty.gemspec +10 -1
  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_bokeh.ipynb +156 -0
  19. data/examples/sample_google_chart.ipynb +229 -68
  20. data/examples/sample_images/bar_bokeh.html +85 -0
  21. data/examples/sample_images/barh_bokeh.html +85 -0
  22. data/examples/sample_images/box_plot_bokeh.html +85 -0
  23. data/examples/sample_images/curve_bokeh.html +85 -0
  24. data/examples/sample_images/curve_with_function_bokeh.html +85 -0
  25. data/examples/sample_images/hist_gruff.png +0 -0
  26. data/examples/sample_images/scatter_bokeh.html +85 -0
  27. data/examples/sample_pyplot.ipynb +40 -38
  28. data/images/penguins_body_mass_g_flipper_length_mm_scatter_plot.png +0 -0
  29. data/images/penguins_body_mass_g_flipper_length_mm_species_scatter_plot.png +0 -0
  30. data/images/penguins_body_mass_g_flipper_length_mm_species_sex_scatter_plot.png +0 -0
  31. data/images/penguins_species_body_mass_g_bar_plot_h.png +0 -0
  32. data/images/penguins_species_body_mass_g_bar_plot_v.png +0 -0
  33. data/images/penguins_species_body_mass_g_box_plot_h.png +0 -0
  34. data/images/penguins_species_body_mass_g_box_plot_v.png +0 -0
  35. data/images/penguins_species_body_mass_g_sex_bar_plot_v.png +0 -0
  36. data/images/penguins_species_body_mass_g_sex_box_plot_v.png +0 -0
  37. data/lib/charty.rb +14 -1
  38. data/lib/charty/backend_methods.rb +8 -0
  39. data/lib/charty/backends.rb +80 -0
  40. data/lib/charty/backends/bokeh.rb +32 -26
  41. data/lib/charty/backends/google_charts.rb +267 -0
  42. data/lib/charty/backends/gruff.rb +102 -83
  43. data/lib/charty/backends/plotly.rb +685 -0
  44. data/lib/charty/backends/pyplot.rb +586 -92
  45. data/lib/charty/backends/rubyplot.rb +82 -74
  46. data/lib/charty/backends/unicode_plot.rb +79 -0
  47. data/lib/charty/index.rb +213 -0
  48. data/lib/charty/linspace.rb +1 -1
  49. data/lib/charty/missing_value_support.rb +14 -0
  50. data/lib/charty/plot_methods.rb +184 -0
  51. data/lib/charty/plotter.rb +48 -40
  52. data/lib/charty/plotters.rb +11 -0
  53. data/lib/charty/plotters/abstract_plotter.rb +183 -0
  54. data/lib/charty/plotters/bar_plotter.rb +201 -0
  55. data/lib/charty/plotters/box_plotter.rb +79 -0
  56. data/lib/charty/plotters/categorical_plotter.rb +380 -0
  57. data/lib/charty/plotters/count_plotter.rb +7 -0
  58. data/lib/charty/plotters/estimation_support.rb +84 -0
  59. data/lib/charty/plotters/random_support.rb +25 -0
  60. data/lib/charty/plotters/relational_plotter.rb +518 -0
  61. data/lib/charty/plotters/scatter_plotter.rb +104 -0
  62. data/lib/charty/plotters/vector_plotter.rb +6 -0
  63. data/lib/charty/statistics.rb +114 -0
  64. data/lib/charty/table.rb +80 -3
  65. data/lib/charty/table_adapters.rb +25 -0
  66. data/lib/charty/table_adapters/active_record_adapter.rb +63 -0
  67. data/lib/charty/table_adapters/base_adapter.rb +69 -0
  68. data/lib/charty/table_adapters/daru_adapter.rb +70 -0
  69. data/lib/charty/table_adapters/datasets_adapter.rb +49 -0
  70. data/lib/charty/table_adapters/hash_adapter.rb +224 -0
  71. data/lib/charty/table_adapters/narray_adapter.rb +76 -0
  72. data/lib/charty/table_adapters/nmatrix_adapter.rb +67 -0
  73. data/lib/charty/table_adapters/pandas_adapter.rb +81 -0
  74. data/lib/charty/util.rb +20 -0
  75. data/lib/charty/vector.rb +69 -0
  76. data/lib/charty/vector_adapters.rb +183 -0
  77. data/lib/charty/vector_adapters/array_adapter.rb +109 -0
  78. data/lib/charty/vector_adapters/daru_adapter.rb +171 -0
  79. data/lib/charty/vector_adapters/narray_adapter.rb +187 -0
  80. data/lib/charty/vector_adapters/nmatrix_adapter.rb +37 -0
  81. data/lib/charty/vector_adapters/numpy_adapter.rb +168 -0
  82. data/lib/charty/vector_adapters/pandas_adapter.rb +200 -0
  83. data/lib/charty/version.rb +1 -1
  84. metadata +179 -10
  85. data/.travis.yml +0 -11
  86. data/lib/charty/backends/google_chart.rb +0 -167
  87. 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,20 @@
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
+ end
20
+ 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