広告 プログラミング言語-Perl 統計学を理解するためのヒント

12 国の統計情報を用いて、メタ分析用データを作る

これは統計分析システムRの活用方法を解説する記事のために、リアルな子供の人口動態を解析したら面白いかなと思って作りました。Rにはirisデータセットという便利な基礎データが付いてるんですが、それを使った解説記事は沢山あるので、面白みが無いんですよね~!

日本政府統計ポータルサイトより2023年の5~17歳の身長と体重を抽出

このスクリプトは、以下の2つのサイトで入手できるExcelファイルを元に作成しました。

政府統計コード	00400002	
調査の概要:
 学校保健統計調査は、学校における幼児、児童及び生徒の発育、健康等の状態を明らかにすることを目的としています。本調査は、幼稚園、幼保連携型認定こども園、小学校、中学校、義務教育学校、高等学校及び中等教育学校の幼児、児童及び生徒を対象に、毎年実施されます。学校保健安全法により義務づけられている健康診断の結果に基づいて、発育及び健康状態に関する事項(身長、体重及び被患率等)に関する調査を行っており、その結果は、学校保健安全法及び学校給食法の改正をはじめとした学校保健行政の施策の立案検討の際の基礎資料としてだけでなく、我が国の学校保健に関する基礎資料として、各方面で活用されています。	
提供統計名:学校保健統計調査	
提供分類1:令和5年度	
統計表名:身長と体重の相関表及び身長別体重の平均値

学校保健統計調査:令和5年度身長と体重

政府統計コード:00200524 	
調査の概要:
 人口推計は、国勢調査による人口を基に、その後の各月における出生・死亡、入国・出国などの人口の動きを他の人口関連資料から得ることで、毎月1日現在の男女別、年齢階級別の人口を推計しています。また、毎年10月1日現在の全国各歳別結果及び都道府県別結果も推計しています。
 推計結果は、各種白書や国際機関における人口分析、経済分析等の基礎資料として利用されています。
	
提供統計名:人口推計 	
提供分類1:各年10月1日現在人口 		
表分類:	全国 	
統計表名:年齢(各歳)、男女別人口及び人口性比-総人口、日本人人口(2023年10月1日現在) 

2023年 年齢性別の人口推計

人口推計は、国勢調査による悉皆(しっかい)調査のデータを元に得られた推計値で、殆どの人口関連資料の元になっています。

学校保健統計調査は、5歳~17歳の、学校等に通学している方のデータで、5歳の男児1000人に対する割合になっているわけです。Excelシートが、性別、年齢別に、横軸が体重、縦軸が身長の、‰(パーミル)値になっています。

つまり、各年齢、性別毎の人口値で補正しないといけないわけですね。また、通学していない方のデータは含まれません。(身長・体重のデータを集めようがない)

統計用データ作成Perlスクリプト

ということで、作成したperlスクリプトです。各年齢・性別毎の人口については、内部変数に埋め込みました。(手抜き)

学校保健調査のExcelシートは膨大なので、PerlからExcelをOLEで起動して、各シートのデータをテキストデータとして抽出してから、分析可能な生データを作成しています。

例えば、5歳の男児で、身長100cm、体重15kgの子は2.6‰なので、5歳の男児の人口468(千人)×2.6/1000=1.22≒1人(四捨五入)って感じで人数を割り出し、その人数分だけレコードを作るわけです。このように作業することで、統計分析可能なメタデータが作成できます。
(ただ、あまりにも階級が細かすぎて度数が0.1と小さく、データの欠落が目立ったので、今回は50で割りました。度数0.1=1人になる計算です。これ以上データ量を増やしたら、Excelがデータを読み込めませんでした。結局、26万人のデータになりました。)

もし、平均値と標準偏差しか示されていない場合は、乱数等で正規分布シミュレートをしないといけませんが、このデータはかなり細かい階級値になっていたので、単純に同じ身長・体重のレコード数を人数分作るだけで済みました。(それでもかなり大変なんですよ。)

完成したスクリプトは、あっという間にデータを作ってくれます。一応、各シートのデータの取りこぼしが無いかを確認するためのタブ区切りテキストも作成するようになってます。

一応、Excelファイル名「r5_hoken_sanko_01.xlsx」がある所と同じフォルダでスクリプトを実行すると、統計解析ソフトRで扱える「e-stat_hw_2023.csv」ファイルが、数秒で出力されます。2023年度以外のデータを処理したい場合は、スクリプト内のTAB区切りテキストで埋め込んである、人口推計データを差し替えないといけません。

Excelファイルから取り込むように改造出来る方は、是非、チャレンジしてみてね。

#!/usr/bin/perl

# 2003年の学校保健調査より、メタ分析用のデータを作成する。

#必ずソースをUTF-8で記載すること。

use strict;
use Encode;
#use Encode::JP::H2Z;
use utf8;

use Win32::OLE qw(in with);
use Win32::OLE::Const 'Microsoft Excel';
use Win32::OLE::Enum;

binmode STDIN, ":encoding(cp932)";   # 入力なのに、何故かencoding
binmode STDOUT, ":encoding(cp932)";
binmode STDERR, ":encoding(cp932)";
# ファイルハンドル<IN>等からの入力時はdecodeが必要
# ファイルハンドル<OUT>等への出力時はencodeが必要

our $debug=1;

#=================
# 2023年の年齢性別人口(埋め込み。 手抜きです。)
#Age	M	F
my $population=
'0	388	369
1	409	388
2	423	404
3	427	408
4	446	425
5	468	447
6	480	459
7	502	477
8	514	490
9	514	488
10	525	501
11	527	503
12	541	514
13	546	519
14	548	521
15	559	532
16	556	528
17	551	524
18	559	530
19	593	562
20	606	569
21	632	595
22	653	616
23	663	622
24	657	622
25	668	631
26	665	629
27	664	626
28	670	632
29	666	627
30	650	612
31	653	617
32	647	614
33	657	626
34	667	637
35	687	655
36	702	673
37	712	682
38	734	710
39	758	734
40	767	744
41	766	746
42	773	751
43	806	784
44	826	802
45	856	833
46	878	854
47	920	894
48	956	931
49	1010	983
50	1023	1003
51	1006	983
52	974	956
53	942	927
54	924	914
55	900	891
56	895	889
57	695	697
58	855	856
59	799	802
60	776	782
61	748	757
62	729	742
63	728	748
64	738	760
65	711	740
66	687	718
67	715	754
68	732	776
69	723	776
70	756	824
71	788	868
72	822	915
73	864	981
74	931	1068
75	900	1052
76	836	990
77	502	615
78	518	657
79	610	795
80	570	757
81	562	765
82	520	727
83	445	645
84	361	543
85	355	561
86	334	552
87	301	526
88	252	474
89	206	418
90	176	386
91	144	340
92	113	293
93	85	242
94	65	203
95	48	166
96	31	117
97	21	89
98	13	63
99	8	43
100	11	77';

#=================

our %Population=(); # 人口DBを連想配列で作成。性別と年齢をキーに人口を返す

for my $d (split(/\n/,$population)) {
    my ($Age,$M,$F)=split(/\t/,$d);
    $Population{'M'.$Age}=$M;
    $Population{'F'.$Age}=$F;
}



our $ExcelDir=$0; #実行中のplスクリプトフルパス
$ExcelDir=&de_sjis($ExcelDir);
$ExcelDir=~s/[^\\]+\.pl$//;

&vv('$ExcelDir',$ExcelDir);

# Excel オブジェクトはグローバル変数として宣言し取得。(そうしないと何個もExcelが開くので)
our $Excel=Win32::OLE->GetActiveObject('Excel.Application') || Win32::OLE->new('Excel.Application','Quit');

#Excel ファイルは絶対pathでないと開かないようです。


#取り合えず2023年のデータだけ。(余りにも膨大になるので)
open(OUT,'>e-stat_hw_2023.csv');

print OUT 'Year,Sex,Age,Height,Weight',"\n";

#2023年の、各年齢性別毎の身長と体重の度数(‰)なので、人口による補正が必要。
&excel2text($ExcelDir,"r5_hoken_sanko_01.xlsx");

$Excel->Quit();
close(OUT);

print '[Finish!]',"\n";
<>;
exit;


sub excel2text
{
    my ($ExcelDir,$ExFileName)=@_;

    print 'ExFileName=[',$ExcelDir.$ExFileName,']',"\n";
    
    my $FNameHead=$ExFileName;
    $FNameHead=~s/\.(xls|xlsx)$//;
    &vv('$FNameHead',$FNameHead);

    my $Book=$Excel->Workbooks->Open(&en_sjis($ExcelDir.$ExFileName));
    print 'BookName=[',&de_sjis($Book->Name),']',"\n";

    my $SheetCnt=$Book->WorkSheets->Count;
    print 'SheetCnt=[',$SheetCnt,']',"\n";

    for (my $i=1;$i<=$SheetCnt;$i++) {     # 全シート対象

        my $Sheet_TSV='';    # Tab区切りテキストのイメージを格納する変数

        my $Sheet=$Book->Worksheets($i);
        my $SheetName=$Sheet->{'Name'};
        $SheetName=&de_sjis($SheetName);
        print 'SheetName=[',$SheetName,']',"\n";

        my $MaxRow = $Sheet->Cells->SpecialCells(xlCellTypeLastCell)->{'Row'};
        my $MaxCol = $Sheet->Cells->SpecialCells(xlCellTypeLastCell)->{'Column'};
        print 'MaxRow=[',$MaxRow,']',"\n";
        print 'MaxCol=[',$MaxCol,']',"\n";

        for (my $row=1; $row<=$MaxRow+1; $row++) {
            for (my $col=1; $col<=$MaxCol+1; $col++) {
                my $str1=$Sheet->Cells($row,$col)->{'Value'};
                $str1=&de_sjis($str1);      # CP932からperl内部コードへデコード
                $str1=~s/(\t|\r|\n)//g;     # Tabや改行文字を削除する
                $Sheet_TSV.=$str1."\t";     # Tab区切りテキストとして保存
            }
            chop($Sheet_TSV);   # 最後に足したTABを捨てる
            $Sheet_TSV.="\n";   # 最後に改行コードを追加。
        }

        &out_meta_data($Sheet_TSV,'2023');
    }

    $Excel->{DisplayAlerts} = 'False';
    $Excel->Workbooks->close();
    close(OUT2);
}

print '[Finish!]',"\n";
<>;
exit;

#---------------------------
# $Sheet_TSV のイメージ

#1 身長と体重の相関表及び身長別体重の平均値(26-1)										
#男-5歳		(‰)	(‰)	(‰)	(‰)	・・・	(‰)	(‰)	(kg)	(kg)
#		体重	体重	体重	体重	・・・	体重	体重	体重	体重
#		計	~11kg	12kg	13kg	・・・	36kg	37kg~	平均値	標準偏差
#身長	計	1000.0	0.0	0.2	2.2	・・・	0.3	0.5	19.2	2.75
#身長	~93cm	0.5	0.0	-	0.2	・・・	-	-	14.2	1.45
#身長	94cm	0.2	-	0.0	0.1	・・・	-	-	13.6	1.44
#身長	95cm	0.3	-	-	0.1	・・・	-	-	13.7	0.53
#・・・										
#身長	129cm	0.2								
#身長	130cm~	0.3								

sub out_meta_data
{
    my($ds,$Year)=@_;
    # $ds には上記TSVイメージが入っている。
    my @ds=split(/\n/,$ds);
    
    #性別と年齢
    &vv('$ds[1]',$ds[1]);
    
    my ($sex_age)=split(/\t/,$ds[1]);
    my ($Sex,$Age)=();
    $sex_age=~s/(男|女)\D+(\d+)歳//;
    $Sex=$1;
    $Age=$2;
    if ($Sex eq '男') {
        $Sex='M';
    } else {
        $Sex='F';
    }
    &vv('$Sex',$Sex);
    &vv('$Age',$Age);
    
    open(OUT2,'>'.$Year.$Sex.$Age.'.txt');    # デバッグ用にテキストに吐く

    # 体重を拾う
    &vv('$ds[3]',$ds[3]);
    
    my ($title,$height_org,@weight_org)=split(/\t/,$ds[3]);# index 3 行目に体重データあり 最初の2カラムを捨てる
    my @weight = map { $_ =~ s/\D*//g; $_ } @weight_org;  # 数値以外を削除
    &vv('@weight',join(' ',@weight));
#    <>;

    my $i=5;    # データは5行目からスタート
    my $continue_flug=1;
    while($continue_flug) {
        # 身長の値を拾う
        my ($title,$height_org,@dosuu_org)=split(/\t/,$ds[$i]); # 最初の2カラムを捨てる(@weightにindexを合わせる)
        my @dosuu= map { $_ =~ s/-/0/; $_ } @dosuu_org; # マイナスを0に置換
        
        my $Height=$height_org;
        $Height=~s/\D*//g;   # 数値以外を削除
        &vv('$Height',$Height);
        $Height+=0; # 強制的に数値にする。NULLが0になる。

        if ($Height == 0 ) {
            $continue_flug=0;
            last;
        }

        for (my $i2=3;$i2<=$#weight;$i2++) {
            &vv('$weight[$i2]',$weight[$i2]);
            my $Weight=$weight[$i2];
            
            my $Dosuu1000=0;
            if ($Weight>0) {  # 「平均値」「標準偏差」等の体重データが無い所のデータを捨てる
                $Dosuu1000=int($Population{$Sex.$Age}*$dosuu[$i2]/50+0.5); #各性別年齢の人口に度数を掛け四捨五入(0.1=1人になるよう50で割った)
                if ($Dosuu1000 > 0) {    # 度数が整数で存在する場合のみ
                    &vv('$Year',$Year);
                    &vv('$Sex',$Sex);
                    &vv('$Age',$Age);
                    &vv('$Height',$Height);
                    &vv('$Weight',$Weight);
                    &vv('$Dosuu1000',$Dosuu1000);
                    print OUT2 &en_sjis(join (',',$Year,$Sex,$Age,$Height,$Weight,$Dosuu1000)."\n"); # デバッグ用のTSV
#<>;
                    for (my $i3=1;$i3<=$Dosuu1000;$i3++) {
                        print OUT &en_sjis(join (',',$Year,$Sex,$Age,$Height,$Weight)."\n");
                    }
                }
            }
        }
        $i++;
    }
    close(OUT2);
}






#===============================
sub en_jis
{
    my ($buf)=@_;
    encode('iso-2022-jp',$buf);
}

sub de_jis
{
    my ($buf)=@_;
    decode('iso-2022-jp',$buf);
}

sub en_sjis
{
    my ($buf)=@_;
    encode('cp932',$buf);
}

sub de_sjis
{
    my ($buf)=@_;
    decode('cp932',$buf);
}

sub en_utf8
{
    my ($buf)=@_;
    encode('utf-8',$buf);
}

sub de_utf8
{
    my ($buf)=@_;
    decode('utf-8',$buf);
}

sub en_euc
{
    my ($buf)=@_;
    encode('euc-jp',$buf);
}

sub de_euc
{
    my ($buf)=@_;
    decode('euc-jp',$buf);
}

sub vv
{
    my($Name,$Value)=@_;
    if($debug) {
        print $Name.'=['.$Value.']',"\n";
    }
}

1;

-プログラミング言語-Perl, 統計学を理解するためのヒント