SASマクロ 集計

【SAS】サクッと複数の条件を組み合わせたクロス集計(割合)をする

今度は、【SAS】サクッと複数の条件を組み合わせたクロス集計(件数)をする、の割合バージョンになります。

割合に関しては、行方向(表側)、または、列方向(表頭)のどちらのN数を分母に取るかで、出力レイアウトが変わります。

サクッとクロス集計の割合を算出したい人向けのマクロです。

1.サンプルデータ

サンプルとして、以下の外部ファイル読み込みマクロで読み込んだ「国勢調査令和2年人口集計」を使用してみます。


 
読み込んだデータ「list_population」は以下になり、性、年代、都道府県の人口分布になっています。
tab_sasdata

 

2.展開例

・行方向(表側)を分母にして算出

行方向(表側)のN数を分母にする場合は、引数bunbo=rを指定します。

%cross_rate(
	indata			=list_population,
	sibori			=8000 <= area <= 14000,
	weight			=population,
	bunbo			=r,
	round			=,
	percent			=,
	row				=area=8000	# area=9000	# area=10000	# area=11000	# area=12000	# area=13000	# area=14000,
	row_label		=茨城県		# 栃木県	# 群馬県		# 埼玉県		# 千葉県		# 東京都		# 神奈川県,
	col				=sex=1	# sex=2,
	col_label		=男		# 女, 
	prefix			=out,
	outdata			=cross_rate_weight_row
);

下図のように、行方向(表側)のN数が分母になり、男と女の合計が100パーセントになっています。
cross_rate_weight_row
 

・行方向(表側)を分母にして算出し、四捨五入する

四捨五入する場合は、引数roundに、小数点以下何桁に丸めるかを指定します。
以下はround=0.01を指定しているので、小数点以下2桁に丸めています。

%cross_rate(
	indata			=list_population,
	sibori			=8000 <= area <= 14000,
	weight			=population,
	bunbo			=r,
	round			=0.01,
	percent			=,
	row				=area=8000	# area=9000	# area=10000	# area=11000	# area=12000	# area=13000	# area=14000,
	row_label		=茨城県		# 栃木県	# 群馬県		# 埼玉県		# 千葉県		# 東京都		# 神奈川県,
	col				=sex=1	# sex=2,
	col_label		=男		# 女, 
	prefix			=out,
	outdata			=cross_rate_weight_row_round
);

以下のように、先ほどと違って、割合が四捨五入されています。
偶数丸め(銀行丸め)などではなく、普通の四捨五入なので、合計が約100パーセントになります。
cross_rate_weight_row_round
 

・列方向(表頭)を分母にして算出し、四捨五入して、パーセント表示にする

今度は列方向のN数を分母にするため、引数bunbo=cを指定しています。
四捨五入後にパーセント表示にするため、引数percent=Yを指定しています。

%cross_rate(
	indata			=list_population,
	sibori			=8000 <= area <= 14000,
	weight			=population,
	bunbo			=c,
	round			=0.01,
	percent			=Y,
	row				=area=8000	# area=9000	# area=10000	# area=11000	# area=12000	# area=13000 and age>=sum(10, 20)	# area=14000 ,
	row_label		=茨城県		# 栃木県	# 群馬県		# 埼玉県		# 千葉県		# 東京都30歳以上					# 神奈川県,
	col				=sex in (1 2)	# . < age < 20,
	col_label		=男女合計		# 未成年, 
	prefix			=out,
	outdata			=cross_rate_weight_col_round
);

以下のように、列方向(表頭)のN数が分母になり、100パーセント表示に変わりました。
cross_rate_weight_col_round
 

3.参考プログラム

以下は、参考プログラムになります。
右上のコピーボタンを押せば、プログラム全体をコピーできます。

%macro cross_rate(indata=, sibori=, weight=, bunbo=, round=, percent=, row=, row_label=, col=, col_label=, prefix=out, outdata=);
%put --------------------------------------------------;
%put cross_rate;	/*クロス集計(割合)*/
%put &=indata;		/*入力データセットを指定(データセットオプションの指定可)*/
%put &=sibori;		/*読込みデータの絞り条件(省略可)*/
%put &=weight;		/*ウェイト用変数を指定(省略した場合はレコード数の割合を算出する)*/
%put &=bunbo;		/*行方向(表側)を分母にする場合はr、列方向(表頭)を分母にする場合はcを指定し、省略した場合は読込んだレコード数が分母となる*/
%put &=round;		/*例えば、小数点以下3桁目を四捨五入して2桁にしたい場合は0.01を指定し、省略した場合は四捨五入なしとなる*/
%put &=percent;		/*パーセント表示にする場合はYを指定*/
%put &=row;			/*表側条件を#で区切って指定(省略可)*/
%put &=row_label;	/*表側ラベルを#で区切って指定(省略可)*/
%put &=col;			/*表頭条件を#で区切って指定し(省略可)*/
%put &=col_label;	/*表頭ラベルを#で区切って指定(省略可)*/
%put &=prefix;		/*出力変数の接頭辞を指定(既定値=out)*/
%put &=outdata;		/*出力データセットを指定(データセットオプションの指定可)*/
%put --------------------------------------------------;

	/*ローカルマクロ変数の定義*/
	%local
		_row_len
		_row_label_len
		_row_num
		_row_label_num
		_r
		_c
	;

	/*表側数の算出*/
	%let _row_len		=%length(%superq(row));
	%let _row_label_len	=%length(%superq(row_label));
	%if &_row_len. > 0 %then %do;
		%let _row_num=%eval(%sysfunc(count(%superq(row), #)) + 1);
	%end;
	%else %let _row_num=0;

	%if &_row_label_len. > 0 %then %do;
		%let _row_label_num=%eval(%sysfunc(count(%superq(row_label), #)) + 1);
	%end;
	%else %let _row_label_num=0;

	%if &_row_len. > 0 and &_row_label_len. > 0 and &_row_num. ne &_row_label_num. %then %do;
		%put WARNING:指定した表側条件と表側ラベルの数が異なっています;
	%end;

	%put ----- 指定した表側数チェック -----;
	%put &=_row_num;
	%put &=_row_label_num;
	%put;

	/*表頭数の算出*/
	%let _col_len		=%length(%superq(col));
	%let _col_label_len	=%length(%superq(col_label));
	%if &_col_len. > 0 %then %do;
		%let _col_num=%eval(%sysfunc(count(%superq(col), #)) + 1);
	%end;
	%else %let _col_num=0;

	%if &_col_label_len. > 0 %then %do;
		%let _col_label_num=%eval(%sysfunc(count(%superq(col_label), #)) + 1);
	%end;
	%else %let _col_label_num=0;

	%if &_col_len. > 0 and &_col_label_len. > 0 and &_col_num. ne &_col_label_num. %then %do;
		%put WARNING:指定した表頭条件と表頭ラベルの数が異なっています;
	%end;

	%put ----- 指定した表頭数チェック -----;
	%put &=_col_num;
	%put &=_col_label_num;
	%put;

	/*N数(全体)の追加*/
	%let _row_num		=%eval(&_row_num. + 1);
	%let _row_label_num	=%eval(&_row_label_num. + 1);
	%let _col_num		=%eval(&_col_num. + 1);
	%let _col_label_num	=%eval(&_col_label_num. + 1);
	%let row			=%str() # %superq(row);
	%if %index(&bunbo., r) > 0 or %length(&bunbo.)=0 %then	%let row_label=全体 # %superq(row_label);
	%else							 	  					%let row_label=N数 # %superq(row_label);
	%let col=%str() # %superq(col);
	%if %index(&bunbo., c) > 0 or %length(&bunbo.)=0 %then	%let col_label=全体 # %superq(col_label);
	%else 							 	  					%let col_label=N数 # %superq(col_label);
	
	/*データの読込み*/
	data _cross_rate_cemp;
		set &indata.;

		/*絞り条件*/
		%if %length(%superq(sibori)) > 0 %then %do;
			if &sibori.;
		%end;

		/*カウント用変数の作成*/
		%if %length(&weight.) > 0 %then %do;
			if not missing(&weight.) then _count=&weight.;
			else _count=0;
		%end;
		%else %do;
			_count=1;
		%end;
	run;

	/*読込んだレコード数*/
	proc sql noprint; 
		select sum(_count) into:_n_all from _cross_rate_cemp;
	quit;
	%put ----- 読込んだレコード数チェック -----;
	%put &=_n_all;
	%put;

	/*集計*/
	%do _r=1 %to &_row_num.;

		/*表側条件ごとの絞り込み*/
		data _cross_rate_cemp2;
			set _cross_rate_cemp;
			%if %qscan(%superq(row), &_r., #) ne %str() %then %do;
				if %scan(%superq(row), &_r., #);
			%end;
		run;

		%if &sysnobs. > 0 %then %do;
			data _cross_rate_sum&_r.;
				keep
					joken
					label
					_num1 - _num&_col_num.
				;
				set _cross_rate_cemp2 end=eof;

				length joken label $1000.;
				%if %qscan(%superq(row), &_r., #) ne %str() %then %do;
					joken=strip("%qscan(%superq(row), &_r., #)");
				%end;
				%else %do;
					joken="";
				%end;

				%if %qscan(%superq(row_label), &_r., #) ne %str() %then %do;
					label=strip("%qscan(%superq(row_label), &_r., #)");
				%end;
				%else %do;
					label="";
				%end;

				%do _c=1 %to &_col_num.;
					%if %qscan(%superq(col), &_c., #) ne %str() %then %do;
						if %scan(%superq(col), &_c., #) then _num&_c. + _count;
					%end;
					%else %do;
						_num&_c. + _count;
					%end;
				%end;
				if eof;
			run;

		%end;
		%else %do;
			/*全レコード数が0の場合はダミーデータを作成する*/
			data _cross_rate_sum&_r.;
				length joken label $1000.;
				%if %qscan(%superq(row), &_r., #) ne %str() %then %do;
					joken=strip("%qscan(%superq(row), &_r., #)");
				%end;
				%else %do;
					joken="";
				%end;

				%if %qscan(%superq(row_label), &_r., #) ne %str() %then %do;
					label=strip("%qscan(%superq(row_label), &_r., #)");
				%end;
				%else %do;
					label="";
				%end;

				%do _c=1 %to &_col_num.;
					_num&_c. = 0;
				%end;
			run;
		%end;

	%end;

	/*統合して出力*/
	data &outdata.;
		keep
			no
			joken
			label
			%do _c=1 %to &_col_num.;
				&prefix.&_c.
			%end;
		;
		label
			no="No"
			joken="表側条件"
			label="表側ラベル"

			%if %index(&bunbo., c) > 0 %then %do;
				%if &_col_label_len. > 0 %then %do;
					%do _c=1 %to &_col_label_num.;
						%if %qscan(%superq(col_label), &_c., #) ne %str() %then %do;
							&prefix.&_c.="%qscan(%superq(col_label), &_c., #)"
						%end;
						%else %do;
							&prefix.&_c.=""
						%end;
					%end;
				%end;
				%else %if &_col_len. > 0 %then %do;
					%do _c=1 %to &_col_num.;
						%if %qscan(%superq(col), &_c., #) ne %str() %then %do;
							&prefix.&_c.="%qscan(%superq(col), &_c., #)"
						%end;
						%else %do;
							&prefix.&_c.=""
						%end;
					%end;				
				%end;
			%end;
			%else %do;
				&prefix.1="%qscan(%superq(col_label), 1, #)"
				%if &_col_label_len. > 0 %then %do;
					%do _c=2 %to &_col_label_num.;
						%if %qscan(%superq(col_label), &_c., #) ne %str() %then %do;
							&prefix.&_c.="%qscan(%superq(col_label), &_c., #)"
						%end;
						%else %do;
							&prefix.&_c.=""
						%end;
					%end;
				%end;
				%else %if &_col_len. > 0 %then %do;
					%do _c=2 %to &_col_num.;
						%if %qscan(%superq(col), &_c., #) ne %str() %then %do;
							&prefix.&_c.="%qscan(%superq(col), &_c., #)"
						%end;
						%else %do;
							&prefix.&_c.=""
						%end;
					%end;				
				%end;
			%end;
		;
		/*N数列を追加した分をリネームしてずらす*/
		rename
			&prefix.1=all
			%do _c=2 %to &_col_num.;	
				&prefix.&_c.=&prefix.%eval(&_c. - 1)
			%end;
		;

		set _cross_rate_sum1 - _cross_rate_sum&_row_num.;
		no=_n_-1;

		
		%if %index(&bunbo., c) > 0 %then %do;
			/*表頭を分母にする場合は分母用変数を作成する*/
			retain _bunbo1-_bunbo&_col_num.;

			if no=0 then do;
				*表頭のN数;
				%do _c=1 %to &_col_num.;
					&prefix.&_c.=_num&_c.;
				%end;

				%do _c=1 %to &_col_num.;
					_bunbo&_c.=_num&_c.;
				%end;
			end;
			else do;
				%do _c=1 %to &_col_num.;
					if _bunbo&_c. > 0 then &prefix.&_c.=_num&_c. / _bunbo&_c.;
					else &prefix.&_c.=.;
				%end; 
			end;
		%end;
		%else %if %index(&bunbo., r) > 0 %then %do;
			/*表側を分母にする場合*/
			&prefix.1=_num1;
			%do _c=2 %to &_col_num.;
				if _num1 > 0 then &prefix.&_c.=_num&_c. / _num1;
				else &prefix.&_c.=.;
			%end; 
		%end;
		%else %do;
			/*読込んだレコード数を分母にする場合(デフォルト)*/
			&prefix.1=_num1;
			%do _c=1 %to &_col_num.;
				if &_n_all. > 0 then &prefix.&_c.=_num&_c. / &_n_all.;
				else &prefix.&_c.=.;
			%end;
		%end;

		/*四捨五入*/
		%if %length(&round.) > 0 %then %do;
			%if %index(&bunbo., c) > 0 %then %do;
				if no ne 0 then do;
					%do _c=1 %to &_col_num.;
						if &prefix.&_c. ne . then &prefix.&_c.=round(&prefix.&_c., &round.);
					%end;
				end;
			%end;
			%else %do;
				%do _c=2 %to &_col_num.;
					if &prefix.&_c. ne . then &prefix.&_c.=round(&prefix.&_c., &round.);
				%end;
			%end;
		%end;

		/*パーセント表示*/
		%if %upcase(&percent.) = Y %then %do;
			%if %index(&bunbo., c) > 0 %then %do;
				if no ne 0 then do;
					%do _c=1 %to &_col_num.;
						if &prefix.&_c. ne . then &prefix.&_c.=&prefix.&_c. * 100;
					%end;
				end;
			%end;
			%else %do;
				%do _c=2 %to &_col_num.;
					if &prefix.&_c. ne . then &prefix.&_c.=&prefix.&_c. * 100;
				%end;
			%end;
		%end;
	run;

	/*不要データの削除*/
	proc datasets lib=work noprint;
		delete _cross_rate_:;
	quit;

%mend cross_rate;