SASマクロ 集計

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

一般的に、複数の変数を組み合わせた条件で集計するときは、事前に集計用変数を作成し、それからPROC FREQやPROC SUMMARYなどのプロシジャで集計することが多いかと思います。

本記事では、事前に集計用変数を作成せずに、マクロの引数に条件を指定するだけでクロス集計ができるマクロをご紹介します。

こんなこともできるんだなぁ、ぐらいにご参考になさって下さい。

1.サンプルデータ

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


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

 

2.展開例

・レコード数を集計

ウェイトを掛けず、1レコードを1としてカウントする例です。

%cross_count(
	indata			=list_population,
	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_count
);

今回はサンプルデータ自体が整った集計表になっているので、以下のように、全部同じ件数になっています。
cross_count
 

・母数を絞ってレコード数を集計

引数siboriに条件を指定し、母数を絞って読み込む展開例です。

%cross_count(
	indata			=list_population,
	sibori			=8000 <= area <= 14000,
	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_count_sibori
);

先ほどとは違い、母数が関東地域のみに絞られました。
cross_count_sibori
 

・ウェイトを掛けて集計

次はウェイトを掛けて集計する例です。
引数weightに変数や定数を指定することで、1レコードを1件ではなく、ウェイト値で集計します。

%cross_count(
	indata			=list_population,
	sibori			=8000 <= area <= 14000,
	weight			=population,
	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_count_weight
);

以下のように、引数weightで指定した変数population(人口)で集計されています。
cross_count_weight
 

・ラベル無しで集計

ラベルが不要な場合は、引数row_labelとcol_labelを省略します。

%cross_count(
	indata			=list_population,
	sibori			=8000 <= area <= 14000,
	weight			=population,
	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_count_nolabel
);

以下のように、行方向(表側)ラベルはブランクになり、列方向(表頭)ラベルは条件式が表示されます。
cross_count_nolabel
 

・条件を組み合わせて集計

次は、複数の条件を組み合わせて集計する例です。
関数や不等号など、IFステートメントの条件式に指定できるものであれば大丈夫です。

%cross_count(
	indata			=list_population,
	sibori			=8000 <= area <= 14000,
	weight			=population,
	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_count_joken
);

以下のように、様々な条件でサクッとクロス集計ができました。
cross_count_joken
※ちなみに、引数の区切り文字をシャープ「#」にしたのは、SASでの使用頻度が極めて低いからです(主観です)。
 

3.参考プログラム

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

%macro cross_count(indata=, sibori=, weight=, row=, row_label=, col=, col_label=, prefix=out, outdata=);
%put --------------------------------------------------;
%put cross_count;	/*クロス集計(件数)*/
%put &=indata;		/*入力データセットを指定(データセットオプションの指定可)*/
%put &=sibori;		/*読込みデータの絞り条件(省略可)*/
%put &=weight;		/*ウェイト用変数を指定(省略した場合はレコード数をカウントする)*/
%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);
	%let row_label		=N数 # %superq(row_label);
	%let col			=%str() # %superq(col);
	%let col_label		=N数 # %superq(col_label);

	
	/*データの読込み*/
	data _cross_count_temp;
		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;

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

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

		%if &sysnobs. > 0 %then %do;
			data _cross_count_sum&_r.;
				keep
					joken
					label
					&prefix.1 - &prefix.&_col_num.
				;
				set _cross_count_temp2 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 &prefix.&_c. + count;
					%end;
					%else %do;
						&prefix.&_c. + count;
					%end;
				%end;
				if eof;
			run;

		%end;
		%else %do;
			/*全レコード数が0の場合はダミーデータを作成する*/
			data _cross_count_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.;
					&prefix.&_c. = 0;
				%end;
			run;
		%end;

	%end;

	/*統合して出力*/
	data &outdata.;
		label
			no="No"
			joken="表側条件"
			label="表側ラベル"
			&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;
		;
		/*N数列を追加した分をリネームしてずらす*/
		rename
			&prefix.1=n
			%do _c=2 %to &_col_num.;	
				&prefix.&_c.=&prefix.%eval(&_c. - 1)
			%end;
		;
		set _cross_count_sum1 - _cross_count_sum&_row_num.;
		no=_n_-1;
	run;

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

%mend cross_count;