ラベル sas tips の投稿を表示しています。 すべての投稿を表示
ラベル sas tips の投稿を表示しています。 すべての投稿を表示

2025年4月1日火曜日

複数の変数に同じ処理をかける時は配列が使えるの話

 例えばA、B、Cの3つの変数に同じ処理…今回はそれぞれに+1する…を実行する時、arrayが使えることもある。同じ処理をピコピコと繰り返し書くのが面倒な時は配列に記載してやればいっぱい似たようなものを書かなくて良い。具体例は以下の通り。

配列hogeを定義して、do overで全部にdo以下の処理を当てはめている。dimで要素数取って、とかをやっても良いのだが、今の場合のように全部に処理をすることが明らかな場合はわざわざdimで要素数を取らなくても良い。

data _null_ ;

    a = 1 ;

    b = 2 ;

    c = 3 ;

    array hoge a b c ;

    do over hoge ;

        hoge = hoge + 1 ;

    end ;

    putlog a b c ;

run ;

2025年3月1日土曜日

rtfの表に上付きの🄬を出力する

rtfの表内に上付き文字を出したいときはインラインフォーマットで^{super A}とすると上付き文字のAが出せますし、ギリシャ文字なんかを出すときは^{unicode 03B2}で出したりします。

環境依存文字の🄬も似たように^{unicode 24C7}でrtf上に出せますが、上付きの🄬…ユニコード指定の文字を上付き…で出すには工夫が要るのか…?という話です。特段の工夫とかは要らず、hoge^{super ^{unicode 24C7}}とやるとhogeの右肩に🄬を出すことができます。便利ですね。


インラインフォーマット指定前の^はods escapecharacterで指定しています。試してないのですが、defaultの(*esc*)だと重ねれない気がするので何かしらの文字をエスケープ文字として設定してください 

2025年2月7日金曜日

最新の更新日時のフォルダ/ファイルを取得する

指定したフォルダの中から最も新しいファイル(例えばxlsxなりcsv…) のファイル名を取得したい時があります。ファイル名が同じで中身だけ更新されるならプログラムを変えなくても良いですが、更新のたびにな前の末尾に日付が入ったりしてファイル名が更新される時があります。そんな時に毎度プログラムを更新するのはちょっとめんどくさいので、更新日時が一番最後のファイルの名前をマクロ変数に格納するプログラムの紹介です。

以下のデータステップで、if文に記載の拡張子が.xlsxとファイル名に_Specを持つファイルの中から、最も更新日が新しいファイルの名前をマクロ変数_spec_fileに格納しています。

%let _path = hogehoge ;

filename PPASS pipe "dir /od /b &_path ";

data _null_ ;

  length _inpass $200 ;

  infile PPASS DLM='09'X DSD MISSOVER LRECL=5000 ;

  input _inpass $ ;

  if ( index(_inpass,".xlsx")>0 and index(_inpass, "_Spec")>0 ) then call symputx("_SPEC_FILE",_inpass) ;

run ;

重要なのはfilename PPASS pipe "dir &_mspec_path /b /od";の部分です。コマンドプロンプトで使用するdirコマンドをsasで使用して、その結果をinfileでデータステップに回収しています。/odで更新日時順、/bでファイル名だけ取っています。データステップに更新日時順でファイル名を持ったレコードが発生するので、一番最後を取れば更新日時が一番最後、つまり一番最新のファイルが取得できる…というわけです。便利ですね。

更新日時が一番最後のフォルダを取るときはデータステップのif文で条件を変えても行けますが、余計なファイルをはじくようにdirのオプションに/adを指定しても良いです。/adでフォルダだけ取得できるので…

これで勝手に取得できますが、念のため何をマクロ変数に入れたかは都度確認が必要です。意図通りのものと違うものがマクロ変数化されているとややこしいので…

2024年12月1日日曜日

日付が欠測の日付をsas日付に変換する

 例えばjun/2023のように年月だけの日付をmmm/yyyyで表した文字値があるとして、これをsas日付に変換するにはmonyy8.formatが使えます。sas日付にさえできればjun/2023の文字を2023-06等表記を変えた文字値に変換することもできるので意外と使い道が多く助かっています。


data _null_ ;

   a = "24/jun/2023" ; b = put(input(a, date11.), yymmdd10.) ; putlog a= b= ; output ;

   a = "jun/2023"    ; b = put(input(a, monyy8.), yymmd7.)   ; putlog a= b= ; output ;

run ;

a=24/jun/2023 b=2023-06-24

a=jun/2023 b=2023-06


sas日付からyyyy-mm形式の文字値に変換する際は上記の通りyymmd.formatを使います。ややこしいのですがyymmw.formatとyymmxw.formatがそれぞれ別のものとしてあって、今回はyymmxw.formatのxの部分にdを指定してハイフン区切りの出力としています。yymms.とすると/区切りになります。

2024年7月1日月曜日

sasで変数の和を計算する話

 同じobsの二個以上の変数の和を求めるときは、sum関数を使うか変数を+で足すかのどちらかです。どちらも似たような処理ですが、書き方で微妙に欠損の取り扱いが違うので注意が必要です。足す変数に欠測が含まれるときはsum関数は欠測を飛ばして足し算して、+で足す時は足し算そのものが行われません。

3157  data _null_ ;
3158  a = 2 ; b = . ; c = a+b ;  putlog c= ; output ;
3159  a = 2 ; b = . ; c = sum(a,b) ; putlog c= ; output ;
3160
3161  run ;

c=.
c=2
NOTE: 欠損値を含んだ計算により、以下の箇所で欠損値が生成されました。
      (回数)(行:カラム)
      1 3158:22
NOTE: DATAステートメント処理(合計処理時間):
      処理時間           0.00 秒
      CPU時間            0.00 秒

出てるlogは読もうね。欠損の処理がめんどくさいしNoteだからと欠損値を含んだ計算…をlogに残したままにするとこの手の結果が変わるやつを見逃すから、この手のはできればlogに残さないようにしてね。
あと誰かのを引き継いだ時に欠損値を含んだ計算…のlogが残っていると何がどうなってるのかの確認を全部しないといけないからね。出来ればlogに残さないようにしてね(憤怒)

2024年6月1日土曜日

excel日付とsas日付の違いの話

excel日付とSAS日付はどちらも日付を数値にしたものですが、そういえばこの二つは開始日が違ったな…ということがありましたので、忘れないように記事にします。こんな事めったにないとは思いますが。

この上記のexcelは、1行目に変数ラベル、2行目の変数名、3行目に日付(excel日付)が格納されています。これをsasにimportすると以下のようになります。仮なので読み込む際にオプションのgetnamesをyesにしたとしています。

データとしてはexcel日付の列ですが、変数名がデータの値として格納されているので文字列として扱われた結果2024/5/2がexcel日付のまま45414として文字列になっています。ところで2024/5/2のsas日付は23498なのでこのまま日付を型変換すると2024/5/2になりません。45414は2084/5/3です。

11   data _null_ ;
12       a = '02may2024d'd ;
13       putlog a= ;
14   run ;

a=23498

excel日付は1が1900-1-1なのに対し、SAS日付は1が1960-1-2なので60年と1日の差があります。ちなみにSAS日付上での1960-1-1は0です。excel日付は1始まりですがSAS日付は0始まりということもこの時初めて知りました。

なのでexcel日付からSAS日付に変換する際には1900-1-1と1960-1-2の差分の(21915 + 1)をexcel日付側から引けば日付が一致します。以下の実行ログを見ると、引き算した後のEXDTには正しく2024-5-2が格納されていて、excelから読み込んだものをそのままformat当てたORGDTは2084-5-3となっています。

130
131  data _null_ ;
132      set sh1 ;
133
134      format EXDT  yymmdd10. ;
135      format ORGDT yymmdd10. ;
136
137      if _n_ = 2 then do ;
138          ORGDT = label ;
139          EXDT = label - (21915 + 1) ;
140
141          putlog ORGDT= EXDT= ;
142      end ;
143
144  run ;

NOTE: 以下の箇所で文字値を数値に変換しました。(行:カラム)

      138:17   139:16

ORGDT=2084-05-03 EXDT=2024-05-02

NOTE: データセットWORK.SH1から2オブザベーションを読み込みました。

NOTE: DATA ステートメント処理(合計処理時間):

      処理時間           0.00 秒

      CPU時間            0.00 秒

普段こんなことはほとんど起こらないのですが、読み込むexcelファイルの日付がexcel日付で入力されている+その列にexcel日付以外の文字列が存在することで今回のようなことが起こります。例えば今回のような1行目が変数ラベルで2行目が変数名になっていたりとか、SASで取り込む際には不要ですけどファイルの作成者名が上の方に入ってたりとか、で紛れ込んできます。読み込むexcelをきれいに加工してからSASにimportすれば良いのですが、あまりexcelに変に手を加えるのも憚られる時があるし…で難しいところです。

2024年5月1日水曜日

階層構造をもったjsonをSASに読み込む話

 少し前(2019年は"少し"前です)のSASユーザー会で、jsonに対応したLibnameステートメントを使って単階層のJSONをSASに読み込む内容の発表がありました。…そういえば複数階層を持つjsonをSASで読み込むとどうなるのかって気になりますよね。私はなりました。時間が空いていますがちょっと調べたので記事にします。件のSASユーザー会の資料は何となく怖いのでリンクしません。「JSONという奇妙な拡張子とSAS」で調べてみてください。HITすると良いんですが。

用意したのは以下の構造のjsonです。このブログは容赦なくベタ張りするのでスクロールが大変ですね。すみません。改善する気は今のところありません。例えばid:1は子階層childrenを持っています。

{

  "id": "1",
  "name": "root",
  "children": [
    {
      "id": "2",
      "name": "child1",
      "Detail": [
        {
          "id": "3",
          "name": "grandchild1",
          "items": [{"itemID": "1A", "itemprice": 280}]
        },
        {
          "id": "4",
          "name": "grandchild2",
          "items": []
        }
      ]
    },
    {
      "id": "5",
      "name": "child2",
      "Detail": [
        {
          "id": "6",
          "name": "grandchild3",
          "items": [{"itemID": "1B", "itemprice": 500}]
        }
      ]
    }
  ]
}

これをSAS以下のプログラムでSASに読み込みます。encodingをutf-8にしているのは何となくです。このデータはシングルバイトのみなので違いはありません。

filename TG "piyopiyo\Hierarchy.json" encoding="utf-8" ;

libname IN json fileref = TG ;

proc copy in = IN out = WORK ; run ;

libname IN clear ;

読み込むと以下の通りAllData、Children、Children_detail、Detal_items、rootのデータセットになります。


それぞれが各階層のデータに対応していますが、階層間のつながりはAllDataを見ればわかるようになっています。各階層が縦に積まれているので、実際には転置したほうが使いやすいかもしれません。

以下の画像はAlldataデータセットの内容で、例えば1階層目にはidが1、NameがRoot、子階層としてChildrenを持っていて、2階層目のChildrenはidが2、nameがchild1で子階層(1階層目から見たら孫階層)としてDetailを持っている、ことなどがわかります。

Libnameでとりあえず読み込んだだけにしては階層の情報も含めていい感じに取得できていると思います。SASが自動で作成するmapファイルもなかなかのもんじゃないですか。



2024年2月2日金曜日

EXCELファイルをRでxptファイルに変換し、SASで回収する話

 BASE SASではproc importのdbmsにxlsxあたりを指定できないという話があります。excelを読み込みたくばSAS/ACCESS Interface to PC Filesを導入しろとのSAS社からの思し召しなのですが、そう言われるとどうにかならんのかと考えてしまいますね。こんな取り組みは弊社の業務では1mmも使わないので無駄なのですが、気に食わないものはしょうがない。最近はpythonがどうだと大盛り上がりですが、今回はRでexcelファイルをxptに変換して、sasでそれを読み込むことを目指します。sas7bdatは仕様が公開されていないらしいので、どうしてもsas以外を使うとXPTファイルにしかならないのは考え物です…案外私が知らないだけでどうにかなるのかもしれませんが。

まずはR恒例のパッケージインストールから。以下のコードを実行すると、パッケージがパソコンに入っていると普通にロードを、なければダウンロードします。今回はdplyrとtidyverseを使います。libsにほしいパッケージを指定すれば、なにもこの二つに限らず実行できます。

libs <- c("dplyr","haven","readxl")

requireLibs <- function(libs) {

  for(lib in libs){

    if(!require(lib, character.only = T)){

      install.packages(lib)

      require(lib, character.only = T)

    }

  }

}

requireLibs(libs)

まずはexcelを読み込みます。Rはいろんなパッケージがあって、同じ処理でもいろんな関数があって困るのですが、私はread_xlsxを使っています。よく似た名前のread.xlsxもあるので難しいですね。read_xlsxは標準だと変数の型推定が guess_max = min(1000, n_max),となっているので1000行まで判定してくれます。データが多そうなら適宜この引数を変えましょう。 何もしなくても1000まで判定してくれるのえらいですね。

inpath <- "piyopiyo/book1.xlsx"

infile <- read_xlsx(path = inpath, sheet = "Sheet1")

いざ中身を見ると以下の内容です。全部数値のAは数字に、文字を含んでいるBとCは文字値になっていて、Cの11行目の長い文字列も切れずに読み込めています。データはtibble型になっていますがデータフレームです。tidyverseではこっちをよく使っているようですが。

head(infile, 11)

# A tibble: 11 × 3

       a b     c         

   <dbl> <chr> <chr>     

 1     1 1     xxxx      

 2     2 2     2         

 3     3 3     3         

 4     4 4     4         

 5     5 5     5         

 6     6 6     6         

 7     7 7     7         

 8     8 8     8         

 9     9 9     9         

10    10 10    10        

11    11 xxxx  xxxxxxxxxx

このinfileをそのままxptにしてもよいのですが、すべての変数を文字値として扱いたい時を想定して一処理追加します。

chr2_infile <- infile %>% mutate_if(. , .predicate=is.numeric, .funs= ~as.character(.) )

上記でinfileの数字変数、今回でいうところのAを文字に変換して、chr2_infileという名前に変えています。もう少し解説すると、mutate関数でデータフレームの列単位で一括処理ができますが、今回はmutate_ifにすることで、数字変数に対して一括で文字値化する処理をしています。.predicateに処理をかける変数の条件を、.funsに実際に行う処理を指定します。変数単位で指定して処理を書くのはSASではあまりない考えで好きです。まだ私もピンと来ていないところが多々ありますが…
作ったchr2_infileの中身を見てみると、Aが無事chrに変換されているのがわかります。
 head(chr2_infile, 11)
# A tibble: 11 × 3
   a     b     c         
   <chr> <chr> <chr>     
 1 1     1     xxxx      
 2 2     2     2         
 3 3     3     3         
 4 4     4     4         
 5 5     5     5         
 6 6     6     6         
 7 7     7     7         
 8 8     8     8         
 9 9     9     9         
10 10    10    10        
11 11    xxxx  xxxxxxxxxx

最後に作ったchr2_infileをxptファイルに変換します。version引数は標準だと8なのですがなぜか8だと作ったxptをsasで読み込めません。sas側がerror出します。仕方がないのでversionは5とします。この引数は5か8しか指定できません。
お察しの通りxpt version 5で作成しますので、変数名が長すぎたりするとうまく作れないと思います。試してませんけど…

write_xpt(chr2_infile, "piyopiyo/book1.xpt", version = 5 )

上記を実行すると第二引数に指定したパスとファイル名でxptファイルが出力されますので、sasから回収に向かいましょう。これは私のいつもの書き方なのですが、Libnameステートメントにxptファイルを指定して、それをworkにコピーしています。コピーしたら元のlibnameは要らないのでクリアします。

libname INXPT xport "piyopiyo\book1.xpt" ;

proc copy in=INXPT out = work ; 
run ;

libname INXPT clear ;

workにコピーしたbook1データセットを見に行くと、無事以下のように格納されています。文字長に余裕とかは無いので、Aはlengthが2です。この辺はRから作ったxptファイルなのでしょうがないですね。そもそも固定長じゃないしね。


ブログの途中であったxptのversionが5じゃないとSAS側がerror出すというのは結構今一ポイントです。一応githubにxptをproc copeyではなくて自動マクロの%xpt2locでworkに移せばversion 8のxptでも読み込めるよとあるのですが、実際にやってみると実行時にerrorは出ませんが0obsのxptとして読み込まれてしまうので、書き出し時のxptのverisonは5を指定する必要があります。…今後の修正と更新に期待です。xptのversion5って変数名8文字とかのとんでもなく厳しい縛りがあるのであまり使いたくないんですよね…

それはそうとてBASE SASしかないとEXCELファイルを読み込むのにずいぶん難儀すると思いますが、Rを使ったこの方法で何とかBASE SASしかない環境でもEXCELが読み込めるようになるとよいのですが。xptのversionだけはどうにかならんのか。今時5て。自分の業務には1mmも関係ないですけど…
ちょっと記事内にとっ散らかったので最後にR側のコードをまとめておきます。

#パッケージインポート
libs <- c("dplyr","haven","readxl")
requireLibs <- function(libs) {
  for(lib in libs){
    if(!require(lib, character.only = T)){
      install.packages(lib)
      require(lib, character.only = T)
    }
  }
}
requireLibs(libs)

#excelをRに読み込み
inpath <- "piyopiyo/book1.xlsx"
infile <- read_xlsx(path = inpath, sheet = "Sheet1")

#数字変数を文字変数に変換
chr2_infile <- infile %>% mutate_if(. , .predicate=is.numeric, .funs= ~as.character(.) )

#xptに変換
write_xpt(chr2_infile, "piyopiyo/book1.xpt", version = 5 )




2024年2月1日木曜日

SASでexcelを読み込むときのdbmsはまずxlsxを使う

 何度目かもわかりませんが、excelをsasに読み込む話をまたします。表題の通りSASでexcelファイルを読み込むときにはまずdbms=XLSXを指定するのが私のやり方です。

dbmsにはやれexcelだxlsxだexcelcsだといろいろ指定できるのが厄介で、それぞれどう違うんだという話への回答がsasの日本語フォーラムにあります。検索してもこの回答にたどり着くまでが遠すぎるのでこのブログでも紹介します。マジで見てくれ。お願いだ。これで今回の記事はおしまいですが、それだと寂しいのでもうちょっと追記します。

リンク先の記事では以下の画像のように回答されていて、excelcsはsasとexcelのbit数が違う場合に、excelはbit数が同じ場合に、xlsxはbitの違いやexcelの有無にかかわらず使用できます。とあります。excelは途中まで数字、途中から文字値の列を読み込んだら文字値の部分を欠測で認識するとんでもない欠陥があるのでそもそも使い物にならないこともあって、bit数にかかわらず使用できるうえにまだ文字値と数字をちゃんと判別するxlsxをまず試してみましょう…という話です。この辺のことは前に記事にしています。


これでexcelは無事sasで読み込めました、めでたしめでたしとはなりません。BASE SASのpric importはdbmsにxlsxと指定できないのです。なんとCSV,DLM,JMP,TABの4つにしか対応していない。こうなると泣く泣くCSVで読み込むしかないのが現状です…




2023年10月24日火曜日

複合パネルのグラフの作り方についての紹介

 先日のsasユーザー総会で散布図行列の作り方なる素晴らしい発表があってからというもの、あちこちで散布図行列の作り方や一ページに複数枚のグラフを貼った、複合パネルのグラフの作り方が紹介されている。これだけ色々なところで取り上げられているなら便乗しないともったいない。

例えばかの有名なブログではRWIで複合パネルのグラフを作る方法について紹介されている
https://sas-tumesas.blogspot.com/2023/10/blog-post.html

普通に考えればGTLでやればいいというのは全くその通りなのだが、sgplotで書いたグラフ画像をproc reportを使って一ページに複数枚配置する方法について紹介する。需要?多分無いよ

グラフについては各々sgplotで出力してもらうとして、今回は以下のようなpngを3*3で配置してみようと思う。どこにでもあるpngですね。PharmaSUG 2023 SDEは大阪で開催予定です。ぜひご参加下さい。

この画像を3*3にproc reportで並べたものをrtf出力すると以下のようなイメージに。もうちょっと縦に伸ばすとぴったりはいるが、どうせ脚注やら何かしらを入れるので余白のままにしている。そこまでの調整がめんどくさかったわけではないんです本当です信じてください。


以下が出力プログラム。データセットに出力したいpng名を指定した変数を作って、proc reportでそれぞれを呼び出して並べています。あくまでpingを並べているだけなので相関係数などの文字列だけ出したいときは、それだけの画像を良い感じに準備すればok。散布図行列ではないので複数グラフ共通のラベルなどは出せないのであしからず。

%let path = piyopiyo ;

data V_GRAPH ;

    L1 = "&path.\PharmaSUG.png" ; R1 = "&path.\PharmaSUG.png" ; output ;

    L1 = "&path.\PharmaSUG.png" ; R1 = "&path.\PharmaSUG.png" ; output ;

    L1 = "&path.\PharmaSUG.png" ; R1 = "&path.\PharmaSUG.png" ; output ;   

run ;


title1 ;

title2 ;


options orientation=portrait papersize=A4;

ods rtf file = "&path.\multi.rtf" nogtitle nogfootnote ;

      proc report data=V_GRAPH nowindows style(report)=[rules = none frame = void] ;

         column L1 R1 ;

        /*- style --*/

        define L1   / style={cellwidth=9.5 cm cellheight = 6.5 cm } "" ;

        define R1   / style={cellwidth=9.5 cm cellheight = 6.5 cm} "" ;

        compute L1 ;

            call define(_col_ , "style" , "style=[preimage='"||L1||"' just = center]") ;

            L1 = "" ;

        endcomp ;

        compute R1 ;

            call define(_col_ , "style" , "style=[preimage='"||R1||"' just = center]");

            R1 = "" ;

        endcomp ;

        footnote1 j=l "test" ;       

      run;

ods rtf close;





2023年4月1日土曜日

sasからExcelを読み込む際に、全ての変数を文字列として扱う話

親の顔くらい業務中に見た話ですが、excelからsasにデータを読み込む際に全てのデータを文字として扱いたいときの話です。

要はdbms="xlsx"を指定してExcelを読み込んだ後に、数値変数をデータステップで文字変数に変換するしかない(多分)という内容です。それで終わると寂しいのでsasexcelを読み込む方法を以下に紹介します。

まず読み込むexcelは下の物です。変数b11行目にxxxという文字があり、変数cには1行目に文字があります。それ以外は数字です。

sasexcelエンジン(仮称)を使って読み込む場合

libname でもproc import でも良いのですが、"excel"を指定して読み込む方法がまずあります。libname hoge excel "piyo.xlsx" ; なり、proc import datafile = "piyo.xlsx" out = V_OUT dbms = excel ; です。

どちらも普通にexcelを読み込んでくれますが、同じ変数に数値と文字が混在している場合がややこしくなります。mixedオプションをyesにすると上から8行目までの値で変数属性が決まるので、Excel上で8行目までは数字、9行目に文字が入っている変数はsasでは数字変数となり、9行目の文字列は欠測扱いになります。

上記excelを読み込むと以下のようになります。変数bの文字が欠測になり、変数cは文字変数扱いです。


文字値の意図しない欠測を回避するためには「変数属性の決定をデータの8行目までではなく、データの最後まで読み込んでから行う」ことが必要になりますが、この変更はsasでは指定できずレジストリを変更しないといけません。sasdocumentに以下の記載があります。「The options are located in a key of the Microsoft Windows registry.」じゃねーんだよ

 https://go.documentation.sas.com/doc/en/pgmsascdc/9.4_3.5/acpcref/n0msy4hy1so0ren1acm90iijxn8j.htm

Interaction

The 'TypeGuessRows' entry in your registry settings can affect the behavior of the MIXED= option.
The options are located in a key of the Microsoft Windows registry.

なおmixednoにすると全部数字変数として扱われるので、文字と数値の混在する変数の文字値は全部欠測になります。この場合だと変数b11行目と変数c1行目が欠測です。

意図しない欠測が出てくるのは困りものですがいったん見なかったことにして、表題の「excel上の変数を全て文字値にして取り込む」にはproc importではdbdsoptsを指定すればokです。例えば以下のように指定すると、変数が3つとも200文字の文字変数として扱われます。ただし上述した「9行目以降の欠測になった文字列」は欠測のまま文字化されています。惜しい。

proc import datafile = "hoge.xlsx" out = hogeim dbms = excel replace  ;
    sheet = "sheet1" ;
    getnames = yes ;
    mixed = yes ;
    dbdsopts = "DBTYPE=(a = 'CHAR(200)' b = 'CHAR(200)' c = 'CHAR(200)')" ;
    scantext = yes ;
run ;

libnameではdbsastypeオプションをworkに取り込む際のデータステップに記載すると同じように全て文字変数として取り込めますが、同じように欠測になった文字値はそのまま欠測です。

libname hoge excel "hoge\Book1.xlsx" mixed = yes ;
data hogex ;
    set hoge."Sheet1$"n (dbsastype=(a = char200 b = char200 c = char200) ) ;
run ;

もう一つexcelを読み込む方法として、xlsxエンジンを使う方法(仮称)があります。個人的にはこっちを推しています。

先と同じようにlibname hoge xlsx "piyo.xlsx" なり proc import datafile = "piyo.xlsx" out = hogeimsx dbms = xlsx ;なりを指定してExcelを読み込みます。

どちらの場合でもこうすると以下の画像の通り8列目以降の文字も格納され、該当の変数も文字値として扱われています。ただし全て数字格納された変数は数値変数になります。また数値変数も含めて、全ての変数を文字値として扱うためのdbdsoptsdbsastypeオプションは使えません。このオプションはdbms=excel専用です。

ちょっと読み込むexcelを変えています。C変数11行目の長めの文字列も文字切れせずに格納されていますね。

dbdsoptsオプションが使えないので全ての変数を文字値とするには、読み込んだ後にデータステップで加工するしかないと思います。私はlibname を使用した場合はworkに格納する際のデータステップで数字変数を文字に変換しています。文字変数に変換したい変数が少ないと良いのですが、数が増えると変換漏れが出たりするので注意が必要です。importでもオプションが使えないので同じようにデータステップでの加工が必要です。

 libname hoge xlsx "hoge.xlsx" ;

data hoge2 ;
    set hoge.Sheet1 ;
    length V_A $100 ;

    V_A = cats(A) ;
    drop A ;
    rename V_A = A ;
run ;

この方法には大きな問題があって、libnameなりimportなりでexcelを読み込んだ後にデータステップで加工しているので、何かしらの問題、それこそ文字変数が意図せず欠測として扱われている等、が起きている場合に修正ができません。excel指定でexcelを読み込むよりは不具合なくデータは読み込めていますがやはり確認は必要です。

今後xlsxエンジン(仮称)libnameなりimportなりを使ってexcelを読み込む時点でのオプションで、excel内の全ての変数を文字値として指定できる方法が追加されることを祈っています。ddeはあまり使いたくないので

2022年10月1日土曜日

ズンドコを判定する関数を作った

 先日アップした記事でズンドコキヨシをデータステップで作成した。その時は関数化まではしていなかったが、やはり元ネタに則り関数化までせねばならないと使命感に駆られてしまった。このクソ忙しい時に何をやっているのだろうか。実は私は暇だったのかもしれない。本当か?

お題が「関数化」なのであえてマクロではなくfcmpで関数を定義している。fcmp自体触ったことが無かったので随分と苦戦した。特にfcmpでのarrayはデータステップでのarrayとそのまま同じではないことを初めて知った。まだ二つの違いを理解できていないので難儀なもんである。sasも奥が深い。その違いによるものかは分からないが、乱数で0をズン、1をドコに割り当てたかったが既に数字として定義されているのでarrayの中で文字変数を作れなかったため、やむなく0/1のまま処理をしている。00001がズンズンズンズンドコにあたるので、その場合にキヨシが発生したとみなしている。

ややうまくいかないところがあったが、何はともあれfcmpを使った関数化には成功したので思う存分ズンドコキヨシを判定することができる。多分動くからリリースしようぜとは昔の偉い人もよく言ったものである。関数の出力はズンドコした回数だけにしているのでlogに過度に出力されることもなく、プログラムにこっそり混ぜていてもログが大惨事になることは無い。さあletsズンドコ。この記事が弊社にバレないことを祈る。

/*warning回避用*/

options cmplib = work.piyo ;


proc fcmp outlib = work.hoge.func ;

    function kiyoshi() $40 ;


        __n = 0 ;

        do until (cats(of temp1 - temp5) = "00001") ;


            __n + 1 ;

            /*任意の範囲の乱数を発生させるにはint((上限-下限+1)*乱数+下限)*/

            /*今回は0か1の2値が欲しいので上限=1,下限=0*/

            /*01の乱数をズンとドコに割り当てたかったがうまくいかないので01のままにしている*/

            array ch[5] temp1 - temp5 ;

            do i = 1 to dim(ch) ;

                ch[i] = int( 2*rand("uniform") ) ;

            end ;   


        end ;


        length V_RET $40 ;

        V_RET = cats(__n , "回ズンドコしました") ;


        return(V_RET) ;


    endsub ;

run ;



options cmplib = work.hoge ;

data _null_ ;

    HOGE = kiyoshi() ;

    putlog HOGE ;

run ;


/*実行例*/


880  options cmplib = work.hoge ;

881  data _null_ ;

882      HOGE = kiyoshi() ;

883      putlog HOGE ;

884  run ;


19回ズンドコしました

NOTE: DATAステートメント処理(合計処理時間):

      処理時間           0.04 秒

      CPU時間            0.04 秒


2022年9月1日木曜日

sasで幾何平均を出すときの注意事項

幾何平均は掛け算して根を計算するので値に0や負の数が含まれていると計算できないです.sasのプロシジャで求めるときに値に0が含まれていると,プロシジャによって結果が異なることを紹介します.

幾何平均を出すプロシジャのunivariate,surveymeans,ttestでそれぞれ幾何平均を求めます.使うのは0を含んだ3obsのデータです.私としては0を含むと幾何平均は0になるので,そもそも計算しないことが正しいと思いますが,sasだと0で出すか計算しないか0を除いて計算するかの3つなので,どのように数字を扱いたいかを事前に確認するのがよいと思います.
確認してないですが負の値が含まれていると欠損で値が返ってくるのですかね

/*使うデータ*/
data hoge ;
    a = 5 ; output ;    a = 0 ; output ;  a = 4 ; output ;
run;

/*  univariは0を含む幾何平均を0で出力する  */
proc univariate data=hoge noprint;
 var a;
 output out=out1 geomean=geomean;
run;

NOTE: データセットWORK.OUT1は1オブザベーション、1変数です。
NOTE: PROCEDURE UNIVARIATE処理(合計処理時間):
処理時間 0.00 秒
CPU時間 0.01 秒

data _null_ ;
    set out1 ;
    putlog geomean= ;
run ;

geomean=0
NOTE: データセットWORK.OUT1から1オブザベーションを読み込みました。
NOTE: DATAステートメント処理(合計処理時間):
処理時間 0.00 秒
CPU時間 0.00 秒

/*  surveymeansは0を含むと幾何平均を計算しない  */
proc surveymeans data=hoge geomean ;
  var a ;
  ods output GeometricMeans=out2;
run;

ERROR: A variable must be positive when geometric mean is requested.
NOTE: エラーが発生したため、このステップの処理を中止しました。
NOTE: PROCEDURE SURVEYMEANS処理(合計処理時間):
処理時間 0.00 秒
CPU時間 0.00 秒
WARNING: 出力'GeometricMeans'は作成されていません。出力オブジェクト名、ラベル、
パスが正しく記述されているかを確認してください。また、
要求した出力オブジェクトを作成するために、適切なプロシジャオプションが使われ
ているかも確認してください。たとえば、
NOPRINTオプションが使われていないことを確認してください。

/* ttestは0を含む幾何平均は,0を除いた値で計算してwarningを出す */
proc ttest data=hoge dist=lognormal;
  var a;
  ods output Statistics=out3;
run;

WARNING: 1 observations with invalid response values have been deleted. The response was less than or equal to zero for the
lognormal distribution.
NOTE: データセットWORK.OUT3は1オブザベーション、12変数です。
NOTE: PROCEDURE TTEST処理(合計処理時間):
処理時間 0.31 秒
CPU時間 0.17 秒
data _null_ ;
    set out3 ;
    putlog geommean= ;
run ;

GeomMean=4.4721
NOTE: データセットWORK.OUT3から1オブザベーションを読み込みました。
NOTE: DATAステートメント処理(合計処理時間):
処理時間 0.00 秒
CPU時間 0.00 秒

長々と書きましたが要はログを読んでくれ

2022年8月4日木曜日

データステップでズンドコキヨシ

 このクソ忙しいときに何をやっているんだろうか.いやでも煽られたならなぁ!しょうがねえよなぁ!?Twitterで「どうぞ」なんて言われた日にはやってみざるを得ない.しょうがないんよね.元のお題はこちら.ズンドコキヨシを自作関数で表現したとの話.2016年と昔の話ではあるが…一度見かけてまあだれかsasでもやるやろとスルーしていたのは内緒だ.

元のお題は「関数を作成」ですが今回は普通のデータステップでの記載としました.マクロ化や関数化はあきらめました.FCMPよく分からんし.以下にプログラムと実行結果を示しますが,このプログラムにオサレポイントは特にないです.5個発生させた0か1の2値をとる乱数にそれぞれ0なら「ズン」1なら「ドコ」を割り当て,5変数を結合した文字列が既定の並びになったらループを終了させているだけです.arrayで生成する変数は指定がなければ数値変数になることにお気を付けください.数値変数にはズンとドコの文字列の割り当てができないので終了条件が達成されずに無限ループになります(1敗

data _null_ ;
  /*初期値を与えてもいい*/
  /*call streaminit(123) ;*/

 do until (V_RET="キヨシ!!") ;

    /*任意の範囲の乱数を発生させるにはint((上限-下限+1)*乱数+下限)*/
    /*今回は0か1の2値が欲しいので上限=1,下限=0*/
    length HOGE1 - HOGE5 $4 ;
    array ch HOGE1 - HOGE5 ;
      do i = 1 to dim(ch) ;
        ch(i) = ifc( int( 2*rand("uniform") ) = 0 , "ズン" , "ドコ") ;
      end ;

    if cats(HOGE1 , HOGE2 , HOGE3 , HOGE4 , HOGE5)="ズンズンズンズンドコ" then V_RET="キヨシ!!" ;

    /*logに出力*/
    putlog HOGE1 HOGE2 HOGE3 HOGE4 HOGE5 V_RET ;

 end ;
run ;

/*実行結果 特に上の初期値を使ってないのでこの結果は再現できない*/
/*sas studioじゃ日本語が化けるのでローマ字でやってください*/

ズン ズン ドコ ドコ ドコ ドコ ズン ズン ズン ズン ドコ ズン ズン ドコ ドコ ズン ズン ドコ ズン ズン ドコ ドコ ズン ドコ ズン ズン ズン ドコ ズン ズン ズン ズン ドコ ズン ズン ズン ズン ズン ズン ズン ドコ ズン ズン ズン ズン ドコ ドコ ズン ズン ズン ドコ ドコ ドコ ズン ズン ズン ズン ドコ ドコ ズン ドコ ズン ドコ ドコ ドコ ドコ ドコ ズン ドコ ズン
ズン ズン ズン ズン ドコ キヨシ!! NOTE: DATAステートメント処理(合計処理時間):
処理時間 0.00 秒
CPU時間 0.00 秒


2022年4月27日水曜日

変更後のエスケープ文字を確認する話

 sasはエスケープ文字をデフォルトから変えることができます.例えばODS ESCAPECHAR="^" ;などと指定すると,[^]になりますね.自分で直近に作ったプログラムならエスケープ文字を何にしたかは大体見当がつきますが,他の人のを引き継いだり,随分前に作ったプログラムだとエスケープ文字が何かわからないことがあります.だいたい使いそうな文字をエスケープ文字に指定することは無い(はず)ですが、適当に変えるのも怖いので事前に確認はしたいところです

確認するには頑張ってエスケープ文字を指定している箇所を見に行くか,現在のエスケープ文字を格納している自動マクロ変数を見に行くかのどっちかですね.この自動マクロ変数は最近知りました.まだ便利だという状況に出会ったことが無いですがエスケープ文字の指定している箇所を探しに行ったことは何度かあるので,折があれば便利に使えるかなと楽しみです.

4     ODS ESCAPECHAR="^" ;

5    %put &=SYSODSESCAPECHAR ;

SYSODSESCAPECHAR=^

2021年3月9日火曜日

データセットを2回転置する話

 大したことない内容なのでタイトルをどうするかにすごく悩んでしまいました.

sasで集計した時,以下のように1群1obsのデータセットが出来たとします.ARM1の時の数と割合が横に持っていて,同じようにARM2の時が縦に積まれているような状態ですね.これをARM1の数と割合,ARM2の数と割合をそれぞれ同じobsに別の変数として持ちたい時がありますか?ありますよね?そう,あるんですよ

要は2obs3変数(群,数,割合)のデータセットを,1obs5変数(群,群1の数,群1の割合,群2の数,群2の割合)に変換すると言うことです.群1と2の数を転置して横持ち,群1と2の割合を転置して横持ち,最後にそれぞれのデータセットをmergeして1obs5変数のデータセットを作っているプログラムを見て,多分転置2回した方が早いんじゃないか...?と思ったのでここに供養します.


data hoge ;
  ARM = 1 ; COUNT = "1" ; PERCENT = "10%" ; KEY = 1 ; output ;
  ARM = 2 ; COUNT = "2" ; PERCENT = "20%" ; KEY = 1 ; output ;
run ;

proc sort data = hoge;
    by KEY ARM ;
run;

proc transpose data = hoge out = __hoge ;
    by KEY ARM ;
    var COUNT PERCENT ;
run;

proc transpose data = __hoge out = OUT ;
    by KEY;
    id _NAME_ ARM ;
    var COL1 ;
run;

これを実行すると,ARM KEY COUNT1 PERCENT1 COUNT2 PERCENT2の1obsが得られます.KEYの持たせ方を変えれば縦に一部を詰むこともできるのでお試し下さい…

2020年10月1日木曜日

マクロ変数が空であるかを条件にして分岐させる話

マクロ変数が空かそうでないかでプログラムを分岐できませんか?って聞かれました. ちょっと具体的な状況がピンとこないところですが,とりあえず分岐だけはしたのでここに供養します.
最近sasを触らずsuper excel おじさんになっていたので良い気分転換です.

分岐の時にマクロ変数を空白と比較してあげると空かどうかの判定に使えます.以下で言う所の %if &test = %then %do ; ですね. 以下のプログラムでマクロ変数 testが空の時は空とログに出してくれますし,そうじゃない時はからじゃないよってログに出してくれます.
そう言えば近頃のsasでは%ifが平文で使えるので,いちいちマクロを定義して実行って形にしなくて良く見やすいプログラムがかけますね.

option mprint;
%let test=;
%if &test = %then %do ;
data _null_ ;
  putlog "マクロ変数が空です" ;
run ;
%end ;

%if &test ^= %then %do ;
data _null_ ;
  putlog "マクロ変数が空じゃありません" ;
run ;
%end ;
option nomprint ;

実行ログは以下の通りです.マクロ変数が空なことを示すメッセージのみがログに出ています.
460 option mprint;
461 %let test=;
462
463 %if &test = %then %do ;
464 data _null_ ;
465 putlog "マクロ変数が空です" ;
466 run ;
 マクロ変数が空です
NOTE: DATAステートメント処理(合計処理時間): 処理時間 0.00 秒 CPU時間 0.00 秒
467 %end ;
468
469 %if &test ^= %then %do ;
470 data _null_ ;
471 putlog "マクロ変数が空じゃありません" ;
472 run ;
473 %end ;
474
475 option nomprint ;

このマクロ変数の戻り値に半角スペースって昔は通らなかったんですよね…いつか忘れましたが気づいたら通るようになっていました.便利になったもんです. 通らなかった時はマクロ変数をlength関数に入れて,戻り値が0だったらマクロ変数は空とする.みたいに書いていました.

2020年9月1日火曜日

32bit環境で作ったsasのformatを64bit環境にに移送する話

32bit環境で作ったSASのforamtはそのままだと64bit環境では使えません.データセットは互換あるのですが...使えないでは困る場面があるので移行する方法を紹介します.エンコードとかがすごーくややこしそうな局面と思えるのですが,今回は扱いません.

32bit環境で移行用のファイルを作成し,64bit環境で移行用ファイルを解凍します.移行には32bit環境での準備が要るっぽいので,format含めて移行が必要な場合は早めに準備しておいた方が良さそうですね...たまにcptファイルでデータを頂くことがあるので,その場合は以下の新環境で実行するプログラムを実行するとokです

エンコードの話とか怖くて触れたくない,出会いたくもない.ややこしいのが来ないよう日々祈るばかり.マジで勘弁してくれ

/*>>>>>---------- ここから旧環境で実行 ----------<<<<<*/
LIBNAME CAT32 "移行したいsasデータセットとsasのformatファイルのパス";
filename FILE64 '移行用ファイルを保存するパス\移行用ファイル名.cpt';

proc cport lib=CAT32 file=FILE64 ;
run ;

/*>>>>>---------- ここから新環境で実行 ----------<<<<<*/
libname CAT64 "移行後のデータセットを保存したいパス" ;
filename FILE64 "移行用ファイルを保存するパス\移行用ファイル名.cpt" ;

proc cimport lib = CAT64 file = FILE64 ;
run ;

2020年6月1日月曜日

複数の文字変数をつないだら文字切れするのを回避する話

ついに月一の更新すら怪しくなったブログはこちらです.
コロナで生活リズム変わったからね仕方ないね
 
先日私の身辺でちょっと話題になったことをテーマにします.
フリーテキストのコメントを一覧表かなんかに出力する時に,想定より長いコメントが来て途中までしか出力出来てなかったことがありました.一覧表と言えばページ数がかさむので,途中で切れてないか目視チェックは結構厳しいところがあります.安易に変数長を200byteまでにすると200を超える長さのコメントが来るとか,出力の際に()付けたりとか複数のコメントを結合させて1か所に出せとか言われるとぽろっと後ろが切れたりします.お薬の英語名と日本語名と記載名とかもそうですが…

さりとて切れてるのがあるとお叱りを受けてしまうので何とかしないといけません.出力用の変数が十分な長さを持っているかをlengthnで取得して長さを比較すれば確認できますね.
 
data _null_ ;
    /*----- 元のコメント -----*/
    A = "日本語のコメントだよ" ;
    B = "qwertyuiop";
 
    /*----- 出力用変数 -----*/
    length OUT $24 ;
    OUT = A ||  "/" ||  B ;
 
    /*----- 各変数の長さを求める -----*/
    V_A = lengthn(A) ;
    V_B = lengthn(B) ;
    V_OUT = lengthn(OUT) ;
 
    /*----- 元の変数と区切り文字の長さの合計が出力用変数より長い場合にobs番号をログに表示 -----*/
    if V_A + V_B + 1 > V_OUT then putlog "E" "RROR/" _n_ ;
run ;
 
ってタラタラ書きましたけどさあcat系の関数使ったら結合後の文字変数の長さが足りない時はwarning返るよねえ…文字の結合を||でやってんのかね…

241
242  data _null_ ;
243
244     /*----- 元のコメント -----*/
245     A = "日本語のコメントだよ" ;
246     B = "qwertyuiop";
247
248     length hoge hoge2 hoge3 $24 ;
249     hoge = cat(A, "/" , B) ;
250     hoge2 = cats(A, "/" , B) ;
251     hoge3 = catx("/" , A , B) ;
252
253  run ;
WARNING: CAT関数の呼び出しで、結果に割り当てられたバッファの長さが十分ではなかったため、すべての引数を連結して含めることができませんでした。
         正しい結果には31文字が含まれますが、実際の結果は、呼び出し環境によって、24文字に切り捨てられた、またはすべてブランクの可能性があります。
         どの引数以降が切り捨てられたかを次に示します。
NOTE: 第3引数(関数CAT('日本語のコメントだよ','/','qwertyuiop'):行 249 カラム 8)は無効です。
WARNING: CATS関数の呼び出しで、結果に割り当てられたバッファの長さが十分ではなかったため、すべての引数を連結して含めることができませんでした。
         正しい結果には31文字が含まれますが、実際の結果は、呼び出し環境によって、24文字に切り捨てられた、またはすべてブランクの可能性があります。
         どの引数以降が切り捨てられたかを次に示します。
NOTE: 第3引数(関数CATS('日本語のコメントだよ','/','qwertyuiop'):行 250 カラム 9)は無効です。
WARNING: CATX関数の呼び出しで、結果に割り当てられたバッファの長さが十分ではなかったため、すべての引数を連結して含めることができませんでした。
         正しい結果には31文字が含まれますが、実際の結果は、呼び出し環境によって、24文字に切り捨てられた、またはすべてブランクの可能性があります。
         どの引数以降が切り捨てられたかを次に示します。
NOTE: 第3引数(関数CATX('/','日本語のコメントだよ','qwertyuiop'):行 251 カラム 9)は無効です。
A=日本語のコメントだよ B=qwertyuiop hoge=  hoge2=  hoge3=  _ERROR_=1 _N_=1
NOTE: DATAステートメント処理(合計処理時間):
      処理時間           0.01 秒
      CPU時間            0.01 秒

2020年4月1日水曜日

includeで読み込んだプログラムファイルのパスとファイル名を取得する話

%includeで読み込んだプログラムのパスとファイル名を保持している自動マクロ変数を見つけたので紹介です.でもちょっと使い道が思いつかないので良い感じのがあればぜひ教えてください

変数の名前はSYSINCLUDEFILEDIR とSYSINCLUDEFILENAME です.長いので覚えられないですね.これをプログラムに仕込んでおいて,その仕込んだプログラムをincludeすると値が取得できます.平文で書いてもマクロ変数の中身は空なのでincludeしている時に値を取得している感じですかね.errorにならないのはとても良い

例えば以下の簡単なプログラムをtst.sasと名前を付けてincludeすると,ログにプログラム名とパスが表示されます.表示されるんですが...使い道思いつかないですね...ファイル名に関しては実行しているプログラム内で自分のファイル名を取れるのでそこは便利か...?

/*---------- includeしたプログラム(ファイル名はtst.sas) ----------*/
/*読み込み成功*/
%put &=SYSINCLUDEFILEDIR ;
%put &=SYSINCLUDEFILENAME ;

/*---------- include ----------*/
data _null_ ;
  rc = dlgcdir("piyopiyo") ;
run ;
%include "tst.sas" / source2;

/*---------- 実行ログ ----------*/
144  data _null_ ;
145
146  rc = dlgcdir("piyopiyo") ;
147
148  run ;
NOTE: 現在の作業ディレクトリは"piyopiyo"です。
NOTE: DATAステートメント処理(合計処理時間):
      処理時間           0.01 秒
      CPU時間            0.01 秒
171
172  %include "tst.sas" / source2 ;
NOTE: %INCLUDE (レベル1)ファイルtst.sasはファイルpiyopiyo\tst.sasです。
173 +/*読み込み成功*/
174 +%put &=SYSINCLUDEFILEDIR ;
SYSINCLUDEFILEDIR=piyopiyo        /*---------- includeしたプログラムのパス ----------*/
177 +%put &=SYSINCLUDEFILENAME ;
SYSINCLUDEFILENAME=tst.sas        /*---------- includeしたプログラムの名前 ----------*/

あと同じようなものにSYSINCLUDEFILEDEVICE とSYSINCLUDEFILEFILEREF もあります.
includeしたファイルが存在するデバイス名と,includeにfilenameステートメントで定義した文字列を指定した時に,そのfilerefを取得するものです.今回はfilenameしてないので空ですね

175 +%put &=SYSINCLUDEFILEDEVICE  ;
SYSINCLUDEFILEDEVICE=DISK       /*-------- ファイルはdiskにいる,それはそうだ. --------*/
176 +%put &=SYSINCLUDEFILEFILEREF ;
SYSINCLUDEFILEFILEREF=               /*---------- 今回は空 ----------*/

filename hogehoge "hoge.sas" ;としてから%include hogehogeを実行すると,SYSINCLUDEFILEFILEREFの値としてhogehogeが取れます.filefilerefってfileが続くのちょっとややこしい