データクリーニング#
ここでは、妥当でない入力データを確認するためのいくつかの基本的な手法を確認します。データ処理タスクの最初であり最も重要なステップの1つは、値が正しいか、少なくともある一連のルールに従っているかを検証することです。たとえば、変数「GENDER」には2つの値しか存在しないことが期待され、インチで表される身長の変数には妥当な範囲内の値であることが期待されます。一部の重要な業務では、データ入力についてを二重の入力とその検証プロセスが必要とされます。これが行われるかどうかにかかわらず、一連のデータ検査を通してデータを確認することは有用です。
ここでは、Ron Codyの Data Cleaning 101の一部を使用します。例として、生データファイルPAITENTS.TXTに保存された小規模な患者データを使用します(README参照)。このデータセットには以下の変数が含まれています。
変数名 |
説明 |
変数タイプ |
妥当な値 |
---|---|---|---|
PATNO |
患者番号 |
文字列 |
数字 |
GENDER |
性別 |
文字列 |
‘M’ または ‘F’ |
VISIT |
来院日 |
MMDDYY10. |
有効な任意の日付 |
HR |
心拍数 |
数値 |
40 から 100 |
SBP |
収縮期血圧 |
数値 |
80 から 200 |
DBP |
拡張期血圧 |
数値 |
60 から 120 |
DX |
診断コード |
文字列 |
1 から 3 桁の数字 |
AE |
有害事象 |
文字列 |
‘0’ または ‘1’ |
data patients;
infile "./data/patients.txt" pad;
input @1 PATNO $3.
@4 GENDER $1.
@5 VISIT mmddyy10.
@15 HR 3.
@18 SBP 3.
@21 DBP 3.
@24 DX $3.
@27 AE $1.;
label PATNO = "PATIENT NUMBER"
GENDER = "GENDER"
VISIT = "VISIT DATE"
HR = "HEART RATE"
SBP = "SYSTOLIC BLOOD PRESSURE"
DBP = "DIASTOLIC BLOODPRESSURE"
DX = "DIAGNOSIS CODE"
AE = "ADVERSE EVENT?";
format visit mmddyy10.;
run;
proc print data = patients;
run;
OBS | PATNO | GENDER | VISIT | HR | SBP | DBP | DX | AE |
---|---|---|---|---|---|---|---|---|
1 | 001 | M | 11/11/1998 | 88 | 140 | 80 | 1 | 0 |
2 | 002 | F | 11/13/1998 | 84 | 120 | 78 | X | 0 |
3 | 003 | X | 10/21/1998 | 68 | 190 | 100 | 3 | 1 |
4 | 004 | F | 01/01/1999 | 101 | 200 | 120 | 5 | A |
5 | XX5 | M | 05/07/1998 | 68 | 120 | 80 | 1 | 0 |
6 | 006 | 06/15/1999 | 72 | 102 | 68 | 6 | 1 | |
7 | 007 | M | . | 88 | 148 | 102 | 0 | |
8 | 008 | F | 08/08/1998 | 210 | . | . | 7 | 0 |
9 | 009 | M | 09/25/1999 | 86 | 240 | 180 | 4 | 1 |
10 | 010 | F | 10/19/1999 | . | 40 | 120 | 1 | 0 |
11 | 011 | M | . | 68 | 300 | 20 | 4 | 1 |
12 | 012 | M | 10/12/1998 | 60 | 122 | 74 | 0 | |
13 | 013 | 2 | 08/23/1999 | 74 | 108 | 64 | 1 | |
14 | 014 | M | 02/02/1999 | 22 | 130 | 90 | 1 | |
15 | 002 | F | 11/13/1998 | 84 | 120 | 78 | X | 0 |
16 | 003 | M | 11/12/1999 | 58 | 112 | 74 | 0 | |
17 | 015 | F | . | 82 | 148 | 88 | 3 | 1 |
18 | 017 | F | 04/05/1999 | 208 | . | 84 | 2 | 0 |
19 | 019 | M | 06/07/1999 | 58 | 118 | 70 | 0 | |
20 | 123 | M | . | 60 | . | . | 1 | 0 |
21 | 321 | F | . | 900 | 400 | 200 | 5 | 1 |
22 | 020 | F | . | 10 | 20 | 8 | 0 | |
23 | 022 | M | 10/10/1999 | 48 | 114 | 82 | 2 | 1 |
24 | 023 | F | 12/31/1998 | 22 | 34 | 78 | 0 | |
25 | 024 | F | 11/09/1998 | 76 | 120 | 80 | 1 | 0 |
26 | 025 | M | 01/01/1999 | 74 | 102 | 68 | 5 | 1 |
27 | 027 | F | . | . | 166 | 106 | 7 | 0 |
28 | 028 | F | 03/28/1998 | 66 | 150 | 90 | 3 | 0 |
29 | 029 | M | 05/15/1998 | . | . | . | 4 | 1 |
30 | 006 | F | 07/07/1999 | 82 | 148 | 84 | 1 | 0 |
欠損データのチェック#
すでに見たように、SASには文字列データと数値データの欠損値が別々に存在します。
欠損の数値データはピリオド(.)で表されます
欠損の文字列データは空白(’ ‘)または空の文字列(‘’)で表されます
特定の値が欠損しているかどうかを確認するには、対応する欠損データ値と比較して = を使用するか、あるいは値が欠損しているかどうかをチェックし、欠損している場合は1、そうでない場合は0を返す missing 関数を使用できます。
他の欠損データ関数には以下のようなものがあります。
nmiss - 数値変数のリストを与えると、欠損データを含む変数の数をカウントします。
cmiss - 文字列変数または数値変数を与えると、欠損データを含む変数の数をカウントします。
欠損データをチェックするその他の便利な方法として、PROC FREQを使って文字列変数の欠損データ値の数をカウントしたり、nmissオプションを付けたPROC MEANSを使って数値変数の欠損値の数をカウントしたりできます。
例#
以下のプログラムは「GENDER」または 「VISIT」が欠損の行を表示します。
proc print data = patients;
where gender = ' ' or visit = .;
run;
OBS | PATNO | GENDER | VISIT | HR | SBP | DBP | DX | AE |
---|---|---|---|---|---|---|---|---|
6 | 006 | 06/15/1999 | 72 | 102 | 68 | 6 | 1 | |
7 | 007 | M | . | 88 | 148 | 102 | 0 | |
11 | 011 | M | . | 68 | 300 | 20 | 4 | 1 |
17 | 015 | F | . | 82 | 148 | 88 | 3 | 1 |
20 | 123 | M | . | 60 | . | . | 1 | 0 |
21 | 321 | F | . | 900 | 400 | 200 | 5 | 1 |
22 | 020 | F | . | 10 | 20 | 8 | 0 | |
27 | 027 | F | . | . | 166 | 106 | 7 | 0 |
代わりに、missing関数を使って「GENDER」または「VISIT」が欠損値かどうかをチェックすることもできます。
proc print data = patients;
where missing(gender) or missing(visit);
run;
OBS | PATNO | GENDER | VISIT | HR | SBP | DBP | DX | AE |
---|---|---|---|---|---|---|---|---|
6 | 006 | 06/15/1999 | 72 | 102 | 68 | 6 | 1 | |
7 | 007 | M | . | 88 | 148 | 102 | 0 | |
11 | 011 | M | . | 68 | 300 | 20 | 4 | 1 |
17 | 015 | F | . | 82 | 148 | 88 | 3 | 1 |
20 | 123 | M | . | 60 | . | . | 1 | 0 |
21 | 321 | F | . | 900 | 400 | 200 | 5 | 1 |
22 | 020 | F | . | 10 | 20 | 8 | 0 | |
27 | 027 | F | . | . | 166 | 106 | 7 | 0 |
例#
次の関数を使うコードでは、cmiss関数を使って完全ケース(どの変数にも欠損データがない行)を見つける方法を示しています。
data patients_cc;
set patients;
if cmiss(of _all_) ^= 0 then delete;
run;
proc print data = patients_cc;
run;
OBS | PATNO | GENDER | VISIT | HR | SBP | DBP | DX | AE |
---|---|---|---|---|---|---|---|---|
1 | 001 | M | 11/11/1998 | 88 | 140 | 80 | 1 | 0 |
2 | 002 | F | 11/13/1998 | 84 | 120 | 78 | X | 0 |
3 | 003 | X | 10/21/1998 | 68 | 190 | 100 | 3 | 1 |
4 | 004 | F | 01/01/1999 | 101 | 200 | 120 | 5 | A |
5 | XX5 | M | 05/07/1998 | 68 | 120 | 80 | 1 | 0 |
6 | 009 | M | 09/25/1999 | 86 | 240 | 180 | 4 | 1 |
7 | 002 | F | 11/13/1998 | 84 | 120 | 78 | X | 0 |
8 | 022 | M | 10/10/1999 | 48 | 114 | 82 | 2 | 1 |
9 | 024 | F | 11/09/1998 | 76 | 120 | 80 | 1 | 0 |
10 | 025 | M | 01/01/1999 | 74 | 102 | 68 | 5 | 1 |
11 | 028 | F | 03/28/1998 | 66 | 150 | 90 | 3 | 0 |
12 | 006 | F | 07/07/1999 | 82 | 148 | 84 | 1 | 0 |
ここでは、データセット内のすべての変数をcmiss関数の呼び出しの中でリストアップするために、特別なSAS変数 _ALL_ を使用しています。これにより、手動で変数をすべてリストアップしたり、変数リストを使ったりする必要なく、特定の行のすべての変数で欠損データをチェックできます。文字列変数と数値変数が混在しているので、cmissを使用していますが、数値変数しかなかった場合(または _NUMERIC_ を使って数値変数のみをチェックする場合)は、nmissを使うことができます。行が完全な場合には、欠損値の数は0になります。
妥当でない文字列値のチェック#
文字列変数のカテゴリ数が限られている場合、妥当でない入力をチェックする簡単な手法は、PROC FREQを使うことです。文字列変数の度数表を作成すると、観測された全カテゴリと出現頻度がリストアップされます。
例#
以下のプログラムはFREQプロシージャにより変数「GENDER」、「DX」、「AE」について妥当でない入力を確認します。 これらの変数について上で変数の説明と値の規則を記載した表があります。
title "Frequency Counts";
proc freq data = patients;
tables GENDER DX AE / nocum nopercent;
run;
FREQ プロシジャ
GENDER | |
---|---|
GENDER | 度数 |
欠損値の度数 = 1 | |
2 | 1 |
F | 14 |
M | 13 |
X | 1 |
DIAGNOSIS CODE | |
---|---|
DX | 度数 |
欠損値の度数 = 7 | |
1 | 7 |
2 | 2 |
3 | 3 |
4 | 3 |
5 | 3 |
6 | 1 |
7 | 2 |
X | 2 |
ADVERSE EVENT? | |
---|---|
AE | 度数 |
欠損値の度数 = 1 | |
0 | 18 |
1 | 10 |
A | 1 |
「GENDER」には2やXなどの誤ったコーディングの値があり、「DX」にはXという誤ったコーディングの値があり、「AE」にはAという誤ったコーディングの値があること、そしてすべてに少なくとも1つの欠損値があることがわかります。これらがどのようにコードされるべきだったのかを確認するため、患者データを再確認する必要があります。
データステップによる妥当でない文字列の特定#
期待されるパターンに合わないオブザベーションを特定するために、IFステートメントを使ったデータステップを用いることもできます。
例#
以下のプログラムはIFステートメントとPUT、VERIFY関数により、変数「GENDER」、「DX」、「AE」について妥当でないコード値を確認します。
title "Listing Invalid Input";
data _null_;
set patients;
file print; *Print to output window instead of LOG window;
***CHECK GENDER;
if GENDER not in ('F','M',' ') then
put PATNO= GENDER=;
***CHECK DX;
**Verify returns position of first character that is not ' ' or 1-9;
if verify(DX,' 0123456789') ^= 0 then
put PATNO= DX=;
***CHECK AE;
if AE not in ('0','1',' ') then
put PATNO= AE=;
run;
PATNO=002 DX=X PATNO=003 GENDER=X PATNO=004 AE=A PATNO=013 GENDER=2 PATNO=002 DX=X
データセットキーワード_NULL_を使うと、新しいデータセットを作成せずにデータセットPATIENTSからデータを読み込めます。FILE PRINTステートメントはPUTステートメントの出力をログウィンドウではなくアウトプットウィンドウに出力するよう指示しています。「GENDER」に期待される妥当ば値は ‘M’、’F’、’ ‘のみなので、これらの特定の値のみをIFで確認し、妥当でないな値が含まれる行を出力できます。「AE」についても同様に、妥当な値は’0’、’1’、’ ‘のみなので、それらの値のみをチェックします。「DX」については、0から9の数字と最大3桁の文字列のみが妥当であることがわかっているので、VERIFY関数を使って「DX」の中の0から9の数字または空白以外の最初の文字の位置を返します。「DX」に妥当な値しか含まれていない場合、VERIFY関数は0を返します。
データステップで誤ってコーディングされた文字値を特定する別の方法は、ユーザ定義のフォーマットを使うことです。
例#
以下のプログラムは「GNDER」、「DX」、「AE」について期待されない値を’misscoded’と変換するフォーマットを定義することで、誤ってコーディングされた行を特定します。
proc format;
value $GENDER 'F','M' = 'VALID'
' ' = 'MISSING'
OTHER = 'MISCODED';
value $DX '001' - '999' = 'VALID'
' ' = 'MISSING'
OTHER = 'MISCODED';
value $AE '0','1' = 'VALID'
' ' = 'MISSING'
OTHER = 'MISCODED';
run;
title "Listing Invalid Input";
data _null_;
set patients;
file print; *print to output window instead of log window;
***CHECK GENDER;
if put(GENDER, $GENDER.) = 'MISCODED'
then put PATNO= GENDER=;
***CHECK DX;
if put(DX, $DX.) = 'MISCODED'
then put PATNO= DX=;
***CHECK AE;
if put(AE, $AE.) = 'MISCODED'
then put PATNO= AE=;
run;
PATNO=002 DX=X PATNO=003 GENDER=X PATNO=004 AE=A PATNO=013 GENDER=2 PATNO=002 DX=X
PUT関数は実行時にフォーマットを適用し、変換された’MISCODED’または他の値が返ってきます。値がOTHERカテゴリーに分類され’MISCODED’となった場合には、「PATNO」とコードされた変数とその値が出力されます。
妥当でない数値のチェック#
数値データの妥当でないことをチェックする手法は、文字列データで使った手法とかなり異なります。数値変数がとりうる値は通常多岐にわたりますが、データエラーを特定するためにいくつかの手法があります。簡単な手法のひとつは、各数値変数の最大からと最小からいくつかの値を調べることです。収縮期血圧値で12または1200のような値があった場合、データ値の入力時または元のデータ収集フォームのいずれかでエラーが発生したことは確実です。
また、内的整合性の手法を使って、データエラーの可能性があるものを特定することもできます。ほとんどのデータ値が特定の範囲内に収まっている場合、その範囲から大きく外れた値はデータエラーである可能性があります。このセクションではこれらのアイデアに基づくプログラムを作成します。
数値変数に欠損値やその変数の有効範囲外の値があるかどうかを確認する最も簡単な方法は、PROC MEANSまたはPROC UNIVARIATEを使うことです。
例#
以下のプログラムでは、PROC MEANS を使用して、統計量nmiss、min、max を使用して、大きすぎる、小さすぎる、または欠落している 「HR」、「SBP」、「DBP」 の値があるか確認します。
title "CHECKING NUMERIC VARIABLES";
proc means data=patients n nmiss min max maxdec=0;
var hr sbp dbp;
run;
MEANS プロシジャ
変数 | ラベル | N | 欠損値の数 | 最小値 | 最大値 |
---|---|---|---|---|---|
HR
SBP
DBP
|
HEART RATE
SYSTOLIC BLOOD PRESSURE
DIASTOLIC BLOODPRESSURE
|
27
26
27
|
3
4
3
|
10
20
8
|
900
400
200
|
出力から、「HR」、「SBP」および「DBP」に妥当である範囲外の値と欠損値があることがはっきりとわかりますが、どの値が無効なのか、妥当でない値がいくつあるのかはわかりません。
文字変数と同様に、妥当でない入力がある行を特定する簡単な2つの方法があります。
期待される範囲外の値をチェックするためにIFステートメントを使う
誤ってコーディングされた値を特定するためにフォーマットを使う
例#
以下のプログラムはIFステートメントとフォーマットの2つの方法を使用して「HR」、「SBP」、「DBP」の妥当でない値を特定します。まずはIFステートメントのものから始めましょう。
title "LISTING OF INVALID DATA VALUES";
data _null_;
set patients;
file print; *send output to the output window;
*NOTE: WE WILL ONLY INPUT THOSE VARIABLES OF INTEREST;
*CHECK HR;
if (HR < 40 AND HR ^= .) or HR > 100
then put PATNO= HR=;
*CHECK SBP;
if (SBP < 80 AND SBP ^= .) or SBP > 200
then put PATNO= SBP=;
*CHECK DBP;
if (DBP < 60 AND DBP ^= .) or DBP > 120
then put PATNO= DBP=;
run;
PATNO=004 HR=101 PATNO=008 HR=210 PATNO=009 SBP=240 PATNO=009 DBP=180 PATNO=010 SBP=40 PATNO=011 SBP=300 PATNO=011 DBP=20 PATNO=014 HR=22 PATNO=017 HR=208 PATNO=321 HR=900 PATNO=321 SBP=400 PATNO=321 DBP=200 PATNO=020 HR=10 PATNO=020 SBP=20 PATNO=020 DBP=8 PATNO=023 HR=22 PATNO=023 SBP=34
注意点として、分割点より下の値をチェックするときは、その値が欠損でないことも確認する必要があります。なぜなら、欠損値(.)は不等式の中で負の無限大として解釈されるからです。
かわりの方法としてフォーマットを使うこともでき、次のプログラムではそれを示します。
proc format;
value HR_CK 40-100, . = 'OK'; *Values between 40 and 100 and . are OK;
value SBP_CK 80-200, . = 'OK'; *Value between 80 and 200 and . are OK;
value DBP_CK 60-120, . = 'OK'; *Value between 60 and 120 and . are OK;
run;
/* The PUT function applies the given format to the given variable */
data _null_;
set patients;
file print;
if put(HR,HR_CK.) ^= 'OK'
then put PATNO= HR=;
if put(SBP,SBP_CK.) ^= 'OK'
then put PATNO= SBP=;
if put(DBP,DBP_CK.) ^= 'OK'
then put PATNO= DBP=;
run;
PATNO=004 HR=101 PATNO=008 HR=210 PATNO=009 SBP=240 PATNO=009 DBP=180 PATNO=010 SBP=40 PATNO=011 SBP=300 PATNO=011 DBP=20 PATNO=014 HR=22 PATNO=017 HR=208 PATNO=321 HR=900 PATNO=321 SBP=400 PATNO=321 DBP=200 PATNO=020 HR=10 PATNO=020 SBP=20 PATNO=020 DBP=8 PATNO=023 HR=22 PATNO=023 SBP=34
このプログラムは非常にシンプルかつ効率的です。ユーザ定義フォーマットHR_CK、SBP_CK、DBP_CKはすべて、許容範囲内の値と欠損値に’OK’のフォーマットを割り当てます。データステップでは、PUT関数を使ってその値が有効範囲外かどうかをテストしています。例えば、心拍数の値が22のように40から100の範囲外または欠損値の場合、OKのフォーマットは割り当てられません。その結果、心拍数のPUT関数の値は’OK’とならず、IFステートメントの条件が真となります。そして、妥当でない値を出力するためのPUTステートメントが実行され、FILE PRINTによりアウトプットウィンドウに出力します。
これはもちろん、データのチェックの手法の基本的な紹介に過ぎません。より詳細については、Ron CodyのCody’s Data Cleaning Techniques Using SAS などを参照してください。
演習#
PROC IMPORTを使ってBike_Lanes.csvからデータセットbikeとして読み込みます。
データセットbikeには何行あり、そのうち完全ケースは何行ありますか? ヒント: cmiss(of ALL)を使ってデータセットにおいて0/1の変数を作り、その0/1変数をPROC MEANSで合計することで完全ケースの数がわかります。
変数「route」が欠損していない行のみを抽出して一時データセットhave_routeを作成します。そして、PROC FREQを使って変数「subType」の度数表(欠損値を含む)を作成してください。