SPSS Modelerのデータ検査ノード（目的変数がカテゴリ型）をPythonで書き換える。

SPSS Modelerのデータ検査ノードをPythonで書き換えます。データの傾向が一覧でつかめるのでとても人気のあるノードです。目的変数がカテゴリ型が連続型かによって動きが異なるところがありますが、今回は目的変数がカテゴリ型である場合の書き換えを行います。

#0．データ

目的変数
Risk：信用リスク

説明変数
Age：年齢
Income：収入ランク
Credit_cards：クレジットカード枚数
Education：学歴
Car_loans:車のローン数



年齢や収入ランクから信用リスクを判定する2値分類のモデルを評価します。


#1m．①データ検査　Modeler版
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/99be9888-636c-303e-ac39-72d4435c328e.png)
まず、CSVの読み込み時にカテゴリ型のデータを文字列で読み込みます。これはデータに意図的に欠損値や空白データを混入させているためです。Car_loans、Credit_cards、Education、Income、Riskは文字列に上書きしています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/b46be4e1-4f1c-fcf3-e799-5403b57a59c0.png)

プレビューすると以下のように連続型の欠損値は\$null\$で表され、カテゴリ型の欠損値は空文字や空白文字で表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/eb1c3f47-b286-4222-df8b-ae783f97eea3.png)

次に、データ型ノードで説明変数、目的変数の定義を行います。
「値の読み込み」ボタンをクリックします。その後、Riskのロールを「対象」（目的変数の意味）を設定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/f9688f86-93c8-403c-6e72-4f0b25e67aae.png)

後はデータ検査ノードを実行するだけです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/e2e1388a-0d42-8e2e-c8ad-264f606489df.png)
サンプルグラフでは連続型のデータはヒストグラム、カテゴリ型のデータは度数の棒グラフを目的変数のRiskの値を元に色分けしてくれます。このグラフで各変数の分布と目的変数と各説明変数の関係性がぱっと見で把握できるのがとても良いところです。

また、連続型については最小、最大、平均値、標準偏差、歪度が集計されます。
そして、カテゴリ型についてはカテゴリ数が集計されます。
「有効」はNULLや空白値を除いた有効なデータ件数です。

次に欠損値検査タブを見てみます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/d96d85df-ba43-6e41-2021-9d27428c76f2.png)

ここでは、連続型については外れ値（3シグマ以上）、極値（5シグマ以上）の件数、ヌル値の件数が出ます。
また、カテゴリ型では、空文字（’’）、空白文字（’　’）の件数が出ます。なお、空白を別途定義している場合はその件数も出ます。
そして、ヌル値、空文字（’’）、空白文字（’　’）を除いたレコード数の「有効なレコ―ド」数と「非欠損値の割合」（有効レコード数/全体の件数）が表示されます。
やはり欠損値の状況が一覧で分かりやすく理解できます。

なおこのタブには、今回は取り上げませんが、外れ値や欠損値を補完することもできる機能もついています。


#1p．①データ検査　pandas版

## 設定
まず、カテゴリ型のデータをCategory型でCSVを読み込みます。

```python
df = pd.read_csv('credit_risk_missing.csv',dtype = {'CustID':'int', 'Age':'float', 'Car_loans':'category','Credit_cards':'category',
                                                    'Education':'category','Income':'category','Risk':'category'}
```

pandasではカテゴリ型もNaNで表示されました。空白文字（9行目のEducation）は空白で表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/62c092bb-8737-79fa-f305-01212052703a.png)

* 参考：pandasでデータを読み込むときに気を付けること(dtypeの指定) - Qiita 

https://qiita.com/haru1977/items/53c582eb9e264ccf8574

## グラフ表示
まず、複数のグラフを表示するためにnotebookの出力域を拡大します。

```python:
%%javascript
IPython.OutputArea.auto_scroll_threshold = 9999;
```

* 参考:ipython-notebook — ipythonノートブック出力ウィンドウのサイズを変更します 

https://www.webdevqa.jp.net/ja/ipython-notebook/ipython%E3%83%8E%E3%83%BC%E3%83%88%E3%83%96%E3%83%83%E3%82%AF%E5%87%BA%E5%8A%9B%E3%82%A6%E3%82%A3%E3%83%B3%E3%83%89%E3%82%A6%E3%81%AE%E3%82%B5%E3%82%A4%E3%82%BA%E3%82%92%E5%A4%89%E6%9B%B4%E3%81%97%E3%81%BE%E3%81%99/1041963108/


連続型のデータはヒストグラム、カテゴリ型のデータは度数の棒グラフを目的変数のRiskの値を元に色分けして表示します。
df[col].dtype.nameでCatagory型や連続型を判定しています。
連続型はたくさんの型がありうるので正規表現re.sub(r'[0-9]+', '', df[col].dtype.name) in ['int','float']をつかって判定しています。

棒グラフとヒストグラムの書き方は以下の記事も参考にしてください。

https://qiita.com/kawada2017/items/8da12e49405f01270abb

https://qiita.com/kawada2017/items/e0a32ec84dd771dd7b89

```python:グラフ表示
import re
targetcol='Risk'
for col in df.columns:
    
    if df[col].dtype.name=='category':
        dfcross=pd.crosstab(df[col],df[targetcol])
        dfcross=dfcross.reindex(['1','0'],axis="columns")
        dfcross.plot.bar(stacked=True,title=col)
    elif re.sub(r'[0-9]+', '', df[col].dtype.name) in ['int','float']:
        nums=[]
        targets=df[targetcol].cat.categories.sort_values (ascending=False)
        for i,target in enumerate(targets):
            nums.append(df.query(targetcol+'==\'{0}\''.format(target))[col])
        plt.title(col)
        plt.hist(nums,bins=25, stacked=True,label=targets.astype('str'))
        plt.legend()
        plt.figure()
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/6cfb0d15-22b1-78f0-e725-e2908baa0bfb.png)



* 参考：python - Check if dataframe column is Categorical - Stack Overflow 

https://stackoverflow.com/questions/26924904/check-if-dataframe-column-is-categorical

* 参考：Python で文字列から数字を削除する | Delft スタック 

https://www.delftstack.com/ja/howto/python/remove-numbers-from-string-python/

## 列の要約
連続型変数については最小、最大、平均値、標準偏差はdescribe()で計算できます。

```python:連続型の要約
df.describe()
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/4dcb0d08-41b5-e796-f1e9-e12fdaf91228.png)

Modelerと違って4分位のパーセンタイル値がでるのは便利です。ただし、歪度は別途計算する必要があります。
歪度はskew()で計算します。カテゴリ型の列についても、値としてはほとんど参考にはなりませんが、計算されています。

```python:歪度
df.skew()
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/be987d05-ebc0-aff9-23ec-d3c408003576.png)

カテゴリ型変数についてカテゴリ数を計算してみます。連続型の変数についてもカウントされています。Modelerは空文字をカテゴリとしてカウントしていますが、nunique()は空文字はNullとして無視していますので、Modelerのカテゴリ数とは異なります。後程欠損値のところで詳しく解説します。

```python:カテゴリ数
df.nunique()
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/3011dbfe-0b73-c09a-8d0e-baea7eeb3e19.png)

* 参考：pandasで歪度(Skewness)と尖度(Kurtosis)を計算 - Qiita 

https://qiita.com/ymmy02/items/36e708d3d9e5e4e6a457

## 外れ値、極値
Modelerでは外れ値を3シグマ以上、極値を5シグマ以上で計算しているので同様に計算します。数値列だけが対象になるのでre.sub(r'[0-9]+', '', df[col].dtype.name) in ['int','float']で判定しています。

件数は式.sum()で計算しています。

数値列だけが対象になるので、データフレームの件数は可変です。そのため、Seriesオブジェクトをつくってからappendしています。

```python:外れ値、極値
df1 = pd.DataFrame(index=[], columns=['外れ値','極値'])
for col in df.columns:
    if re.sub(r'[0-9]+', '', df[col].dtype.name) in ['int','float']:
        limit_low3=df[col].mean()-df[col].std()*3
        limit_high3=df[col].mean()+df[col].std()*3
        limit_low5=df[col].mean()-df[col].std()*5
        limit_high5=df[col].mean()+df[col].std()*5
        hazurechi=((df[col] <limit_low3)|(df[col] >limit_high3)).sum()
        kyokuchi=((df[col] <limit_low5)|(df[col] >limit_high5)).sum()
        df1=df1.append(pd.Series([hazurechi-kyokuchi,kyokuchi],index=df1.columns,name=col))
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/b315b18f-18a0-5db6-a9d2-8952ed0dd385.png)

* 参考：Python初心者向け：四分位数/標準偏差を用いた外れ値の除外 | happy analysis 

https://happy-analysis.com/python/python-topic-outlier.html

* 参考：Pandasにて条件にあったカウント（count）を行う方法【value_countsの使い方など】 | ウルトラフリーダム 

https://life-freedom888.com/pandas-count/

* 参考：Pythonで、空のDataFrameにindexとセットで1行追加する | ITを使っていこう 

https://it-ojisan.tokyo/python-dataframe-append/

## 欠損値
欠損値はisnull().sum()で数えることができます。

```python:欠損値1
df.isnull().sum()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/63f52a5e-1b5b-2138-4a36-742f5752d82d.png)

しかしながら、Modelerとは欠損値の考え方が違っているのでカウント数が異なります。

* Modelerはデータが存在しない場合、連続値についてはNULL値としてカウントし、カテゴリ型については空文字としてカウントします。pandas（というかnumpy）は空文字もNULLとして扱います。
* Modelerは’　’のような空白文字は欠損値として扱います。pandasはデータとして扱います。

上のModelerの仕様と同じようにpandasで欠損値を計算するには以下のようになります。
空白文字はdf[col].str.match(r'^ +$'))).sum()というように正規表現でカウントしています。

```python:欠損値2
#欠損値2
colnames=['非欠損値の割合','有効なレコード','ヌル値','空文字','空白文字']
dfcol=pd.DataFrame(index=df.columns,columns=colnames)


#ヌル
for i,col in enumerate(dfcol.index):
    if df[col].dtype.name=='category':
        dfcol.iloc[i,colnames.index('ヌル値')]=0
    elif re.sub(r'[0-9]+', '', df[col].dtype.name) in ['int','float']:
        dfcol.iloc[i,colnames.index('ヌル値')]=df[col].isnull().sum()
#空文字列
for i,col in enumerate(dfcol.index):
    if df[col].dtype.name=='category':
        dfcol.iloc[i,colnames.index('空文字')]=df[col].isnull().sum()
    elif re.sub(r'[0-9]+', '', df[col].dtype.name) in ['int','float']:
        dfcol.iloc[i,colnames.index('空文字')]=0
#空白文字
for i,col in enumerate(dfcol.index):
    if df[col].dtype.name=='category':
        dfcol.iloc[i,colnames.index('空白文字')]=((df[col].str.match(r'^ +$'))).sum()+dfcol.iloc[i,colnames.index('空文字')]
    elif re.sub(r'[0-9]+', '', df[col].dtype.name) in ['int','float']:
        dfcol.iloc[i,colnames.index('空白文字')]=0
#有効なレコード
dfcol['有効なレコード']=len(df)-(dfcol['ヌル値']+dfcol['空白文字'])
dfcol['非欠損値の割合']=dfcol['有効なレコード']/len(df)*100```
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/099dc2dc-3293-b416-73f0-41aa547d567c.png)



#5. サンプル
サンプルは以下に置きました。

ストリーム
https://github.com/hkwd/200611Modeler2Python/blob/master/dataaudit/dataaudit.str?raw=true
notebook
https://github.com/hkwd/200611Modeler2Python/tree/master/dataaudit/dataaudit.ipynb
データ
https://raw.githubusercontent.com/hkwd/200611Modeler2Python/master/data/credit_risk_missing.csv

■テスト環境
Modeler 18.3
Windows 10 64bit
Python 3.8.10
pandas 1.0.5



#6. 参考情報
データ検査ノード

https://www.ibm.com/docs/ja/spss-modeler/18.3.0?topic=nodes-data-audit-node