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

プログラミング

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

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

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

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

プログラミングらしいコマンドです.

最初は暗号のように見えますが、徐々に慣れてくるととても便利です.ぜひ使いこなしてみてください.

具体的にはどのようなことをするのでしょうか?

例えばある特定の変数において行った処理を、他の変数においても同様に実施する、というようなことがあると思います.

ID変数1変数2変数3
1a1b1c1
2a2b2c2

それぞれの変数の平均値を出してそれを元の値から引く、「中心化」という処理を行いたい場合、

summarize 変数1, detail
generate m変数1 = 変数1 - r(mean)

こんな処理を行います.

これを変数2でも変数3でも同じようにやりたい場合、変数1というところだけ次々に切り替えられたらよいですよね?

そのような処理を繰り返し処理「ループコマンド」を駆使して行うことができるわけです.

そして今回の記事ではその繰り返しの元となる変数・値のリストアップの仕方と、リストされる変数・値の形から方法を整理してみたいと思います.

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

まず、リストされる変数・値の形によって大きく2つに分かれることを理解しましょう.

ざっくりと言えば、数列かそれ以外かで使うループコマンドが大別されます.

数列の場合 ⇒ forvalues

それ以外 ⇒ foreach

数列の場合にはどのように数字を並べるかのバリエーションだけですので簡単です.

それに対して、foreachの場合は、変数・値のリストアップの仕方によって記載方法が細かく分かれます.

まずはforvaluesの方から説明していきたいと思います.

1)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ずつの幅で
自分は2の方法を採っていることが多いです.

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

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

なお、この方法は実は少しだけ応用編があります.ズバリ、ローカルマクロと組み合わせてもっと便利に!という方法です(後述します).

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

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

次に、数列以外の変数や値を扱う方法について説明します.数列以外なので、基本的には文字列を使うと覚えてもらってよいと思います.

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

冒頭の例ですと、次のようなループプログラムを書いて実行させることになります.

foreach V of varlist 変数1 変数2 変数3 {
  summarize `V', detail
  generate m`V' = `V' - r(mean)
 }

ここで、「V」はlocal変数名です.プログラムが走っている時だけ有効で、varlist以下に並んでいる変数群が一つ一つ順番に当てはめられて以下のプログラムを実行することになります.

つまり、変数1に対して、

  1. summarizeコマンドを実行して、値の要約値を算出する(結果をあとのコマンドで使用するため)
  2. 上記のコマンドを実行したあと、 return results (結果を一時的に保管しておく箱みたいなもの)から平均値をとりだし、元の値から引いたものを「m変数1」として新たな変数を作成する

この流れを変数1から3まで順番に実行していくことになります.

もうちょっとこれを一般化すると、以下のような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    {

1のinを使ったforeach構文ですが、このリストにはデータセットなんかも入れることができます.

foreach file in this.dta that.dta theother.dta {
           merge 1:1 id using "`file'"
           keep if _merge == 3
           drop _merge
        }

「file」というlocal変数を使って、「this」,「that」,「theother」というデータセットを順番にmergeしているだけです.

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

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をコピーしました