精神病患者の判定しようとしたら前処理に時間がかかった話

この記事は[SRA Advent Calendar 2018](http://qiita.com/advent-calendar/2018/sra)の17日目の記事です。

関西事業部の安井です。
6月頃から有料の機械学習セミナー・ディープラーニングセミナーに通い、色々とノウハウを学びました。
復習も兼ねて、精神病患者の診断をしたいと思っていましたが、時間の都合上、前処理途中でタイムアップとなってしまいました...。
とりあえず、できたところまでは投稿しようと思います。

# 概要
データは Kaggle で公開されている下記のデータを使用します。

- kaggle の下記 URL からデータセットをダウンロード  
  https://www.kaggle.com/osmi/mental-health-in-tech-survey

このデータセットは精神面での健康及び精神疾患の頻度に対する考え方を調査したアンケート結果となります。なおアンケートは2014年にテクノロジー系の職場で実施されました。

# 目的
アンケートの結果から精神病かどうか判定します。

# データセット
分析をするにあたって、データ自信をどう扱うか、モデルに突っ込む前に色々と処理することが重要ですが(いわゆる前処理)、そのさらに前段階でより重要なことがあります。
データの特性(カラムの意味やどんな値か)を知ることが重要です。(今回この作業が思ったより時間がかかったため、分析までいかなかったわけですが...。)

- ファイル形式  
  - CSV ファイル
  - 1259行 x 27列
- カラムの意味
 
| カラム名                  | 意味 |
|:--------------------------|:------------|
| Timestamp                 | アンケートを提出した日時 |
| Age                       | 回答者の年齢 |
| Gender                    | 回答者の性別 |
| Country                   | 回答者の国 |
| state                     | アメリカに住んでいる場合、どの州に住んでいますか？ |
| self_employed             | 自営業ですか？ |
| family_history            | 親族の中に精神病を患った人はいますか？ |
| treatment                 | メンタルヘルスケアを受けたことはありますか？ |
| work_interfere            | 精神健康状態が悪いと仕事に影響しますか？ |
| no_employees              | 会社または職場の人数は？ |
| remote_work               | 労働時間の少なくとも50%以上はリモート(社外)で働いていますか？ |
| tech_company              | テクノロジー系の会社または組織ですか？ |
| benefits                  | あなたの雇用主はメンタルヘルスに対する利益(手当)を提供していますか？ |
| care_options              | あなたはあなたの雇用主が提供するメンタルヘルスケアに対するオプションについて認識していますか？ |
| wellness_program          | あなたの雇用主は従業員の健康問題の一つとしてメンタルヘルスについて議論したことがありますか？ |
| seek_help                 | あなたの雇用主はメンタルヘルスの問題について学ぶためのリソースや援助の探し方を提供していますか？ |
| anonymity                 | あなたがメンタルヘルスあるいは薬物乱用に対する治療を受けることを選択する場合、匿名性は保護されますか？ |
| leave                     | メンタルヘルスに対する治療のために傷病休暇を利用することはどれくらい簡単ですか？ |
| mental_health_consequence | あなたの雇用主に精神面での健康問題について話し合うとネガティブな結果になると思いますか？ |
| phys_health_consequence   | あなたの雇用主に身体面での健康問題について話し合うとネガティブな結果になると思いますか？ |
| coworkers                 | 職場の同僚に精神面での健康問題について話し合いたいと思いますか？ |
| supervisor                | 直属の上司に精神面での健康問題について話し合いたいと思いますか？ |
| mental_health_interview   | 就職候補先の面接で精神面での健康問題を提示しようと思いますか？ | 
| phys_health_interview     | 就職候補先の面接で身体面での健康問題を提示しようと思いますか？ |
| mental_vs_physical        | あなたの雇用主は精神的健康を身体的健康と同じくらい重視していると感じますか？ |
| obs_consequence           | あなたの職場で精神病になった同僚に対してネガティブな結果になったことを聞いたことや見たことはありますか？ |
| comments                  | 追加のメモ、コメント |

## 目的変数
目的変数は treatment (メンタルヘルスケアを受けたことはありますか？)とします。他の変数によって、treatment を予測できるように分析します。

## 基礎集計
```python
# csv ファイルの読み込み
df_data = pd.read_csv("./survey.csv")
print(df_data.columns)
# カラムの表示
"""
結果
Index(['Timestamp', 'Age', 'Gender', 'Country', 'state', 'self_employed', 'family_history', 'treatment', 'work_interfere', 'no_employees', 'remote_work', 'tech_company', 'benefits', 'care_options', 'wellness_program', 'seek_help', 'anonymity', 'leave', 'mental_health_consequence', 'phys_health_consequence', 'coworkers', 'supervisor', 'mental_health_interview', 'phys_health_interview', 'mental_vs_physical', 'obs_consequence', 'comments'], dtype='object')
"""
# 統計情報を確認
display(df_data.describe(include='all'))
# 結果は割愛

# 各カラムの中身を確認する。
for column in df_data.columns.values:
    print(column)
    print(df_data[column].unique())
"""
結果
Age
[ 37 44 32 31 33 35 39 42 23 29 36 27 46 41 34 30 40 38 50 24 18 28 26 22 19 25 45 21 -29 43 56 60 54 329 55 99999999999 48 20 57 58 47 62 51 65 49 -1726 5 53 61 8 11 -1 72]
Gender
['Female' 'M' 'Male' 'male' 'female' 'm' 'Male-ish' 'maile' 'Trans-female' 'Cis Female' 'F' 'something kinda male?' 'Cis Male' 'Woman' 'f' 'Mal' 'Male (CIS)' 'queer/she/they' 'non-binary' 'Femake' 'woman' 'Make' 'Nah' 'All' 'Enby' 'fluid' 'Genderqueer' 'Female ' 'Androgyne' 'Agender' 'cis-female/femme' 'Guy (-ish) ^_^' 'male leaning androgynous' 'Male ' 'Man' 'Trans woman' 'msle' 'Neuter' 'Female (trans)' 'queer' 'Female (cis)' 'Mail' 'cis male' 'A little about you' 'Malr' 'p' 'femail' 'Cis Man' 'ostensibly male, unsure what that really means']
Country
['United States' 'Canada' 'United Kingdom' 'Bulgaria' 'France' 'Portugal' 'Netherlands' 'Switzerland' 'Poland' 'Australia' 'Germany' 'Russia' 'Mexico' 'Brazil' 'Slovenia' 'Costa Rica' 'Austria' 'Ireland' 'India' 'South Africa' 'Italy' 'Sweden' 'Colombia' 'Latvia' 'Romania' 'Belgium' 'New Zealand' 'Zimbabwe' 'Spain' 'Finland' 'Uruguay' 'Israel' 'Bosnia and Herzegovina' 'Hungary' 'Singapore' 'Japan' 'Nigeria' 'Croatia' 'Norway' 'Thailand' 'Denmark' 'Bahamas, The' 'Greece' 'Moldova' 'Georgia' 'China' 'Czech Republic' 'Philippines']
state
['IL' 'IN' nan 'TX' 'TN' 'MI' 'OH' 'CA' 'CT' 'MD' 'NY' 'NC' 'MA' 'IA' 'PA' 'WA' 'WI' 'UT' 'NM' 'OR' 'FL' 'MN' 'MO' 'AZ' 'CO' 'GA' 'DC' 'NE' 'WV' 'OK' 'KS' 'VA' 'NH' 'KY' 'AL' 'NV' 'NJ' 'SC' 'VT' 'SD' 'ID' 'MS' 'RI' 'WY' 'LA' 'ME']
self_employed
[nan 'Yes' 'No']
family_history
['No' 'Yes']
treatment
['Yes' 'No']
work_interfere
['Often' 'Rarely' 'Never' 'Sometimes' nan]
no_employees
['6-25' 'More than 1000' '26-100' '100-500' '1-5' '500-1000']
remote_work
['No' 'Yes']
tech_company
['Yes' 'No']
benefits
['Yes' "Don't know" 'No']
care_options
['Not sure' 'No' 'Yes']
wellness_program
['No' "Don't know" 'Yes']
seek_help
['Yes' "Don't know" 'No']
anonymity
['Yes' "Don't know" 'No']
leave
['Somewhat easy' "Don't know" 'Somewhat difficult' 'Very difficult'
 'Very easy']
mental_health_consequence
['No' 'Maybe' 'Yes']
phys_health_consequence
['No' 'Yes' 'Maybe']
coworkers
['Some of them' 'No' 'Yes']
supervisor
['Yes' 'No' 'Some of them']
mental_health_interview
['No' 'Yes' 'Maybe']
phys_health_interview
['Maybe' 'No' 'Yes']
mental_vs_physical
['Yes' "Don't know" 'No']
obs_consequence
['No' 'Yes']
# comments は量が多いので割愛
"""
```

## 前処理
1. 年齢にありえない値が見られるので、外れ値とみなしてデータから除去します。  
   対象：-1726, -29, -1, 329, 99999999999
2. 性別が様々なので、M(男性)とF(女性)に統一します。  
   また、どちらか判断できないものは'O'とします。  
   M に変換する文字列 -> M, Male, male, m, Male-ish, maile, something kinda male?, Cis Male, Mal, Male (CIS), Make, Guy (-ish) ^_^, Male , Man, msle, Mail, cis male, Malr, Cis Man  
   F に変換する文字列 -> Female, female, Trans-female, Cis Female, F, Woman, f, queer/she/they, Femake, woman, Female, cis-female/femme, Trans woman, Female (trans), Female (cis), femail  
   O に変換する文字列 -> non-binary, Nah, All, Enby, fluid, Genderqueer, Androgyne, Agender, male leaning androgynous, Neuter, queer, A little about you, p, ostensibly male, unsure what that really means  
3. 欠損値の有無の確認  
   欠損値が見られる場合は、no_data で補います。
4. 使用しないカラムの削除
5. カテゴリー変数を全てダミー変数で置き換える

```python
# データ前処理
## 1. 外れ値の除去 (年齢が0歳以上99歳以下とする。)
df_data_new = df_data[(0<=df_data["Age"]) & (df_data["Age"]<=99)]
print(df_data_new["Age"].unique())
"""
# 結果
[37 44 32 31 33 35 39 42 23 29 36 27 46 41 34 30 40 38 50 24 18 28 26 22 19 25 45 21 43 56 60 54 55 48 20 57 58 47 62 51 65 49  5 53 61  8 11 72]
"""

## 2. 性別の名寄せ
df_data_new["Gender_Uni"] = df_data_new["Gender"].map({"M": "M", "Male":"M", "male":"M", "m":"M", "Male-ish":"M", "maile":"M", "something kinda male?":"M", "Cis Male":"M", "Mal":"M", "Male (CIS)":"M", "Make":"M", "Guy (-ish) ^_^":"M", "Male ":"M", "Man":"M", "msle":"M", "Mail":"M", "cis male":"M", "Malr":"M", "Cis Man":"M", "Female":"F", "female":"F", "Trans-female":"F", "Cis Female":"F", "Woman":"F", "f":"F", "F":"F", "queer/she/they":"F", "Femake":"F", "woman":"F", "Female":"F", "cis-female/femme":"F", "Trans woman":"F", "Female (trans)":"F", "Female (cis)":"F", "femail":"F", "Female ":"F", "non-binary":"O", "Nah":"O", "All":"O", "Enby":"O", "fluid":"O", "Genderqueer":"O", "Androgyne":"O", "Agender":"O", "male leaning androgynous":"O", "Neuter":"O", "queer":"O", "A little about you":"O", "p":"O", "ostensibly male, unsure what that really means":"O"})

print(df_data_new["Gender_Uni"].unique())
"""
# 結果
['F' 'M' 'O']
"""

display(df_data_new["Gender_Uni"].value_counts())
"""
# 結果
M    990
F    252
O     12
Name: Gender_Uni, dtype: int64
"""

##3. 欠損値の確認と対応
###確認
display(df_data_new.isnull().sum())
"""
Timestamp                       0
Age                             0
Gender                          0
Country                         0
state                         513
self_employed                  18
family_history                  0
treatment                       0
work_interfere                263
no_employees                    0
remote_work                     0
tech_company                    0
benefits                        0
care_options                    0
wellness_program                0
seek_help                       0
anonymity                       0
leave                           0
mental_health_consequence       0
phys_health_consequence         0
coworkers                       0
supervisor                      0
mental_health_interview         0
phys_health_interview           0
mental_vs_physical              0
obs_consequence                 0
comments                     1091
Gender_Uni                      0
dtype: int64
"""

### 欠損値を処理する
#### self_employed -> No_data
#### state -> No_data
#### work_interfere -> No_data
#### comments -> No_data
df_data_new = df_data_new.fillna({"self_employed":"No_data", "state":"No_data", "work_interfere":"No_data", "comments":"No_data"})

### 確認
display(df_data_new.isnull().sum())
"""
Timestamp                    0
Age                          0
Gender                       0
Country                      0
state                        0
self_employed                0
family_history               0
treatment                    0
work_interfere               0
no_employees                 0
remote_work                  0
tech_company                 0
benefits                     0
care_options                 0
wellness_program             0
seek_help                    0
anonymity                    0
leave                        0
mental_health_consequence    0
phys_health_consequence      0
coworkers                    0
supervisor                   0
mental_health_interview      0
phys_health_interview        0
mental_vs_physical           0
obs_consequence              0
comments                     0
Gender_Uni                   0
dtype: int64
"""

##4. 使用しないカラムの削除
### Timestamp, Gender, state, comments を削除する
### Timestamp: 回答した日時なので、精神病の判定に影響を与えるはずがない
### Gender: Gender_Uni を使用
### state: アメリカの地域によって、精神病判断に寄与する可能性はあるが、今回は使用しないことにする
### comments: 内容は判定に重要になりそうだが、今回は使用しないことにする
df_data_new = df_data_new.drop(["Timestamp", "Gender", "state", "comments"], axis=1)

##5. カテゴリー変数を全てダミー変数で置き換える
### どの変数をダミー変数とするか、確認。2値の場合は0/1でOK。
```

## 今後
ということで、今回は前処理途中で終了となってしまいました。当然前処理は大変なのですが、カラムの意味を理解するのにも思ったより時間がかかってしました。前処理では、Gender の名寄せが地味に大変でした。データの集計等も満足にできなかったので、もう少し余裕のあるときにまとめ直したいと思います。