charty 0.1.5.dev → 0.2.5

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