【问题标题】:How to create a histogram from a flat Array in Ruby [closed]如何从 Ruby 中的平面数组创建直方图 [关闭]
【发布时间】:2013-10-06 17:26:00
【问题描述】:

如何创建整数数组的直方图?例如:

data = [0,1,2,2,2,2,2,3,3,3,3,3,3,4,4,4,4,5,5,6,6,6,7,7,7,7,7,8,9,9,10]

我想根据0、1、2 等的条目数创建一个直方图。有没有简单的方法在 Ruby 中做到这一点?

输出应该是两个数组。第一个数组应该包含组(bins),第二个数组应该包含出现次数(频率)。

对于上面给出的data,我希望得到以下输出:

bins         # => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
frequencies  # => [1, 1, 5, 6, 4, 2, 3, 5, 1, 2, 1]

【问题讨论】:

  • 你想要的输出格式是什么?
  • 当您提出问题、索取代码时,您需要展示您的研究成果以及您为解决问题所做的任何尝试,以及您解释它们为什么不起作用的原因。
  • 我会初始化一个类似h = Hash.new(0) 的“计数哈希”并计算每个元素的出现次数:data.each{|v| h[v] += 1}。之后,h 将如下所示:=> {0=>1, 1=>1, 2=>5, 3=>6, 4=>4, 5=>2, 6=>3, 7=>5, 8=>1, 9=>2, 10=>1} 您可以使用h.keys 和h.values 从该哈希中提取箱和频率。我希望你觉得这很有用。
  • Ruby 2.7.0 引入Enumerable#tally: data.tally => {0=>1, 1=>1, 2=>5, 3=>6, 4=>4, 5=>2, 6=>3, 7=>5, 8=>1, 9=>2, 10=>1}

标签: ruby histogram


【解决方案1】:

使用“histogram”。

data = [0,1,2,2,2,2,2,3,3,3,3,3,3,4,4,4,4,5,5,6,6,6,7,7,7,7,7,8,9,9,10]
(bins, freqs) = data.histogram 

这将创建一个包含直方图箱的数组bins 和包含频率的数组freqs。 gem 还支持不同的分箱行为和权重/分数。

希望这会有所帮助。

【讨论】:

    【解决方案2】:

    Ruby 的 Array 继承了 group_by from Enumerable,这很好:

    Hash[*data.group_by{ |v| v }.flat_map{ |k, v| [k, v.size] }]
    

    返回:

    {
         0 => 1,
         1 => 1,
         2 => 5,
         3 => 6,
         4 => 4,
         5 => 2,
         6 => 3,
         7 => 5,
         8 => 1,
         9 => 2,
        10 => 1
    }
    

    这只是一个不错的'n clean hash。如果你想要一个包含每个 bin 和频率对的数组,你可以缩短它并使用:

    data = [0,1,2,2,3,3,3,4]
    data.group_by{ |v| v }.map{ |k, v| [k, v.size] }
    # => [[0, 1], [1, 1], [2, 2], [3, 3], [4, 1]]
    

    下面是代码和group_by 对较小数据集所做的事情:

    data.group_by{ |v| v }    
    # => {0=>[0], 1=>[1], 2=>[2, 2], 3=>[3, 3, 3], 4=>[4]}
    
    data.group_by{ |v| v }.flat_map{ |k, v| [k, v.size] }  
    # => [0, 1, 1, 1, 2, 2, 3, 3, 4, 1]
    

    正如 Telmo Costa 在 cmets 中提到的,Ruby 在 v2.7.0 中引入了tally。运行快速基准测试表明tally 快了大约 3 倍:

    require 'fruity'
    
    puts "Ruby v#{RUBY_VERSION}"
    
    data = [0,1,2,2,2,2,2,3,3,3,3,3,3,4,4,4,4,5,5,6,6,6,7,7,7,7,7,8,9,9,10]
    
    data.group_by{ |v| v }.map{ |k, v| [k, v.size] }.to_h
    # => {0=>1, 1=>1, 2=>5, 3=>6, 4=>4, 5=>2, 6=>3, 7=>5, 8=>1, 9=>2, 10=>1}
    data.group_by { |v| v }.transform_values(&:size)
    # => {0=>1, 1=>1, 2=>5, 3=>6, 4=>4, 5=>2, 6=>3, 7=>5, 8=>1, 9=>2, 10=>1}
    data.tally 
    # => {0=>1, 1=>1, 2=>5, 3=>6, 4=>4, 5=>2, 6=>3, 7=>5, 8=>1, 9=>2, 10=>1}
    data.group_by{ |v| v }.keys.sort.map { |key| [key, data.group_by{ |v| v }[key].size] }.to_h
    # => {0=>1, 1=>1, 2=>5, 3=>6, 4=>4, 5=>2, 6=>3, 7=>5, 8=>1, 9=>2, 10=>1}
    
    compare do
      gb { data.group_by{ |v| v }.map{ |k, v| [k, v.size] }.to_h }
      rriemann { data.group_by { |v| v }.transform_values(&:size) }
      telmo_costa { data.tally }
      CBK {data.group_by{ |v| v }.keys.sort.map { |key| [key, data.group_by{ |v| v }[key].size] }.to_h }
    end
    

    导致:

    # >> Ruby v2.7.0
    # >> Running each test 1024 times. Test will take about 2 seconds.
    # >> telmo_costa is faster than rriemann by 2x ± 0.1
    # >> rriemann is similar to gb
    # >> gb is faster than CBK by 8x ± 1.0
    

    所以使用tally。

    【讨论】:

    • 最新的 ruby​​ 版本允许使用一些语法糖来缩短版本:data = group_by { |v| v }.transform_values(&:size)
    • 这应该是公认的答案!
    • 你可以使用itself:data.group_by(&:itself).transform_values(&:size)。或者,之前有没有说过,从 Ruby 2.7.0 开始data.tally。
    • 按升序对键进行排序:data.group_by{ |v| v }.keys.sort.map do |key| [key, data.group_by{ |v| v }[key].size] end
    • 不,不要。如果您使用 OPs 样本数组,它至少会慢 8 倍。查看基准。
    猜你喜欢
    • 2015-08-26
    • 1970-01-01
    • 1970-01-01
    • 2018-03-11
    • 1970-01-01
    • 2016-04-25
    • 1970-01-01
    • 1970-01-01
    • 2015-05-30
    相关资源
    最近更新 更多