SASでデータ加工時の備忘録

機械学習用のデータセットをSASで作成していて、いろいろな加工を試したのでそのメモです。

## 日付操作
### 日付を表す数値8桁のカラムから日にちを取得する
例: 20240310 → 10 を取得
```sql
proc sql;
    select
        date, /* 数値8桁のカラム */
        mod(date, 100) as day /* 末尾の2桁を取り出す */
    from
        table
    ;
quit;
```


### 日付型のカラムから日にちを取得する
例: 2024-03-10 → 10 を取得
```sql
proc sql;
    select
        date, /* 日付型のカラム */
        day(date) as day /* 日にちの2桁を取り出す */
    from
        table
    ;
quit;
```

### 数値型の日付（YYYYMMDD形式）のカラムから指定した年月との月数の差分を計算
例: 202312 - 20230301 → 9
```sql
%let target_ym = 202312;  /* YYYYMM形式 */

proc sql;
    select 
        date,  /* 数値8桁のカラム */
        intck('month', 
              input(put(date, 8.), yymmdd8.), 
              input(put(&target_ym, 6.) || '01', yymmddn8.)) as month_diff
    from
        table
    ;
quit;
```

### 数値型の日付（YYYYMMDD形式）のカラムから指定した年月での年齢を計算
例: 202312 - 19920806 → 31
```sql
%let target_ym = 202312;  /* YYYYMM形式 */

proc sql;
    select 
        birthday,
        int(yrdif(input(put(birthday, 8.), yymmdd8.), input(put(target_ym.01, 8.), yymmdd8.)), 'AGE') as age
    from
        table
    ;
quit;
```

### 日付変数の作成
※グローバル変数として定義するため以降のプロセスで利用可能
#### 未来月
例: 2312 → yymm1after = 2401 ～ yymm12after = 2412
```sql
%macro create_future_dates(yymm);

    %let year = %substr(&yymm, 1, 2);
    %let month = %substr(&yymm, 3, 2);
    %let base_date = %sysfunc(mdy(&month, 1, 20&year));

    %do i = 1 %to 12;
        %let future_date = %sysfunc(intnx(month, &base_date, &i, e));
        %let future_yymm = %sysfunc(putn(&future_date, yymmn6.));
        %global yymm_&i.after;
        %let yymm_&i.after = %substr(&future_yymm, 3, 4);
		%put yymm_&i.after = %substr(&future_yymm, 3, 4);
    %end;

%mend create_future_dates;

/* 実行 */
%create_future_dates(2312);
```

#### 過去月
例: 2312 → yymm1before = 2311 ～ yymm12before = 2212
```sql
%macro create_past_dates(yymm);

    %let year = %substr(&yymm, 1, 2);
    %let month = %substr(&yymm, 3, 2);
    %let base_date = %sysfunc(mdy(&month, 1, 20&year));

    %do i = 1 %to 12;
        %let past_date = %sysfunc(intnx(month, &base_date, -&i, e));
        %let past_yymm = %sysfunc(putn(&past_date, yymmn6.));
        %global yymm_&i.before;
        %let yymm_&i.before = %substr(&past_yymm, 3, 4);
		%put yymm_&i.before = %substr(&past_yymm, 3, 4);
    %end;

%mend create_past_dates;

/* 実行 */
%create_past_dates(2312);
```

## 文字列操作
### 数値型のカラムを取得し、頭に文字列をつけて連結し、文字列型として取得
例: 3 → num_3
```sql
proc sql;
    select
        catx('_', 'num', category) as category_str
    from
        table
    ;
quit;
```

### 文字列の数字を削除
例: 電話料金１０月分 → 電話料金月分
```sql
data modified_data;
    set original_data;
    modified_column = kcompress(column_name, '0123456789０１２３４５６７８９'); /* 前半の数字は半角、後半の数字は全角 */
run;
```

## 数値操作
### 傾きの作成（例として3変数）
```math
m = \frac{N \sum (xy) - \sum x \sum y}{N \sum (x^2) - (\sum x)^2}
```

例: [100, 110, 120] → 10 / [15, 10, 5] → -5
```sql
proc sql;
    select
        user_id,
        income1,
        income2,
        income3,
        (3 * (sum(1*income1 + 2*income2, 3*income3)) - (sum(1+2+3) * sum(income1 + income2 + income3))) / (3 * sum(1*1 + 2*2 + 3*3) - (sum(1+2+3))**2) as income_slope
    from
        table
    group by
        user_id
    ;
quit;
```

## その他
### インデックス用のカラムを作成し連番を付与
```sql
data index_dataset;
    set no_index_dataset;
    index = _N_ ; /* 上から順番に1,2,3,,を付与する */
run;
```

### 作成したデータセットをCSVエクスポート
```sql
libname mylib 'C:\your\library\path';

proc export data=your_dataset
    outfile='&mylib.\output.csv'
    dbms=csv
    replace;
run;
```

### 2つのデータセットが同一かを調べる（リファクタリング時など）
```sql
proc compare base=dataset_before compare=dataset_after;
run;
```