web-dev-qa-db-ja.com

Ruby)のフラット配列からヒストグラムを作成する方法

整数の配列のヒストグラムを作成するにはどうすればよいですか?例えば:

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]

012などのエントリ数に基づいてヒストグラムを作成したいと思います。 Rubyでそれを行う簡単な方法はありますか?

出力は2つの配列である必要があります。最初の配列にはグループ(ビン)が含まれ、2番目の配列には出現回数(頻度)が含まれている必要があります。

上記の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]
21
Whitecat

" 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は、さまざまなビニング動作と重み/分数もサポートしています。

お役に立てれば。

9
Rahul Jiresal

Rubyの配列は 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クリーンハッシュです。各ビンと周波数ペアの配列が必要な場合は、それを短くして使用できます。

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がコメントで述べたように、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 を使用します。

49
the Tin Man