ループに関するエトセトラ

プログラミング

このブログでは、統計解析ソフトStataのプログラミングのTipsや便利コマンドを紹介しています.

Facebook groupでは、ちょっとした疑問や気づいたことなどを共有して貰うフォーラムになっています. ブログと合わせて個人の学習に役立てて貰えれば幸いです.

さて、本日ご紹介するのは、ループコマンド全般についてです.

ループコマンドは、同じことを繰り返し実行するコマンドを、次々に実行するという、

プログラミングらしいコマンドです.最初は暗号のように見えますが、徐々に慣れてくるととても便利です.ぜひ使いこなしてみてください.

1. 種類と使い方について

1)foreachの基本構文:foreachの直後の3要素

  ⇒ local変数名、in または of リストタイプ、数列または文字列

基本的には文字列を使うと覚えてもらってよいと思います.(数列は次のforvaluesのところで紹介します)

そして基本形では、文字列に持ってこれるのは、変数名のみであることに注意が必要です.

syntaxは、local変数名をlclnameとして、以下のような形になります.

foreach lclname  in または of リストタイプ  数列または文字列 {
       `lclname' を含んだcommand syntax
        } 
規則1. foreach文の最後にある “{ ” のあとには何も書かない
規則2. コマンドsyntaxは新しい行にする
規則3. 締めくくりの “}” は新しい行で、単独にする

先ほど、「文字列には変数のみ」という原則を書きましたが、local・global変数も受け付けています.以下のパターンに整理することができます.

1. foreach lclname in any_list {
2. foreach lclname of local local_macro_name   {
3. foreach lclname of global global_macro_name   {
4. foreach lclname of varlist  varlist    {
5. foreach lclname of newlist  newvarlist {
6. foreach lclname of numlist  numlist    {

マクロを使った例をこのあとに詳しく解説します.

2)forvalues の基本構文

こちらは数を一定の規則に従って次々に代入していく方法です.

forvalues lclname = range {
       `lclname' を含んだcommand syntax
        } 
rangeの表現方法
1. #1(#d)#2      : #1 ~ #2の範囲で、#dずつの幅で    
2. #1/#2         : #1 ~ #2 の範囲のすべての整数     
3. #1 #t to #2   : #1 ~ #2の範囲で、#t – #1ずつの幅で
4. #1 #t :  #2    : #1 ~ #2の範囲で、#t – #1ずつの幅で

具体的によく使うのが以下のようなsyntaxになります.縦持ちのデータで一人複数行のデータが存在している場合に一人一人がいくつずつデータを持っているのかを数えるというコマンドをループでやってみますと、以下のようになります.

forvalues n = 1/10 {
     display "ID=", `n'  
   count if id==`n' 
        } 

まあ、このコマンドに関してはbysortとかで一気にやってしまうことのほうが多いとは思いますが…。

2. 注意点

1)数が多すぎるエラー

先ほどforeachは基本的には文字列(変数名やlocal変数名)を入れることが原則であることを説明しましたが、数を入れることもできます.でもあまりやらない方がいいです.というのも、一定数を超えると、「数が多すぎる」というエラーが出てしまうからです.

参照URL

foreach lclname of num 1/1600以上の数値 {
     } 

“invalid numlist has too many elements”

というエラーがでてしまいます.

forvalues lclname = 1/1600以上の数値 {
     } 

こうしておけばいいんですが、それなら初めからforeachは文字列、forvaluesは数列、という原則で覚えておいた方がよいでしょう.

2)変数名ではなく値でループを回したい①

先ほど説明した通り、foreach のvarlistには変数名を並べるのが基本です.しかし、その変数の中のエレメントを使いたいことがありますよね.

例えば、変数名でcityというのがあるけど、そのcityごとに集計をしてみたい、みたいな状況です.通常はそのcityしかvarlistに入れることができないのですが、Tokyo, New York, San Fransiscoのような、変数cityの中身を入れたい!という状況ですね.

.foreach lclname of varlist var1 var2 var3 … {

ここでvar1~3は変数名でなければなりませんが、“varlist” の代わりに

”local +文字列の入ったlocal macro”

とすればlocal変数に格納された数列をあたかも一つ一つを変数として扱うようにループコマンドを走らせることができます.

変数の中身を取り出す方法としてよく使われるのが、”levelsof”というコマンドです.

.levels of city, local(citynames)

のような感じでcitynamesというローカル変数にその変数cityの中身を格納してくれますので、それを一つ一つ取り出してループを回すというイメージです.

levelsof city, local(citynames)
foreach lclname of local citynamess {
   list city var1 var2 var3 ... if ID== "`lclname'"
   } 

これは条件設定をすることもできます.例えばある特定の条件で絞り込んだIDのリストで、特定変数の結果を表示させたい場合、以下のように表現しましょう.

levelsof ID if (条件節) , local(levels)
foreach lclname of local levels {
   list ID var1 var2 var3 ... if ID== "`lclname'"
   } 

このとき、”ID”という変数に含まれ、特定の条件で絞り込まれた値が数列としてlocal 変数である”levels”の中に格納されます.

そのlocal変数を呼び出すことで中に格納された値でループを回せるという優れものです.

sysuse census, clear
  levelsof state in 1/10, local(levels)
        foreach v of local levels {
                display "`v'" 
        } 

3)変数名ではなく値でループを回したい②

  ⇒ Macroで連番を振ってループに乗せる

これは超絶技巧だと思っています.めちゃくちゃ便利です.横着な管理者がとっても好むタイプの方法です.

local macroにしてもglobal macroにしても、

展開中のデータの変数リストに無い文字列を入れる場合には、

その文字列を ” ” で括って作ればいいのですが、

それに連番を振ると、さらに楽になります.

例.

.local lclname1 “Tokyo”

.local lclname2 “New York”

.local lclname3 “San Fransisco”

.local lclname4 “Nagoya”

このように定義しておくと、変数の一部に文字列が含まれているような状況のときに便利です.

例えば、

 forvalues n = 1/4 {
   `lclname`n'' を一部に含んだ変数を用いたコマンドsyntax
} 

これのいいところは、

foreach v of varlist string1 string2 string3 string4 {
...
} 

としたときに、もし string1 という変数が存在しないとエラーが帰ってきてしまうのですが、

それを防げるという点にあります.あるいは変数名の一部に使われている文字列を代用させることもできます.実際にはこちらの使い方のがメインになるかもしれません.以下はまさにその実例です.

例えば、複合心血管イベントのそれぞれのイベント発生率を計算するループを作りたい場合、

そのイベントの名前が生存分析の要素となる変数(イベント有無、イベントまでの時間など)にいろいろ使われている場合に便利です.

例えば、aval_cvd はCVDイベントまでの時間、cnsr_cvdはイベント有無の変数として、

local cvd1 "cvd"  
local cvd2 "hf"  
local cvd3 "cad"   
local cvd4 "stroke"
/* 変数名の一部を連番付きlocal変数で置き換える */
forvalues n = 1/4 {
   stset aval_`cvd`n'' , failure(cnsr_`cvd`n'' == 0)
	count if cnsr_`cvd`n'' == 0
	local ev = r(N)
	stsum
	local ir = r(ir)*1000
   noi disp "`cvd`n''", `ev', %4.1f `ir'
	} 

こうすればイベント発生数と発生率(1000人年当たり)がきれいに出力され、結果を整えるのがとても楽です.

まとめ

・ループコマンドについてまとめました.

・local macroなどと組み合わせてちょっとした工夫をすると結果の出力が楽になります.

コメント

  1. […] ループを使って繰り返しのコマンドをひとまとめにするコツを以前の記事でまとめていますが、今回は、ループをいくつも重ねてすっきり洗練されたプログラムを書くコツをまとめてみたいと思います. […]

  2. […] ループに関する内容を網羅した記事はコチラ. […]

タイトルとURLをコピーしました