charty 0.2.3 → 0.2.8
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 +172 -4
- data/Rakefile +4 -5
- data/charty.gemspec +10 -6
- 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 +8 -1
- data/lib/charty/backends/bokeh.rb +2 -2
- data/lib/charty/backends/google_charts.rb +1 -1
- data/lib/charty/backends/gruff.rb +14 -3
- data/lib/charty/backends/plotly.rb +731 -32
- data/lib/charty/backends/plotly_helpers/html_renderer.rb +203 -0
- data/lib/charty/backends/plotly_helpers/notebook_renderer.rb +87 -0
- data/lib/charty/backends/plotly_helpers/plotly_renderer.rb +121 -0
- data/lib/charty/backends/pyplot.rb +514 -66
- data/lib/charty/backends/rubyplot.rb +1 -1
- data/lib/charty/cache_dir.rb +27 -0
- data/lib/charty/dash_pattern_generator.rb +57 -0
- data/lib/charty/index.rb +213 -0
- data/lib/charty/iruby_helper.rb +18 -0
- data/lib/charty/linspace.rb +1 -1
- data/lib/charty/plot_methods.rb +283 -8
- data/lib/charty/plotter.rb +2 -2
- data/lib/charty/plotters.rb +11 -0
- data/lib/charty/plotters/abstract_plotter.rb +186 -16
- data/lib/charty/plotters/bar_plotter.rb +189 -7
- data/lib/charty/plotters/box_plotter.rb +64 -11
- data/lib/charty/plotters/categorical_plotter.rb +272 -40
- data/lib/charty/plotters/count_plotter.rb +7 -0
- data/lib/charty/plotters/distribution_plotter.rb +143 -0
- data/lib/charty/plotters/estimation_support.rb +84 -0
- data/lib/charty/plotters/histogram_plotter.rb +186 -0
- data/lib/charty/plotters/line_plotter.rb +300 -0
- data/lib/charty/plotters/random_support.rb +25 -0
- data/lib/charty/plotters/relational_plotter.rb +635 -0
- data/lib/charty/plotters/scatter_plotter.rb +80 -0
- data/lib/charty/plotters/vector_plotter.rb +6 -0
- data/lib/charty/statistics.rb +96 -2
- data/lib/charty/table.rb +160 -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 +166 -0
- data/lib/charty/table_adapters/daru_adapter.rb +39 -3
- data/lib/charty/table_adapters/datasets_adapter.rb +13 -2
- data/lib/charty/table_adapters/hash_adapter.rb +141 -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 +163 -0
- data/lib/charty/util.rb +28 -0
- data/lib/charty/vector.rb +69 -0
- data/lib/charty/vector_adapters.rb +187 -0
- data/lib/charty/vector_adapters/array_adapter.rb +101 -0
- data/lib/charty/vector_adapters/daru_adapter.rb +163 -0
- data/lib/charty/vector_adapters/narray_adapter.rb +182 -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 +199 -0
- data/lib/charty/version.rb +1 -1
- metadata +92 -25
    
        data/lib/charty/plotter.rb
    CHANGED
    
    | @@ -237,11 +237,11 @@ module Charty | |
| 237 237 | 
             
                end
         | 
| 238 238 |  | 
| 239 239 | 
             
                def render(filename=nil)
         | 
| 240 | 
            -
                  @backend. | 
| 240 | 
            +
                  @backend.old_style_render(self, filename)
         | 
| 241 241 | 
             
                end
         | 
| 242 242 |  | 
| 243 243 | 
             
                def save(filename=nil, **kw)
         | 
| 244 | 
            -
                  @backend. | 
| 244 | 
            +
                  @backend.old_style_save(self, filename, **kw)
         | 
| 245 245 | 
             
                end
         | 
| 246 246 |  | 
| 247 247 | 
             
                def apply(backend)
         | 
    
        data/lib/charty/plotters.rb
    CHANGED
    
    | @@ -1,4 +1,15 @@ | |
| 1 1 | 
             
            require_relative "plotters/abstract_plotter"
         | 
| 2 | 
            +
            require_relative "plotters/random_support"
         | 
| 3 | 
            +
            require_relative "plotters/estimation_support"
         | 
| 2 4 | 
             
            require_relative "plotters/categorical_plotter"
         | 
| 3 5 | 
             
            require_relative "plotters/bar_plotter"
         | 
| 4 6 | 
             
            require_relative "plotters/box_plotter"
         | 
| 7 | 
            +
            require_relative "plotters/count_plotter"
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            require_relative "plotters/vector_plotter"
         | 
| 10 | 
            +
            require_relative "plotters/relational_plotter"
         | 
| 11 | 
            +
            require_relative "plotters/scatter_plotter"
         | 
| 12 | 
            +
            require_relative "plotters/line_plotter"
         | 
| 13 | 
            +
             | 
| 14 | 
            +
            require_relative "plotters/distribution_plotter"
         | 
| 15 | 
            +
            require_relative "plotters/histogram_plotter"
         | 
| @@ -8,36 +8,68 @@ module Charty | |
| 8 8 | 
             
                    self.data = data
         | 
| 9 9 | 
             
                    self.palette = palette
         | 
| 10 10 | 
             
                    substitute_options(options)
         | 
| 11 | 
            -
                    yield self if block_given?
         | 
| 12 | 
            -
                  end
         | 
| 13 11 |  | 
| 14 | 
            -
             | 
| 12 | 
            +
                    @var_levels = {}
         | 
| 13 | 
            +
                    @var_ordered = {x: false, y: false}
         | 
| 15 14 |  | 
| 16 | 
            -
             | 
| 17 | 
            -
                    @x = check_dimension(x, :x)
         | 
| 15 | 
            +
                    yield self if block_given?
         | 
| 18 16 | 
             
                  end
         | 
| 19 17 |  | 
| 20 | 
            -
                   | 
| 21 | 
            -
             | 
| 22 | 
            -
                  end
         | 
| 18 | 
            +
                  attr_reader :data, :x, :y, :color
         | 
| 19 | 
            +
                  attr_reader :color_order, :key_color, :palette
         | 
| 23 20 |  | 
| 24 | 
            -
                  def  | 
| 25 | 
            -
                     | 
| 26 | 
            -
             | 
| 27 | 
            -
                       | 
| 28 | 
            -
             | 
| 21 | 
            +
                  def var_levels
         | 
| 22 | 
            +
                    variables.each_key do |var|
         | 
| 23 | 
            +
                      # TODO: Move mappers from RelationalPlotter to here,
         | 
| 24 | 
            +
                      #       and remove the use of instance_variable_get
         | 
| 25 | 
            +
                      if instance_variable_defined?(:"@#{var}_mapper")
         | 
| 26 | 
            +
                        mapper = instance_variable_get(:"@#{var}_mapper")
         | 
| 27 | 
            +
                        @var_levels[var] = mapper.levels
         | 
| 28 | 
            +
                      end
         | 
| 29 29 | 
             
                    end
         | 
| 30 | 
            +
                    @var_levels
         | 
| 31 | 
            +
                  end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                  def inspect
         | 
| 34 | 
            +
                    "#<#{self.class}:0x%016x>" % self.object_id
         | 
| 30 35 | 
             
                  end
         | 
| 31 36 |  | 
| 32 37 | 
             
                  def data=(data)
         | 
| 33 38 | 
             
                    @data = case data
         | 
| 34 39 | 
             
                            when nil, Charty::Table
         | 
| 35 40 | 
             
                              data
         | 
| 41 | 
            +
                            when method(:array?)
         | 
| 42 | 
            +
                              Charty::Vector.new(data)
         | 
| 36 43 | 
             
                            else
         | 
| 37 44 | 
             
                              Charty::Table.new(data)
         | 
| 38 45 | 
             
                            end
         | 
| 39 46 | 
             
                  end
         | 
| 40 47 |  | 
| 48 | 
            +
                  def x=(x)
         | 
| 49 | 
            +
                    @x = check_dimension(x, :x)
         | 
| 50 | 
            +
                  end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                  def y=(y)
         | 
| 53 | 
            +
                    @y = check_dimension(y, :y)
         | 
| 54 | 
            +
                  end
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                  def color=(color)
         | 
| 57 | 
            +
                    @color = check_dimension(color, :color)
         | 
| 58 | 
            +
                  end
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                  def color_order=(color_order)
         | 
| 61 | 
            +
                    @color_order = color_order
         | 
| 62 | 
            +
                  end
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                  # TODO: move to categorical_plotter
         | 
| 65 | 
            +
                  def key_color=(key_color)
         | 
| 66 | 
            +
                    #@key_color = XXX
         | 
| 67 | 
            +
                    unless key_color.nil?
         | 
| 68 | 
            +
                      raise NotImplementedError,
         | 
| 69 | 
            +
                            "Specifying key_color is not supported yet"
         | 
| 70 | 
            +
                    end
         | 
| 71 | 
            +
                  end
         | 
| 72 | 
            +
             | 
| 41 73 | 
             
                  def palette=(palette)
         | 
| 42 74 | 
             
                    @palette = case palette
         | 
| 43 75 | 
             
                               when nil, Palette, Symbol, String
         | 
| @@ -57,10 +89,12 @@ module Charty | |
| 57 89 |  | 
| 58 90 | 
             
                  private def check_dimension(value, name)
         | 
| 59 91 | 
             
                    case value
         | 
| 60 | 
            -
                    when nil, Symbol, String | 
| 92 | 
            +
                    when nil, Symbol, String
         | 
| 61 93 | 
             
                      value
         | 
| 62 94 | 
             
                    when ->(x) { x.respond_to?(:to_str) }
         | 
| 63 95 | 
             
                      value.to_str
         | 
| 96 | 
            +
                    when method(:array?)
         | 
| 97 | 
            +
                      Charty::Vector.new(value)
         | 
| 64 98 | 
             
                    else
         | 
| 65 99 | 
             
                      raise ArgumentError,
         | 
| 66 100 | 
             
                            "invalid type of dimension for #{name} (given #{value.inspect})",
         | 
| @@ -68,13 +102,149 @@ module Charty | |
| 68 102 | 
             
                    end
         | 
| 69 103 | 
             
                  end
         | 
| 70 104 |  | 
| 105 | 
            +
                  private def check_number(value, name, allow_nil: false)
         | 
| 106 | 
            +
                    case value
         | 
| 107 | 
            +
                    when Numeric
         | 
| 108 | 
            +
                      value
         | 
| 109 | 
            +
                    else
         | 
| 110 | 
            +
                      if allow_nil && value.nil?
         | 
| 111 | 
            +
                        nil
         | 
| 112 | 
            +
                      else
         | 
| 113 | 
            +
                        expected = if allow_nil
         | 
| 114 | 
            +
                                     "number or nil"
         | 
| 115 | 
            +
                                   else
         | 
| 116 | 
            +
                                     "number"
         | 
| 117 | 
            +
                                   end
         | 
| 118 | 
            +
                        raise ArgumentError,
         | 
| 119 | 
            +
                              "invalid value for #{name} (%p for #{expected})" % value,
         | 
| 120 | 
            +
                              caller
         | 
| 121 | 
            +
                      end
         | 
| 122 | 
            +
                    end
         | 
| 123 | 
            +
                  end
         | 
| 124 | 
            +
             | 
| 125 | 
            +
                  private def check_boolean(value, name, allow_nil: false)
         | 
| 126 | 
            +
                    case value
         | 
| 127 | 
            +
                    when true, false
         | 
| 128 | 
            +
                      value
         | 
| 129 | 
            +
                    else
         | 
| 130 | 
            +
                      expected = if allow_nil
         | 
| 131 | 
            +
                                   "true, false, or nil"
         | 
| 132 | 
            +
                                 else
         | 
| 133 | 
            +
                                   "true or false"
         | 
| 134 | 
            +
                                 end
         | 
| 135 | 
            +
                      raise ArgumentError,
         | 
| 136 | 
            +
                            "invalid value for #{name} (%p for #{expected})" % value,
         | 
| 137 | 
            +
                            caller
         | 
| 138 | 
            +
                    end
         | 
| 139 | 
            +
                  end
         | 
| 140 | 
            +
             | 
| 141 | 
            +
                  private def variable_type(vector, boolean_type=:numeric)
         | 
| 142 | 
            +
                    if vector.numeric?
         | 
| 143 | 
            +
                      :numeric
         | 
| 144 | 
            +
                    elsif vector.categorical?
         | 
| 145 | 
            +
                      :categorical
         | 
| 146 | 
            +
                    else
         | 
| 147 | 
            +
                      case vector[0]
         | 
| 148 | 
            +
                      when true, false
         | 
| 149 | 
            +
                        boolean_type
         | 
| 150 | 
            +
                      else
         | 
| 151 | 
            +
                        :categorical
         | 
| 152 | 
            +
                      end
         | 
| 153 | 
            +
                    end
         | 
| 154 | 
            +
                  end
         | 
| 155 | 
            +
             | 
| 71 156 | 
             
                  private def array?(value)
         | 
| 72 157 | 
             
                    TableAdapters::HashAdapter.array?(value)
         | 
| 73 158 | 
             
                  end
         | 
| 74 159 |  | 
| 160 | 
            +
                  private def remove_na!(ary)
         | 
| 161 | 
            +
                    ary.reject! {|x| Util.missing?(x) }
         | 
| 162 | 
            +
                    ary
         | 
| 163 | 
            +
                  end
         | 
| 164 | 
            +
             | 
| 165 | 
            +
                  private def each_subset(grouping_vars, reverse: false, processed: false, by_facet: true, allow_empty: false, drop_na: true)
         | 
| 166 | 
            +
                    case grouping_vars
         | 
| 167 | 
            +
                    when nil
         | 
| 168 | 
            +
                      grouping_vars = []
         | 
| 169 | 
            +
                    when String, Symbol
         | 
| 170 | 
            +
                      grouping_vars = [grouping_vars.to_sym]
         | 
| 171 | 
            +
                    end
         | 
| 172 | 
            +
             | 
| 173 | 
            +
                    if by_facet
         | 
| 174 | 
            +
                      [:col, :row].each do |facet_var|
         | 
| 175 | 
            +
                        grouping_vars << facet_var if variables.key?(facet_var)
         | 
| 176 | 
            +
                      end
         | 
| 177 | 
            +
                    end
         | 
| 178 | 
            +
             | 
| 179 | 
            +
                    grouping_vars = grouping_vars.select {|var| variables.key?(var) }
         | 
| 180 | 
            +
             | 
| 181 | 
            +
                    data = processed ? processed_data : plot_data
         | 
| 182 | 
            +
                    data = data.drop_na if drop_na
         | 
| 183 | 
            +
             | 
| 184 | 
            +
                    levels = var_levels.dup
         | 
| 185 | 
            +
             | 
| 186 | 
            +
                    ([:x, :y] & grouping_vars).each do |axis|
         | 
| 187 | 
            +
                      levels[axis] = plot_data[axis].categorical_order()
         | 
| 188 | 
            +
                      if processed
         | 
| 189 | 
            +
                        # TODO: perform inverse conversion of axis scaling here
         | 
| 190 | 
            +
                      end
         | 
| 191 | 
            +
                    end
         | 
| 192 | 
            +
             | 
| 193 | 
            +
                    if not grouping_vars.empty?
         | 
| 194 | 
            +
                      grouped = data.group_by(grouping_vars, sort: false)
         | 
| 195 | 
            +
                      grouped.each_group do |group_key, group_data|
         | 
| 196 | 
            +
                        next if group_data.empty? && !allow_empty
         | 
| 197 | 
            +
             | 
| 198 | 
            +
                        yield(grouping_vars.zip(group_key).to_h, group_data)
         | 
| 199 | 
            +
                      end
         | 
| 200 | 
            +
                    else
         | 
| 201 | 
            +
                      yield({}, data.dup)
         | 
| 202 | 
            +
                    end
         | 
| 203 | 
            +
                  end
         | 
| 204 | 
            +
             | 
| 205 | 
            +
                  def processed_data
         | 
| 206 | 
            +
                    @processed_data ||= calculate_processed_data
         | 
| 207 | 
            +
                  end
         | 
| 208 | 
            +
             | 
| 209 | 
            +
                  private def calculate_processed_data
         | 
| 210 | 
            +
                    # TODO: axis scaling support
         | 
| 211 | 
            +
                    plot_data
         | 
| 212 | 
            +
                  end
         | 
| 213 | 
            +
             | 
| 214 | 
            +
                  def save(filename, **kwargs)
         | 
| 215 | 
            +
                    backend = Backends.current
         | 
| 216 | 
            +
                    call_render_plot(backend, notebook: false, **kwargs)
         | 
| 217 | 
            +
                    backend.save(filename, **kwargs)
         | 
| 218 | 
            +
                  end
         | 
| 219 | 
            +
             | 
| 220 | 
            +
                  def render(notebook: false, **kwargs)
         | 
| 221 | 
            +
                    backend = Backends.current
         | 
| 222 | 
            +
                    call_render_plot(backend, notebook: notebook, **kwargs)
         | 
| 223 | 
            +
                    backend.render(notebook: notebook, **kwargs)
         | 
| 224 | 
            +
                  end
         | 
| 225 | 
            +
             | 
| 226 | 
            +
                  private def call_render_plot(backend, notebook: false, **kwargs)
         | 
| 227 | 
            +
                    backend.begin_figure
         | 
| 228 | 
            +
                    render_plot(backend, notebook: notebook, **kwargs)
         | 
| 229 | 
            +
                  end
         | 
| 230 | 
            +
             | 
| 231 | 
            +
                  private def render_plot(*, **)
         | 
| 232 | 
            +
                    raise NotImplementedError,
         | 
| 233 | 
            +
                          "subclass must implement #{__method__}"
         | 
| 234 | 
            +
                  end
         | 
| 235 | 
            +
             | 
| 75 236 | 
             
                  def to_iruby
         | 
| 76 | 
            -
                     | 
| 77 | 
            -
             | 
| 237 | 
            +
                    render(notebook: IRubyHelper.iruby_notebook?)
         | 
| 238 | 
            +
                  end
         | 
| 239 | 
            +
             | 
| 240 | 
            +
                  def to_iruby_mimebundle(include: [], exclude: [])
         | 
| 241 | 
            +
                    backend = Backends.current
         | 
| 242 | 
            +
                    if backend.respond_to?(:render_mimebundle)
         | 
| 243 | 
            +
                      call_render_plot(backend, notebook: true)
         | 
| 244 | 
            +
                      backend.render_mimebundle(include: include, exclude: exclude)
         | 
| 245 | 
            +
                    else
         | 
| 246 | 
            +
                      {}
         | 
| 247 | 
            +
                    end
         | 
| 78 248 | 
             
                  end
         | 
| 79 249 | 
             
                end
         | 
| 80 250 | 
             
              end
         | 
| @@ -1,18 +1,200 @@ | |
| 1 1 | 
             
            module Charty
         | 
| 2 2 | 
             
              module Plotters
         | 
| 3 3 | 
             
                class BarPlotter < CategoricalPlotter
         | 
| 4 | 
            -
                   | 
| 5 | 
            -
             | 
| 6 | 
            -
             | 
| 4 | 
            +
                  self.default_palette = :light
         | 
| 5 | 
            +
                  self.require_numeric = true
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                  def initialize(data: nil, variables: {}, **options, &block)
         | 
| 8 | 
            +
                    x, y, color = variables.values_at(:x, :y, :color)
         | 
| 9 | 
            +
                    super(x, y, color, data: data, **options, &block)
         | 
| 10 | 
            +
                  end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                  attr_reader :error_color
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                  def error_color=(error_color)
         | 
| 15 | 
            +
                    @error_color = check_error_color(error_color)
         | 
| 16 | 
            +
                  end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                  private def check_error_color(value)
         | 
| 19 | 
            +
                    case value
         | 
| 20 | 
            +
                    when Colors::AbstractColor
         | 
| 21 | 
            +
                      value
         | 
| 22 | 
            +
                    when Array
         | 
| 23 | 
            +
                      Colors::RGB.new(*value)
         | 
| 24 | 
            +
                    when String
         | 
| 25 | 
            +
                      # TODO: Use Colors.parse when it'll be available
         | 
| 26 | 
            +
                      Colors::RGB.parse(value)
         | 
| 27 | 
            +
                    else
         | 
| 28 | 
            +
                      raise ArgumentError,
         | 
| 29 | 
            +
                            "invalid value for error_color (%p for a color, a RGB tripret, or a RGB hex string)" % value
         | 
| 30 | 
            +
                    end
         | 
| 31 | 
            +
                  end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                  attr_reader :error_width
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                  def error_width=(error_width)
         | 
| 36 | 
            +
                    @error_width = check_number(error_width, :error_width, allow_nil: true)
         | 
| 37 | 
            +
                  end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                  attr_reader :cap_size
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                  def cap_size=(cap_size)
         | 
| 42 | 
            +
                    @cap_size = check_number(cap_size, :cap_size, allow_nil: true)
         | 
| 43 | 
            +
                  end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                  private def render_plot(backend, **)
         | 
| 7 46 | 
             
                    draw_bars(backend)
         | 
| 8 47 | 
             
                    annotate_axes(backend)
         | 
| 9 | 
            -
                    backend. | 
| 48 | 
            +
                    backend.invert_yaxis if orient == :h
         | 
| 10 49 | 
             
                  end
         | 
| 11 50 |  | 
| 12 51 | 
             
                  private def draw_bars(backend)
         | 
| 13 | 
            -
                     | 
| 14 | 
            -
             | 
| 15 | 
            -
                     | 
| 52 | 
            +
                    setup_estimations
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                    if @plot_colors.nil?
         | 
| 55 | 
            +
                      bar_pos = (0 ... @estimations.length).to_a
         | 
| 56 | 
            +
                      error_colors = bar_pos.map { error_color }
         | 
| 57 | 
            +
                      if @conf_int.empty?
         | 
| 58 | 
            +
                        ci_params = {}
         | 
| 59 | 
            +
                      else
         | 
| 60 | 
            +
                        ci_params = {conf_int: @conf_int, error_colors: error_colors,
         | 
| 61 | 
            +
                                     error_width: error_width, cap_size: cap_size}
         | 
| 62 | 
            +
                      end
         | 
| 63 | 
            +
                      backend.bar(bar_pos, nil, @estimations, @colors, orient, **ci_params)
         | 
| 64 | 
            +
                    else
         | 
| 65 | 
            +
                      bar_pos = (0 ... @estimations[0].length).to_a
         | 
| 66 | 
            +
                      error_colors = bar_pos.map { error_color }
         | 
| 67 | 
            +
                      offsets = color_offsets
         | 
| 68 | 
            +
                      width = nested_width
         | 
| 69 | 
            +
                      @color_names.each_with_index do |color_name, i|
         | 
| 70 | 
            +
                        pos = bar_pos.map {|x| x + offsets[i] }
         | 
| 71 | 
            +
                        colors = Array.new(@estimations[i].length) { @colors[i] }
         | 
| 72 | 
            +
                        if @conf_int[i].empty?
         | 
| 73 | 
            +
                          ci_params = {}
         | 
| 74 | 
            +
                        else
         | 
| 75 | 
            +
                          ci_params = {conf_int: @conf_int[i], error_colors: error_colors,
         | 
| 76 | 
            +
                                       error_width: error_width, cap_size: cap_size}
         | 
| 77 | 
            +
                        end
         | 
| 78 | 
            +
                        backend.bar(pos, @group_names, @estimations[i], colors, orient,
         | 
| 79 | 
            +
                                    label: color_name, width: width, **ci_params)
         | 
| 80 | 
            +
                      end
         | 
| 81 | 
            +
                    end
         | 
| 82 | 
            +
                  end
         | 
| 83 | 
            +
             | 
| 84 | 
            +
                  private def setup_estimations
         | 
| 85 | 
            +
                    if @color_names.nil?
         | 
| 86 | 
            +
                      setup_estimations_with_single_color_group
         | 
| 87 | 
            +
                    else
         | 
| 88 | 
            +
                      setup_estimations_with_multiple_color_groups
         | 
| 89 | 
            +
                    end
         | 
| 90 | 
            +
                  end
         | 
| 91 | 
            +
             | 
| 92 | 
            +
                  private def setup_estimations_with_single_color_group
         | 
| 93 | 
            +
                    estimations = []
         | 
| 94 | 
            +
                    conf_int = []
         | 
| 95 | 
            +
             | 
| 96 | 
            +
                    @plot_data.each do |group_data|
         | 
| 97 | 
            +
                      # Single color group
         | 
| 98 | 
            +
                      if @plot_units.nil?
         | 
| 99 | 
            +
                        stat_data = group_data.drop_na
         | 
| 100 | 
            +
                        unit_data = nil
         | 
| 101 | 
            +
                      else
         | 
| 102 | 
            +
                        # TODO: Support units
         | 
| 103 | 
            +
                      end
         | 
| 104 | 
            +
             | 
| 105 | 
            +
                      estimation = if stat_data.size == 0
         | 
| 106 | 
            +
                                     Float::NAN
         | 
| 107 | 
            +
                                   else
         | 
| 108 | 
            +
                                     estimate(estimator, stat_data)
         | 
| 109 | 
            +
                                   end
         | 
| 110 | 
            +
                      estimations << estimation
         | 
| 111 | 
            +
             | 
| 112 | 
            +
                      unless ci.nil?
         | 
| 113 | 
            +
                        if stat_data.size < 2
         | 
| 114 | 
            +
                          conf_int << [Float::NAN, Float::NAN]
         | 
| 115 | 
            +
                          next
         | 
| 116 | 
            +
                        end
         | 
| 117 | 
            +
             | 
| 118 | 
            +
                        if ci == :sd
         | 
| 119 | 
            +
                          sd = stat_data.stdev
         | 
| 120 | 
            +
                          conf_int << [estimation - sd, estimation + sd]
         | 
| 121 | 
            +
                        else
         | 
| 122 | 
            +
                          conf_int << Statistics.bootstrap_ci(stat_data, ci, func: estimator, n_boot: n_boot,
         | 
| 123 | 
            +
                                                              units: unit_data, random: random)
         | 
| 124 | 
            +
                        end
         | 
| 125 | 
            +
                      end
         | 
| 126 | 
            +
                    end
         | 
| 127 | 
            +
             | 
| 128 | 
            +
                    @estimations = estimations
         | 
| 129 | 
            +
                    @conf_int = conf_int
         | 
| 130 | 
            +
                  end
         | 
| 131 | 
            +
             | 
| 132 | 
            +
                  private def setup_estimations_with_multiple_color_groups
         | 
| 133 | 
            +
                    estimations = Array.new(@color_names.length) { [] }
         | 
| 134 | 
            +
                    conf_int = Array.new(@color_names.length) { [] }
         | 
| 135 | 
            +
             | 
| 136 | 
            +
                    @plot_data.each_with_index do |group_data, i|
         | 
| 137 | 
            +
                      @color_names.each_with_index do |color_name, j|
         | 
| 138 | 
            +
                        if @plot_colors[i].length == 0
         | 
| 139 | 
            +
                          estimations[j] << Float::NAN
         | 
| 140 | 
            +
                          unless ci.nil?
         | 
| 141 | 
            +
                            conf_int[j] << [Float::NAN, Float::NAN]
         | 
| 142 | 
            +
                          end
         | 
| 143 | 
            +
                          next
         | 
| 144 | 
            +
                        end
         | 
| 145 | 
            +
             | 
| 146 | 
            +
                        color_mask = @plot_colors[i].eq(color_name)
         | 
| 147 | 
            +
                        if @plot_units.nil?
         | 
| 148 | 
            +
                          begin
         | 
| 149 | 
            +
                          stat_data = group_data[color_mask].drop_na
         | 
| 150 | 
            +
                          rescue
         | 
| 151 | 
            +
                            @plot_data.each_with_index {|pd, k| p k => pd }
         | 
| 152 | 
            +
                            @plot_colors.each_with_index {|pc, k| p k => pc }
         | 
| 153 | 
            +
                            raise
         | 
| 154 | 
            +
                          end
         | 
| 155 | 
            +
                          unit_data = nil
         | 
| 156 | 
            +
                        else
         | 
| 157 | 
            +
                          # TODO: Support units
         | 
| 158 | 
            +
                        end
         | 
| 159 | 
            +
             | 
| 160 | 
            +
                        estimation = if stat_data.size == 0
         | 
| 161 | 
            +
                                       Float::NAN
         | 
| 162 | 
            +
                                     else
         | 
| 163 | 
            +
                                       estimate(estimator, stat_data)
         | 
| 164 | 
            +
                                     end
         | 
| 165 | 
            +
                        estimations[j] << estimation
         | 
| 166 | 
            +
             | 
| 167 | 
            +
                        unless ci.nil?
         | 
| 168 | 
            +
                          if stat_data.size < 2
         | 
| 169 | 
            +
                            conf_int[j] << [Float::NAN, Float::NAN]
         | 
| 170 | 
            +
                            next
         | 
| 171 | 
            +
                          end
         | 
| 172 | 
            +
             | 
| 173 | 
            +
                          if ci == :sd
         | 
| 174 | 
            +
                            sd = stat_data.stdev
         | 
| 175 | 
            +
                            conf_int[j] << [estimation - sd, estimation + sd]
         | 
| 176 | 
            +
                          else
         | 
| 177 | 
            +
                            conf_int[j] << Statistics.bootstrap_ci(stat_data, ci, func: estimator, n_boot: n_boot,
         | 
| 178 | 
            +
                                                                   units: unit_data, random: random)
         | 
| 179 | 
            +
                          end
         | 
| 180 | 
            +
                        end
         | 
| 181 | 
            +
                      end
         | 
| 182 | 
            +
                    end
         | 
| 183 | 
            +
             | 
| 184 | 
            +
                    @estimations = estimations
         | 
| 185 | 
            +
                    @conf_int = conf_int
         | 
| 186 | 
            +
                  end
         | 
| 187 | 
            +
             | 
| 188 | 
            +
                  private def estimate(estimator, data)
         | 
| 189 | 
            +
                    case estimator
         | 
| 190 | 
            +
                    when :count
         | 
| 191 | 
            +
                      data.length
         | 
| 192 | 
            +
                    when :mean
         | 
| 193 | 
            +
                      data.mean
         | 
| 194 | 
            +
                    else
         | 
| 195 | 
            +
                      # TODO: Support other estimations
         | 
| 196 | 
            +
                      raise NotImplementedError, "#{estimator} estimator is not supported yet"
         | 
| 197 | 
            +
                    end
         | 
| 16 198 | 
             
                  end
         | 
| 17 199 | 
             
                end
         | 
| 18 200 | 
             
              end
         |