| Top Page | プログラミング | Perl 目次 | prev | next | 索引 |

13. ハッシュの活用:柔軟なデータ集計

ハッシュを使って種類ごとに積算する

こんな売り上げデータがあるとします.

品番号  販売個数
1	1
2	4
1	5
3	12
2	3
1	10

各商品ごとの合計販売個数が知りたいとしたら, 品ごとの販売個数積算用の配列を用意して,こんなプログラムを書けばよいでしょう. 各品の積算販売数が,配列の各要素の値です. 品番号を配列の添え字(何番目の要素かの指定)に使っています.

$record = <>;       #  最初のヘッダ行を読み捨てる.

while ($record = <>) {     #  $line ばっかりもなんなんで,たまには $record とでも.
    chomp $record;
    ($item_id, $amount) = split /\s+/, $record;
    $total_amount[$item_id] += $amount;  #  販売数積算用の配列に,販売個数を足す.
}

print "品番号\t販売個数\n";    # ヘッダ行

for ($id = 1; $id <= 3; ++$id) {
    print $id, "\t", $total_amount[$id], "\n";
}

$total_amount[$item_id] += $amount;は $total_amount[$item_id] = $total_amount[$item_id] + $amount; と同じ意味,$total_amount[$item_id] は最初(その品番がはじめて出てきたとき) は未定義なので数値としてはゼロ,したがって0から順次積算していくことになります.

このプログラムに上のデータを処理させると,こんな結果が得られるでしょう.

品番号  販売個数
1       16
2       7
3       12

では,こんなデータだったらどうしましょう.関東地方の雑木林での木の調査結果 っぽいデータです.一本一本の木の位置(x座標とy座標), 高さ(m),DBH(人間の胸の高さでの直径, cm) の データです.

species          X       Y      height  dbh
エゴノキ        0.28    0.25    10.7    15.5
エゴノキ        0.59    1.63    12.8    19.4
アカシデ        0.50    2.31    2.5     2.6
シラカシ        0.02    3.71    14.0    21.7
アラカシ        0.87    4.73    3.9     4.4
アカシデ        0.62    5.67    11.9    17.6
...
コナラ          19.53   17.45   3.9     4.4
アラカシ        19.21   18.29   2.8     2.9
シラカシ        19.64   19.11   14.0    21.6

※練習用に400個体分のデータを用意しました(> こちらのページ). これを表示してそっくりコピーしてテキストファイルに貼り付けて, 適当な名前(たとえばforest.txt )で保存して使ってください. このデータはプログラムで作ったニセ調査結果です(少々木がこみすぎてます). データ作成用のプログラムも載せておきます (>gen_forest.pl).

こんなデータを解析するにはどうしたらいいでしょうか. 最初の例では,品番号をそのまま配列の添え字に使いました. 種名は文字列ですから,配列の添え字に使えません. そこでハッシュを利用します.

$record = <>;       #  最初のヘッダ行を読み捨てる.

while ($record = <>) {
    chomp $record;
    ($sp, $x, $y, $h, $dbh) = split /\s+/, $record;
    $count{$sp} += 1;  #  個体数積算用のハッシュに1を加える.
}

print "種名\t個体数\n";  # ヘッダ行

foreach $sp (keys %count) {
    print $sp, "\t", $count{$sp}, "\n";
}

各種の積算個体数が,ハッシュの各要素の値です. 種名の文字列を,ハッシュのキー(どの要素かの指定)に使っています.

$count{$sp} += 1; は $count{$sp} = $count{$sp} + 1; と同じ意味, $count{$sp} は最初(その種がはじめて出てきたとき)は未定義なので数値としてはゼロ, したがって0からはじめて順次積算していくことになります.

上のデータを処理すると,こんな出力が得られます.

種名    個体数
コナラ  106
エゴノキ        64
シラカシ        63
クリ    25
クヌギ  79
アラカシ        31
アカシデ        32

タブの文字送りのためにがたがたしてますが,気にしないでください.

※気になるようなら,出力で printf を使って,種名の文字幅を指定します. たとえば printf "%-16s%d\n", $sp, $count{$sp}; と書けば,16字 (いわゆる全角で8文字)の幅が文字列出力用に確保され,そこに左寄せ (書式指定中で 16 の前にマイナスをつけて指定している)で種名を出力し, そのあと整数型で個体数が出力されます.

ハッシュを使えば,1から始まる品番号のようなつごうのよいデータでなくとも, 任意の文字列データをもとにデータの仕訳けをして集計できます. もちろん,最初の例のデータでも,品番を文字列データとして扱ってハッシュで 処理してよいのです.ハッシュなら,品番 50012 番と 9910 番と 71001番の 販売データでも困りません.

さらに一手間かけて,個体数の多いものから順に出力するようにしてみましょう. ハッシュの各要素を,キーではなく値でソートして出力するというワザは 前のページの終わりのほう で出てきました.

$record = <>;       #  最初のヘッダ行を読み捨てる.

while ($record = <>) {
    chomp $record;
    ($sp, $x, $y, $h, $dbh) = split /\s+/, $record;
    $count{$sp} += 1;  #  個体数積算用のハッシュに1を加える.
}

print "種名\t個体数\n";  # ヘッダ行

# 個体数の降順(多いほうから少ないほうへ)で種名と個体数を表示.
foreach $sp (sort {$count{$b} <=> $count{$a} } keys %count) {
    print $sp, "\t", $count{$sp}, "\n";
}

sort のあとのブロック中での,$a と $b の順序を逆にすれば個体数の昇順に 並びます.


'種類ごとの○○'を柔軟に

ハッシュを使うと,ハッシュのキーになるデータごとにいろいろな計算をすることが 容易にできます.たとえば 木の材積(幹の体積)の目安として,(幹の太さの二乗×高さ)という量が よく使われるのですが,これを種ごとに積算したかったら,上のプログラムで 個体数を積算するために $count{$sp} += 1; としているところ, $cum_d2h{$sp} += $h * ($dbh / 100) ** 2; とすればよいでしょう ($dbh を 100 で割っているのは単位をメートルに揃えるため).

練習

※途中から,練習は読者任せにしてしまってますが,読むだけではいまひとつ 納得できないところはぜひ実際に動かして確認してください. ハッシュはなんとなく分かった気になっても,自分のプログラムの中で どう活用するか,最初はなかなかつかみにくいものです. ぜひ試してみることをすすめます.

もう一例.樹種ごとの最大樹高を求めてみましょう.

$record = <>;       #  最初のヘッダ行を読み捨てる.

while ($record = <>) {
    chomp $record;
    ($sp, $x, $y, $h, $dbh) = split /\s+/, $record;
    if ($max_height{$sp} < $h ) {  # この種の,これまでの最大樹高より高かったら,
        $max_height{$sp} = $h;     # この個体の高さを最大樹高に代入
    }
}

print "種名\t最大樹高\n";  # ヘッダ行

foreach $sp (keys %max_height) {
    print $sp, "\t", $max_height{$sp}, "\n";
}

%max_height は,各樹種の名前をキー,最大樹高を値とするハッシュです.

$max_height{$sp} は最初(その種がはじめて出てきたとき)は未定義なので数値としてはゼロ, どんな高さの個体でもゼロよりは高いので,その個体の高さがその種の最大樹高として $max_height{$sp} に代入されます.以降は,記録が更新されるたびに $max_height{$sp} が書き換えられていきます.


次のページでも,この森の木の調査データを題材にした解析を続けます.


| Top Page | プログラミング | Perl 目次 | prev | next |