charty 0.2.3 → 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.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +56 -23
- data/.github/workflows/nmatrix.yml +67 -0
- data/.github/workflows/pycall.yml +86 -0
- data/Gemfile +18 -0
- data/README.md +123 -4
- data/Rakefile +4 -5
- data/charty.gemspec +1 -3
- data/examples/sample_images/hist_gruff.png +0 -0
- data/images/penguins_body_mass_g_flipper_length_mm_scatter_plot.png +0 -0
- data/images/penguins_body_mass_g_flipper_length_mm_species_scatter_plot.png +0 -0
- data/images/penguins_body_mass_g_flipper_length_mm_species_sex_scatter_plot.png +0 -0
- data/images/penguins_species_body_mass_g_bar_plot_h.png +0 -0
- data/images/penguins_species_body_mass_g_bar_plot_v.png +0 -0
- data/images/penguins_species_body_mass_g_box_plot_h.png +0 -0
- data/images/penguins_species_body_mass_g_box_plot_v.png +0 -0
- data/images/penguins_species_body_mass_g_sex_bar_plot_v.png +0 -0
- data/images/penguins_species_body_mass_g_sex_box_plot_v.png +0 -0
- data/lib/charty.rb +4 -0
- data/lib/charty/backends/gruff.rb +13 -2
- data/lib/charty/backends/plotly.rb +322 -20
- data/lib/charty/backends/pyplot.rb +416 -64
- data/lib/charty/index.rb +213 -0
- data/lib/charty/linspace.rb +1 -1
- data/lib/charty/missing_value_support.rb +14 -0
- data/lib/charty/plot_methods.rb +173 -8
- data/lib/charty/plotters.rb +7 -0
- data/lib/charty/plotters/abstract_plotter.rb +87 -12
- data/lib/charty/plotters/bar_plotter.rb +200 -3
- data/lib/charty/plotters/box_plotter.rb +75 -7
- data/lib/charty/plotters/categorical_plotter.rb +272 -40
- data/lib/charty/plotters/count_plotter.rb +7 -0
- data/lib/charty/plotters/estimation_support.rb +84 -0
- data/lib/charty/plotters/random_support.rb +25 -0
- data/lib/charty/plotters/relational_plotter.rb +518 -0
- data/lib/charty/plotters/scatter_plotter.rb +115 -0
- data/lib/charty/plotters/vector_plotter.rb +6 -0
- data/lib/charty/statistics.rb +87 -2
- data/lib/charty/table.rb +50 -15
- data/lib/charty/table_adapters.rb +2 -0
- data/lib/charty/table_adapters/active_record_adapter.rb +17 -9
- data/lib/charty/table_adapters/base_adapter.rb +69 -0
- data/lib/charty/table_adapters/daru_adapter.rb +37 -3
- data/lib/charty/table_adapters/datasets_adapter.rb +6 -2
- data/lib/charty/table_adapters/hash_adapter.rb +130 -16
- data/lib/charty/table_adapters/narray_adapter.rb +25 -6
- data/lib/charty/table_adapters/nmatrix_adapter.rb +15 -5
- data/lib/charty/table_adapters/pandas_adapter.rb +81 -0
- data/lib/charty/vector.rb +69 -0
- data/lib/charty/vector_adapters.rb +183 -0
- data/lib/charty/vector_adapters/array_adapter.rb +109 -0
- data/lib/charty/vector_adapters/daru_adapter.rb +171 -0
- data/lib/charty/vector_adapters/narray_adapter.rb +187 -0
- data/lib/charty/vector_adapters/nmatrix_adapter.rb +37 -0
- data/lib/charty/vector_adapters/numpy_adapter.rb +168 -0
- data/lib/charty/vector_adapters/pandas_adapter.rb +200 -0
- data/lib/charty/version.rb +1 -1
- metadata +33 -45
@@ -0,0 +1,115 @@
|
|
1
|
+
module Charty
|
2
|
+
module Plotters
|
3
|
+
class ScatterPlotter < RelationalPlotter
|
4
|
+
def initialize(data: nil, variables: {}, **options, &block)
|
5
|
+
x, y, color, style, size = variables.values_at(:x, :y, :color, :style, :size)
|
6
|
+
super(x, y, color, style, size, data: data, **options, &block)
|
7
|
+
end
|
8
|
+
|
9
|
+
attr_reader :alpha, :legend
|
10
|
+
|
11
|
+
def alpha=(val)
|
12
|
+
case val
|
13
|
+
when nil, :auto, 0..1
|
14
|
+
@alpha = val
|
15
|
+
when "auto"
|
16
|
+
@alpha = val.to_sym
|
17
|
+
when Numeric
|
18
|
+
raise ArgumentError,
|
19
|
+
"the given alpha is out of bounds " +
|
20
|
+
"(%p for nil, :auto, or number 0..1)" % val
|
21
|
+
else
|
22
|
+
raise ArgumentError,
|
23
|
+
"invalid value of alpha " +
|
24
|
+
"(%p for nil, :auto, or number in 0..1)" % val
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def legend=(val)
|
29
|
+
case val
|
30
|
+
when :auto, :brief, :full, false
|
31
|
+
@legend = val
|
32
|
+
when "auto", "brief", "full"
|
33
|
+
@legend = val.to_sym
|
34
|
+
else
|
35
|
+
raise ArgumentError,
|
36
|
+
"invalid value of legend (%p for :auto, :brief, :full, or false)" % val
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
attr_reader :line_width, :edge_color
|
41
|
+
|
42
|
+
def line_width=(val)
|
43
|
+
@line_width = check_number(val, :line_width, allow_nil: true)
|
44
|
+
end
|
45
|
+
|
46
|
+
def edge_color=(val)
|
47
|
+
@line_width = check_color(val, :edge_color, allow_nil: true)
|
48
|
+
end
|
49
|
+
|
50
|
+
def render
|
51
|
+
backend = Backends.current
|
52
|
+
backend.begin_figure
|
53
|
+
draw_points(backend)
|
54
|
+
annotate_axes(backend)
|
55
|
+
backend.show
|
56
|
+
end
|
57
|
+
|
58
|
+
def save(filename, **opts)
|
59
|
+
backend = Backends.current
|
60
|
+
backend.begin_figure
|
61
|
+
draw_points(backend)
|
62
|
+
annotate_axes(backend)
|
63
|
+
backend.save(filename, **opts)
|
64
|
+
end
|
65
|
+
|
66
|
+
private def draw_points(backend)
|
67
|
+
map_color(palette: palette, order: color_order, norm: color_norm)
|
68
|
+
map_size(sizes: sizes, order: size_order, norm: size_norm)
|
69
|
+
map_style(markers: markers, order: marker_order)
|
70
|
+
|
71
|
+
data = @plot_data.drop_na
|
72
|
+
|
73
|
+
# TODO: shold pass key_color to backend's scatter method.
|
74
|
+
# In pyplot backend, it is passed as color parameter.
|
75
|
+
|
76
|
+
x = data[:x]
|
77
|
+
y = data[:y]
|
78
|
+
color = data[:color] if @variables.key?(:color)
|
79
|
+
style = data[:style] if @variables.key?(:style)
|
80
|
+
size = data[:size] if @variables.key?(:size)
|
81
|
+
|
82
|
+
# TODO: key_color
|
83
|
+
backend.scatter(
|
84
|
+
x, y, @variables,
|
85
|
+
color: color, color_mapper: @color_mapper,
|
86
|
+
style: style, style_mapper: @style_mapper,
|
87
|
+
size: size, size_mapper: @size_mapper,
|
88
|
+
legend: legend
|
89
|
+
)
|
90
|
+
end
|
91
|
+
|
92
|
+
private def annotate_axes(backend)
|
93
|
+
xlabel = self.variables[:x]
|
94
|
+
ylabel = self.variables[:y]
|
95
|
+
backend.set_xlabel(xlabel) unless xlabel.nil?
|
96
|
+
backend.set_ylabel(ylabel) unless ylabel.nil?
|
97
|
+
|
98
|
+
if legend
|
99
|
+
add_legend_data(backend)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
private def add_legend_data(backend)
|
104
|
+
# TODO: Legend Support
|
105
|
+
verbosity = legend
|
106
|
+
verbosity = :auto if verbosity == true
|
107
|
+
|
108
|
+
titles = [:color, :size, :style].filter_map do |v|
|
109
|
+
variables[v] if variables.key?(v)
|
110
|
+
end
|
111
|
+
legend_title = titles.length == 1 ? titles[0] : ""
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
data/lib/charty/statistics.rb
CHANGED
@@ -7,8 +7,8 @@ module Charty
|
|
7
7
|
enum.mean
|
8
8
|
end
|
9
9
|
|
10
|
-
def self.stdev(enum)
|
11
|
-
enum.stdev
|
10
|
+
def self.stdev(enum, population: false)
|
11
|
+
enum.stdev(population: population)
|
12
12
|
end
|
13
13
|
rescue LoadError
|
14
14
|
def self.mean(enum)
|
@@ -25,5 +25,90 @@ module Charty
|
|
25
25
|
Math.sqrt(var)
|
26
26
|
end
|
27
27
|
end
|
28
|
+
|
29
|
+
def self.bootstrap(vector, n_boot: 2000, func: :mean, units: nil, random: nil)
|
30
|
+
n = vector.size
|
31
|
+
random = Charty::Plotters::RandomSupport.check_random(random)
|
32
|
+
func = Charty::Plotters::EstimationSupport.check_estimator(func)
|
33
|
+
|
34
|
+
if units
|
35
|
+
return structured_bootstrap(vector, n_boot, units, func, random)
|
36
|
+
end
|
37
|
+
|
38
|
+
if defined?(Pandas::Series) || defined?(Numpy::NDArray)
|
39
|
+
boot_dist = bootstrap_optimized_for_pycall(vector, n_boot, random, func)
|
40
|
+
return boot_dist if boot_dist
|
41
|
+
end
|
42
|
+
|
43
|
+
boot_dist = Array.new(n_boot) do |i|
|
44
|
+
resampler = Array.new(n) { random.rand(n) }
|
45
|
+
|
46
|
+
w ||= vector.values_at(*resampler)
|
47
|
+
|
48
|
+
case func
|
49
|
+
when :mean
|
50
|
+
mean(w)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
boot_dist
|
55
|
+
end
|
56
|
+
|
57
|
+
private_class_method def self.bootstrap_optimized_for_pycall(vector, n_boot, random, func)
|
58
|
+
case
|
59
|
+
when vector.is_a?(Charty::Vector)
|
60
|
+
bootstrap_optimized_for_pycall(vector.data, n_boot, random, func)
|
61
|
+
|
62
|
+
when defined?(Pandas::Series) && vector.is_a?(Pandas::Series) || vector.is_a?(Numpy::NDArray)
|
63
|
+
# numpy is also available when pandas is available
|
64
|
+
n = vector.size
|
65
|
+
resampler = Numpy.empty(n, dtype: Numpy.intp)
|
66
|
+
Array.new(n_boot) do |i|
|
67
|
+
# TODO: Use Numo and MemoryView to reduce execution time
|
68
|
+
# resampler = Numo::Int64.new(n).rand(n)
|
69
|
+
# w = Numpy.take(vector, resampler)
|
70
|
+
n.times {|i| resampler[i] = random.rand(n) }
|
71
|
+
w = vector.take(resampler)
|
72
|
+
|
73
|
+
case func
|
74
|
+
when :mean
|
75
|
+
w.mean
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
private_class_method def self.structured_bootstrap(vector, n_boot, units, func, random)
|
82
|
+
raise NotImplementedError,
|
83
|
+
"structured bootstrapping has not been supported yet"
|
84
|
+
end
|
85
|
+
|
86
|
+
def self.bootstrap_ci(*vectors, which, n_boot: 2000, func: :mean, units: nil, random: nil)
|
87
|
+
boot = bootstrap(*vectors, n_boot: n_boot, func: func, units: units, random: random)
|
88
|
+
q = [50 - which / 2, 50 + which / 2]
|
89
|
+
if boot.respond_to?(:percentile)
|
90
|
+
boot.percentile(q)
|
91
|
+
else
|
92
|
+
percentile(boot, q)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
# TODO: optimize with introselect algorithm
|
97
|
+
def self.percentile(a, q)
|
98
|
+
return mean(a) if a.size == 0
|
99
|
+
|
100
|
+
a = a.sort
|
101
|
+
n = a.size
|
102
|
+
q.map do |x|
|
103
|
+
x = n * (x / 100.0)
|
104
|
+
i = x.floor
|
105
|
+
if i == n-1
|
106
|
+
a[i]
|
107
|
+
else
|
108
|
+
t = x - i
|
109
|
+
(1-t)*a[i] + t*a[i+1]
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
28
113
|
end
|
29
114
|
end
|
data/lib/charty/table.rb
CHANGED
@@ -13,6 +13,7 @@ module Charty
|
|
13
13
|
|
14
14
|
class Table
|
15
15
|
extend Forwardable
|
16
|
+
include MissingValueSupport
|
16
17
|
|
17
18
|
def initialize(data, **kwargs)
|
18
19
|
adapter_class = TableAdapters.find_adapter_class(data)
|
@@ -21,31 +22,46 @@ module Charty
|
|
21
22
|
else
|
22
23
|
@adapter = adapter_class.new(data, **kwargs)
|
23
24
|
end
|
25
|
+
|
26
|
+
@column_cache = {}
|
24
27
|
end
|
25
28
|
|
26
29
|
attr_reader :adapter
|
27
30
|
|
31
|
+
def_delegators :adapter, :length, :column_length
|
32
|
+
|
33
|
+
def_delegators :adapter, :columns, :columns=
|
34
|
+
def_delegators :adapter, :index, :index=
|
35
|
+
|
28
36
|
def_delegator :@adapter, :column_names
|
29
37
|
def_delegator :@adapter, :data, :raw_data
|
30
38
|
|
31
|
-
def
|
32
|
-
|
39
|
+
def ==(other)
|
40
|
+
return true if equal?(other)
|
41
|
+
|
42
|
+
case other
|
43
|
+
when Charty::Table
|
44
|
+
adapter == other.adapter
|
45
|
+
else
|
46
|
+
super
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def empty?
|
51
|
+
length == 0
|
33
52
|
end
|
34
53
|
|
35
|
-
def [](
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
column = args[1]
|
45
|
-
@adapter[row, column]
|
54
|
+
def [](key)
|
55
|
+
key = case key
|
56
|
+
when Symbol
|
57
|
+
key
|
58
|
+
else
|
59
|
+
String.try_convert(key).to_sym
|
60
|
+
end
|
61
|
+
if @column_cache.key?(key)
|
62
|
+
@column_cache[key]
|
46
63
|
else
|
47
|
-
|
48
|
-
raise ArgumentError, message
|
64
|
+
@column_cache[key] = @adapter[nil, key]
|
49
65
|
end
|
50
66
|
end
|
51
67
|
|
@@ -81,5 +97,24 @@ module Charty
|
|
81
97
|
i += 1
|
82
98
|
end
|
83
99
|
end
|
100
|
+
|
101
|
+
def drop_na
|
102
|
+
# TODO: Must implement this method in each adapter
|
103
|
+
missing_index = index.select do |i|
|
104
|
+
column_names.any? do |key|
|
105
|
+
missing_value?(self[key][i])
|
106
|
+
end
|
107
|
+
end
|
108
|
+
if missing_index.empty?
|
109
|
+
self
|
110
|
+
else
|
111
|
+
select_index = index.to_a - missing_index
|
112
|
+
new_data = column_names.map { |key|
|
113
|
+
vals = select_index.map {|i| self[key][i] }
|
114
|
+
[key, vals]
|
115
|
+
}.to_h
|
116
|
+
Charty::Table.new(new_data, index: select_index)
|
117
|
+
end
|
118
|
+
end
|
84
119
|
end
|
85
120
|
end
|
@@ -15,9 +15,11 @@ module Charty
|
|
15
15
|
end
|
16
16
|
end
|
17
17
|
|
18
|
+
require_relative 'table_adapters/base_adapter'
|
18
19
|
require_relative 'table_adapters/hash_adapter'
|
19
20
|
require_relative 'table_adapters/narray_adapter'
|
20
21
|
require_relative 'table_adapters/datasets_adapter'
|
21
22
|
require_relative 'table_adapters/daru_adapter'
|
22
23
|
require_relative 'table_adapters/active_record_adapter'
|
23
24
|
require_relative 'table_adapters/nmatrix_adapter'
|
25
|
+
require_relative 'table_adapters/pandas_adapter'
|
@@ -1,10 +1,8 @@
|
|
1
1
|
module Charty
|
2
2
|
module TableAdapters
|
3
|
-
class ActiveRecordAdapter
|
3
|
+
class ActiveRecordAdapter < BaseAdapter
|
4
4
|
TableAdapters.register(:active_record, self)
|
5
5
|
|
6
|
-
include Enumerable
|
7
|
-
|
8
6
|
def self.supported?(data)
|
9
7
|
defined?(ActiveRecord::Relation) && data.is_a?(ActiveRecord::Relation)
|
10
8
|
end
|
@@ -12,17 +10,27 @@ module Charty
|
|
12
10
|
def initialize(data)
|
13
11
|
@data = check_type(ActiveRecord::Relation, data, :data)
|
14
12
|
@column_names = @data.column_names.freeze
|
15
|
-
|
13
|
+
self.columns = Index.new(@column_names)
|
14
|
+
self.index = RangeIndex.new(0 ... length)
|
16
15
|
end
|
17
16
|
|
18
|
-
attr_reader :
|
17
|
+
attr_reader :data, :column_names
|
18
|
+
|
19
|
+
def_delegators :data, :size
|
20
|
+
|
21
|
+
alias length size
|
22
|
+
|
23
|
+
def column_length
|
24
|
+
column_names.length
|
25
|
+
end
|
19
26
|
|
20
27
|
def [](row, column)
|
21
|
-
fetch_records unless @
|
28
|
+
fetch_records unless @columns_cache
|
22
29
|
if row
|
23
|
-
@
|
30
|
+
@columns_cache[resolve_column_index(column)][row]
|
24
31
|
else
|
25
|
-
@
|
32
|
+
column_data = @columns_cache[resolve_column_index(column)]
|
33
|
+
Vector.new(column_data, index: index, name: column)
|
26
34
|
end
|
27
35
|
end
|
28
36
|
|
@@ -43,7 +51,7 @@ module Charty
|
|
43
51
|
end
|
44
52
|
|
45
53
|
private def fetch_records
|
46
|
-
@
|
54
|
+
@columns_cache = @data.pluck(*column_names).transpose
|
47
55
|
end
|
48
56
|
|
49
57
|
private def check_type(type, data, name)
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require "forwardable"
|
2
|
+
|
3
|
+
module Charty
|
4
|
+
module TableAdapters
|
5
|
+
class BaseAdapter
|
6
|
+
extend Forwardable
|
7
|
+
include Enumerable
|
8
|
+
|
9
|
+
attr_reader :columns
|
10
|
+
|
11
|
+
def columns=(values)
|
12
|
+
@columns = check_and_convert_index(values, :columns, column_length)
|
13
|
+
end
|
14
|
+
|
15
|
+
def column_names
|
16
|
+
columns.to_a
|
17
|
+
end
|
18
|
+
|
19
|
+
attr_reader :index
|
20
|
+
|
21
|
+
def index=(values)
|
22
|
+
@index = check_and_convert_index(values, :index, length)
|
23
|
+
end
|
24
|
+
|
25
|
+
def ==(other)
|
26
|
+
case other
|
27
|
+
when BaseAdapter
|
28
|
+
return false if columns != other.columns
|
29
|
+
return false if index != other.index
|
30
|
+
compare_data_equality(other)
|
31
|
+
else
|
32
|
+
false
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def compare_data_equality(other)
|
37
|
+
columns.each do |name|
|
38
|
+
if self[nil, name] != other[nil, name]
|
39
|
+
return false
|
40
|
+
end
|
41
|
+
end
|
42
|
+
true
|
43
|
+
end
|
44
|
+
|
45
|
+
private def check_and_convert_index(values, name, expected_length)
|
46
|
+
case values
|
47
|
+
when Index, Range
|
48
|
+
else
|
49
|
+
unless (ary = Array.try_convert(values))
|
50
|
+
raise ArgumentError, "invalid object for %s: %p" % [name, values]
|
51
|
+
end
|
52
|
+
values = ary
|
53
|
+
end
|
54
|
+
if expected_length != values.size
|
55
|
+
raise ArgumentError,
|
56
|
+
"invalid length for %s (%d for %d)" % [name, values.size, expected_length]
|
57
|
+
end
|
58
|
+
case values
|
59
|
+
when Index
|
60
|
+
values
|
61
|
+
when Range
|
62
|
+
RangeIndex.new(values)
|
63
|
+
when Array
|
64
|
+
Index.new(values)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|