record/taniguchi/deli_Machine learning_4


機械学習

20週から36週の妊婦健診データを補完して機械学習

妊婦健診データから20週-36週までのデータを欠損値のみを近似曲線で補完した上で、必要なデータを元にLGBMで学習モデルのAUCを算出した。
2020-04-03 分娩記録、助産録のかなりの説明変数を入れたがあまり関係なかった。
2020-04-06  分娩記録、助産録の中から下に示したneeded_col_listを限定して, やったがやはり分娩時年齢や、血糖などが関係していた. 意外と20週前後のデータも大事なのかもしれない。

import sys
import numpy as np
import pandas as pd
import glob
import datetime as dt
import math
import csv
import copy
from dfply import *
import time
import re
from pylab import rcParams
import heapq
import os
import category_encoders as ce

import matplotlib.pyplot as plt
import japanize_matplotlib #日本語表記にできる
%matplotlib inline

#妊婦健診、助産データなど全て入れる
preg_period_data = pd.read_csv("preg_period_data_20200305.csv", encoding='utf-8')
midwife_data = pd.read_csv("midwife_data.csv", encoding='utf-8')
delivary_data = pd.read_csv("delivary_data.csv", encoding='utf-8')

#列名max表示,重たくなるが仕方ない.
pd.set_option('display.max_columns', 2000)
pd.set_option('display.max_rows', 1000)#行の指定はこれ

preg_period_data_copy = preg_period_data.copy()

#次は、データ補完をするため、外れ値を一旦欠損値にする.
#以下子宮底から体重に到るまでのデータを数値化するために必要なスクリプト
def conv(elem):
  ret = None
   if type(elem) is str:
       elem = elem.replace("..", ".")#子宮底
       elem = re.sub("\+$", "", elem)#子宮底
       elem = re.sub("\[$", "", elem)#腹囲
       elem = re.sub("^\]", "", elem)#腹囲
       elem = re.sub("\*", "", elem)#腹囲
       elem = re.sub("\/", "", elem)#血圧下
       elem = re.sub("\]*", "", elem)#血圧下
       elem = re.sub(",", ".", elem)#体重
       
   try:
       ret = float(elem)
   except:
       print("Err [{}]".format(elem))
       return np.nan
   return ret

preg_period_data_copy['子宮底(cm)'] = preg_period_data_copy['子宮底(cm)'].map(conv).astype(np.float64)
preg_period_data_copy['腹囲(cm)'] = preg_period_data_copy['腹囲(cm)'].map(conv).astype(np.float64)
preg_period_data_copy['血圧上'] = preg_period_data_copy['血圧上'].map(conv).astype(np.float64)
preg_period_data_copy['血圧下'] = preg_period_data_copy['血圧下'].map(conv).astype(np.float64)
preg_period_data_copy['体重'] = preg_period_data_copy['体重'].map(conv).astype(np.float64)

#子宮底と腹囲は逆にしている可能性があるが、明らかにおかしな数値は一度欠損値にする。
#子宮底 11未満、50以上を外れ値として、欠損値入力。
preg_period_data_copy[preg_period_data_copy['子宮底(cm)'] < 11] = np.nan
preg_period_data_copy[preg_period_data_copy['子宮底(cm)'] > 50] = np.nan
#腹囲 50未満、150以上を外れ値として、欠損値入力。
preg_period_data_copy[preg_period_data_copy['腹囲(cm)'] < 50] = np.nan
preg_period_data_copy[preg_period_data_copy['腹囲(cm)'] > 150] = np.nan
#血圧上 60未満、180以上を外れ値として、欠損値入力。
preg_period_data_copy[preg_period_data_copy['血圧上'] < 60] = np.nan
preg_period_data_copy[preg_period_data_copy['血圧上'] > 180] = np.nan
#血圧下 20以下、120以上を外れ値として、欠損値入力。
preg_period_data_copy[preg_period_data_copy['血圧下'] < 20] = np.nan
preg_period_data_copy[preg_period_data_copy['血圧下'] > 120] = np.nan
#体重 35未満、130以上を外れ値として、欠損値入力。
preg_period_data_copy[preg_period_data_copy['体重'] < 35] = np.nan
preg_period_data_copy[preg_period_data_copy['体重'] > 130] = np.nan

#8回以上受診分娩を選別
profile_visit_count = preg_period_data_copy.groupby('プロファイル番号').count()
profile_8 = profile_visit_count[profile_visit_count['子宮底(cm)'] > 8]
select_index = list(profile_8.index)
preg_period_df_visit_8 = preg_period_data_copy[preg_period_data_copy['プロファイル番号'].isin(select_index)]

#補完したい変数を指定して、それに対して20週から36週までのデータを欠損値のみ補完した上で一括で出力する
select_col_list = ['子宮底(cm)', '腹囲(cm)' , '血圧上', '血圧下', '体重']

t1 = time.time()

#データの補完
preg_period_df_visit_8.drop_duplicates(subset=['プロファイル番号','週(計算)'], inplace=True) #週数が重複している行を削除
preg_period_df_visit_8.dropna(subset=['週(計算)'], inplace=True) #週数がnanも除く
preg_period_df_visit_8_20w = preg_period_df_visit_8.groupby('プロファイル番号').filter(lambda group: group['週(計算)'].min() < 20) #初診20w未満
#https://qiita.com/Settin/items/edf90a7b8a6bc0a4bc40 便利
res_fin_df = pd.DataFrame()

for col in select_col_list:
  preg_peridata = preg_period_df_visit_8_20w.dropna(subset=[col]) #目的の変数のnan除く
   profile_nums = preg_peridata['プロファイル番号'].unique()
   profile_nums = profile_nums.astype(int)
   
   df_input = pd.DataFrame(index=range(20,37), columns=[col] )
   
   class_groupby = preg_peridata.groupby('プロファイル番号')
   class_groupby.groups
   
   res_df = pd.DataFrame()
   tmp_df = pd.DataFrame()
   
   for i in profile_nums:
       data_tmp = class_groupby.get_group(i)

      values = data_tmp.loc[:, col]
       weeks =  data_tmp.loc[:, '週(計算)']
       
       raw_val = pd.concat([weeks, values], axis=1)
       raw_val.set_index('週(計算)', inplace = True)
       
       df_input[col] = raw_val

      x = weeks.values
       y = values.values
       
       res2=np.polyfit(x, y, 2)
       y2 = np.poly1d(res2)(x) #2次
       
       forcast_list = []
       
       for i in range(1,43):
           week = np.array(i)
           y = np.poly1d(res2)(week)
           forcast_list.append(y)
           
       forcast_val_df = pd.DataFrame(index=range(18,43), columns=[col] )
       forcast_val_df[col] = pd.Series(forcast_list[17:],  index =range(18,43))
       
       df_input = df_input.fillna(forcast_val_df)
       df_input_t = df_input.T
       res_df = res_df.append(df_input_t, ignore_index=False)
       
   res_df.reset_index(drop=True, inplace=True)
   
   profile_num_df = pd.DataFrame(profile_nums)
   profile_num_df.columns = ['プロファイル番号']
   
   res_df = pd.concat([res_df, profile_num_df], axis=1)
   res_df.set_index('プロファイル番号', inplace=True)

  res_fin_df = pd.concat([res_fin_df, res_df], axis=1)


#headerを再作成 repeatとrange*4の違いに気をつける
select_col_list = [s + '_' for s in select_col_list]

header_gw = np.repeat(select_col_list, 17)

header_list = []

repeat_list = list(range(20,37))
repeat_list = repeat_list*len(select_col_list)

for i, j in zip(header_gw, repeat_list):
  elem = i +'{0}週'.format(j)
   header_list.append(elem)
   
res_fin_df.columns = header_list #headerを一括で変更

t2 = time.time()
elapsed_time = t2-t1
print("処理所要時間:{}秒".format(elapsed_time))
処理所要時間:168.47422790527344秒

データ補完後の出力ファイルはres_fin_df.

preg_period_df_visit_8.drop_duplicates(subset=['プロファイル番号','週(計算)'], inplace=True) #週数が重複している行を削除
preg_period_df_visit_8.dropna(subset=['週(計算)'], inplace=True) #週数がnanも除く

preg_period_df_visit_8_20w = preg_period_df_visit_8.groupby('プロファイル番号').filter(lambda group: group['週(計算)'].min() < 20)

#no.repeatとlistの増幅の違いを把握すること!
select_col_list = ['子宮底(cm)', '腹囲(cm)' , '血圧上', '血圧下', '体重']
select_col_list = [s + '_' for s in select_col_list]

header_gw = np.repeat(select_col_list, 17)

header_list = []

repeat_list = list(range(20,37))
repeat_list = repeat_list*len(select_col_list)

for i, j in zip(header_gw, repeat_list):
  elem = i +'{0}週'.format(j)
   header_list.append(elem)


分娩記録、助産録をmergeする。
labor onsetのタイミングを計算するために日付のdataは、datetime型に変換する。
df_mid_deli = pd.merge(delivary_data, midwife_data, on='プロファイル番号')

df_mid_deli['母生年月日'] = pd.to_datetime(df_mid_deli['母生年月日'])
df_mid_deli['分娩記録 胎児番号1 経過 児娩出 日時'] = pd.to_datetime(df_mid_deli['分娩記録 胎児番号1 経過 児娩出 日時'])
df_mid_deli['分娩記録 経過 陣痛発来 日時'] = pd.to_datetime(df_mid_deli['分娩記録 経過 陣痛発来 日時'])
df_mid_deli['分娩予定日'] = pd.to_datetime(df_mid_deli['分娩予定日'])

labor_gas = df_mid_deli['分娩予定日'][0] - df_mid_deli['分娩記録 経過 陣痛発来 日時'][0]

df_mid_deli = df_mid_deli >> mutate(labor_gas = X['分娩予定日'] - X['分娩記録 経過 陣痛発来 日時']) >> mutate(age_days = X['分娩記録 胎児番号1 経過 児娩出 日時'] - X['母生年月日'])

df_mid_deli = df_mid_deli >> mutate(age_days = X.age_days.dt.days/365)
df_mid_deli['labor_gas'] = df_mid_deli['labor_gas'].dt.days

#分娩方法が予定帝王切開を除く   児1・分娩方法 が 予定帝切 以外  9952まで減る. 早産抜くと7817まで減る.
#児1・分娩胎位 分娩胎位 胎位 骨盤位除く 7803
#最初から誘発を除くのはどうするか 入院理由 コメントで誘発, 誘発分娩、計画無痛を除いて 7655 これは誘発が漏れている
#入院理由 陣痛発来 が1とすると4169が残る これで行くしかない
df_mid_deli = df_mid_deli >> filter_by(X['児1・分娩方法'] != "予定帝切") \
>> filter_by(X['labor_gas'] < 22) \
>> filter_by(X['児1・分娩胎位 分娩胎位 胎位'] != "骨盤位") \
>> filter_by(X['入院理由 コメント'] != "誘発") \
>> filter_by(X['入院理由 コメント'] != "誘発分娩") \
>> filter_by(X['入院理由 コメント'] != "計画無痛") \
>> filter_by(X['入院理由 陣痛発来'] == 1)

df_mid_deli_copy = df_mid_deli.copy()

df_mid_deli_copy.set_index('プロファイル番号', drop=True, inplace = True)


#必要な列の選択
need_col_list =['母身長', '非妊時体重(kg)', '飲酒', '妊娠前 喫煙 なし', '妊娠中喫煙\u3000受動喫煙', '妊娠前 喫煙 能動喫煙',
             '妊娠中喫煙\u3000なし', '妊娠中喫煙 能動喫煙', 'パートナー 喫煙', 'パートナー 飲酒','不妊治療\u3000なし',
              '不妊治療\u3000排卵誘発剤', '不妊治療\u3000AIH', '不妊治療\u3000IVF-ET', '不妊治療\u3000ICSI', '分娩前状況 経妊', '分娩前状況 経産',
              '分娩前状況 経産 早産回数', '分娩前状況 流産', '分娩前状況 中絶', 
               '母体基礎疾患(今回の妊娠) 母体基礎疾患 有無',  '産科合併症 後期 妊娠高血圧症候群 有無', '産科合併症 後期 妊娠高血圧症候群 Eo',
              '産科合併症 後期 妊娠高血圧症候群 S', '産科合併症 後期 妊娠高血圧症候群 H', '産科合併症 後期 妊娠高血圧症候群 P',
              '産科合併症 後期 妊娠高血圧症候群 Lo', '産科合併症 後期 妊娠高血圧症候群 h', '産科合併症 後期 妊娠高血圧症候群 p ',
              '母体感染症(今回の妊娠) 無有', '母体感染症(今回の妊娠) GBS', '母体感染症(今回の妊娠) クラミジアPCR', 
               '妊娠経過 妊娠経過 検査 血糖', '妊娠経過 妊娠経過 検査 50g-oGTT 空腹時', 
               '妊娠経過 妊娠経過 検査 50g-oGTT 60分', '妊娠経過 妊娠経過 検査 50g-oGTT 120分', 'labor_gas', 'age_days']

df_mid_deli_copy = df_mid_deli_copy[need_col_list]

#特定の文字列を置換
df_mid_deli_copy.replace('無', 0, inplace = True)
df_mid_deli_copy.replace('有', 1, inplace=True)
df_mid_deli_copy.replace('男', 0, inplace = True)
df_mid_deli_copy.replace('女', 1, inplace=True)
df_mid_deli_copy.replace('(+)', 1, inplace=True)
df_mid_deli_copy.replace('(-)', 0, inplace=True)

#ざっとnp.nanに置換
df_mid_deli_copy.replace(['検査取消', '' , '.', ' '] , np.nan, inplace=True) #本当はspaceがいくつあってもnanにする方法が欲しい

#妊娠経過 妊娠経過 検査 血糖系は文字列含まれてるので変換する
df_mid_deli_copy['妊娠経過 妊娠経過 検査 血糖'] = df_mid_deli_copy['妊娠経過 妊娠経過 検査 血糖'].astype(np.float64)
df_mid_deli_copy['妊娠経過 妊娠経過 検査 50g-oGTT 60分'] = df_mid_deli_copy['妊娠経過 妊娠経過 検査 50g-oGTT 60分'].astype(np.float64)
df_mid_deli_copy['妊娠経過 妊娠経過 検査 50g-oGTT 120分'] = df_mid_deli_copy['妊娠経過 妊娠経過 検査 50g-oGTT 120分'].astype(np.float64)

#欠損値nanにするコード
#これはそのまま
def outlier_2s(df):
  res_df = pd.DataFrame()
   
   for i in range(len(df.columns)):
       
       # 列を抽出する
       col= df.iloc[:,i]
       
       if col.dtype != 'float64': #煩雑だが、float型の外れ値を探す
           continue

      # 平均と標準偏差
       average = np.mean(col)
       sd = np.std(col)

      # 外れ値の基準点
       outlier_min = average - (sd) * 2
       outlier_max = average + (sd) * 2

      # 範囲から外れている値を除く
       col[col < outlier_min] = np.nan
       col[col > outlier_max] = np.nan
 
   #return df
   return print('作業完了')

outlier_2s(df_mid_deli_copy)


分娩記録、助産録のdataframeと妊婦健診のdataframeを結合する。
df_all_data = pd.concat([df_mid_deli_copy, res_fin_df], axis=1, join='inner')
df_all_data_copy = df_all_data.copy()

####この間にoheでone hot labelにしたいものがあったが、なぜかうまくいかない。

df_all_data_copy["labor_onset_class"] = df_all_data_copy['labor_gas'].apply(lambda x: 1 if x < 0 else 0 )

df_all_data_copy.reset_index(drop=True, inplace=True)

df_all_data_copy.drop('labor_gas', axis = 1, inplace=True)
df_all_data_copy.set_index('labor_onset_class', inplace=True)

#飲酒だけoeする
ce_oe = ce.OrdinalEncoder(cols = '飲酒', handle_unknown='impute')
# trainに含まれている要素がなくても変換可能
df_oe = ce_oe.fit_transform(df_all_data_copy)

機械学習の準備。
#学習データを準備する
train_df = df_oe[0:2200]
test_df = df_oe[2200:]

X_train = np.array(train_df)
X_test = np.array(test_df)

y_train = np.array(train_df.index)
y_test = np.array(test_df.index)

print(X_train.shape, y_train.shape)
print(X_test.shape, y_test.shape)

#LGBMで検証開始

import lightgbm as lgb
from sklearn import metrics

from sklearn.metrics import confusion_matrix
from sklearn.metrics import accuracy_score


train_data = lgb.Dataset(X_train, label = y_train)
eval_data = lgb.Dataset(X_test, label=y_test, reference= train_data)

params = {
'task': 'train',
'boosting_type': 'gbdt',
'objective': 'binary',
'metric': 'auc',
'verbose': 2,
}
gbm = lgb.train(
params,
train_data,
valid_sets=eval_data,
num_boost_round=10000,
verbose_eval=5,
)

y_pred = gbm.predict(X_test, num_iteration=gbm.best_iteration)
fpr, tpr, thresholds = metrics.roc_curve(y_test, y_pred)
auc = metrics.auc(fpr, tpr)
print(auc)
0.5529504327301337
plt.plot(fpr, tpr, label='ROC curve (area = %.2f)'%auc)
plt.legend()
plt.title('ROC curve')
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.grid(True)
省略

lgb.plot_importance(gbm, figsize=(12, 20))
col_list_gbm = train_df.columns

print(col_list_gbm[36])
print(col_list_gbm[34])
print(col_list_gbm[32])
print(col_list_gbm[0])
print(col_list_gbm[38])
print(col_list_gbm[39])
print(col_list_gbm[51])
print(col_list_gbm[87])
print(col_list_gbm[53])
print(col_list_gbm[94])
print(col_list_gbm[78])
print(col_list_gbm[71])

age_days
妊娠経過 妊娠経過 検査 50g-oGTT 60分
妊娠経過 妊娠経過 検査 血糖
母身長
子宮底(cm)_21週
子宮底(cm)_22週
子宮底(cm)_34週
血圧上_36週
子宮底(cm)_36週
血圧下_26週
血圧上_27週
血圧上_20週



31週から36週の妊婦健診データで機械学習(補完せずに)

# 31週から36週の妊婦健診データを欠損値補完せずに作り出す
t1 = time.time()

with open("preg_period_data_20200305.csv", "r") as f:
  
   reader = csv.reader(f)
   header = next(reader) #headerに一つだけ空の列が追加されている
   header = [s.replace('\u3000', '') for s in header] #読み込みの際の不必要な文字列の削除
   header.pop(0)
   select_header = header[6:16]
   
   header_gw = []
   select_header = [s + '_' for s in select_header]
   
   for i in range(31, 37):
       header_add = list(map(lambda x: x + '{0}週'.format(i), select_header))
       header_gw.extend(header_add)
       
   a = np.empty((midwife_data.shape[0], len(header_gw)))
   df = pd.DataFrame(a, columns=header_gw)
   df = pd.concat([midwife_data['プロファイル番号'], df], axis=1)
   
   profile_num = 0 #次のfor以下のif条件からスタートするため
   tmp_df = pd.DataFrame()#次のfor以下の最初のdfへの代入で空振りしないため
   
   for row in reader:
       
       if row[3] == '':#週数がない時、これは除外していいことを確認している
           pass
       
       else:
           if profile_num != int(float(row[0])):
               df[df['プロファイル番号'] == profile_num] = tmp_df  #新しいプロファイル番号の時になった時に今までのtmp_dfでdfを更新、最初だけ意味なし
               
               #プロファイル番号が更新される処理
               profile_num = int(float(row[0]))
               tmp_df = df[df['プロファイル番号'] == profile_num].copy()
               gw_num = int(float(row[3]))
               var_start_col = (gw_num-31)*10+1
               
               if gw_num > 36 or gw_num <31: 
                   continue
                   
               for i in range(10):
                   tmp_df.iloc[0, var_start_col+i] = row[7+i]
                   
           else:
               gw_num = int(float(row[3]))
               var_start_col = (gw_num-31)*10+1
                   
               if gw_num > 36 or gw_num <31:
                   continue
                       
               for i in range(10):
                   tmp_df.iloc[0, var_start_col+i] = row[7+i]

t2 = time.time()
elapsed_time = t2-t1
print("処理所要時間:{}".format(elapsed_time))

df.to_csv("res_20200406.csv", encoding="utf-8_sig", index=None) #index含まないようにしないとずれる!これ大事。
#この時点ですでに要素が文字列型になっている.
800分くらい。

少しデータをクリーニング
#ざっとnp.nanに置換
df.replace([0, '検査取消', '' , '.', ' '] , np.nan, inplace=True) #本当はspaceがいくつあってもnanにする方法が欲しい

#訂正検査がuniqueになっているか確認.
select_col_list = [s for s in df.columns if '尿' in s]
for i in select_col_list:
  print(df[i].unique())

結果をcopyして、妊婦健診で連続変数のものと、定性検査である要素がstrのもので分ける。
そして、外れ値などnp.nanに置換しておく。
df_preg_period = df.copy()

df_preg_period_float = df_preg_period.filter(regex='^(.*(子宮底|腹囲|血圧|体重)).*$', axis=1)
df_preg_period_str = df_preg_period.filter(regex='^(.*尿).*$', axis=1)
df_preg_period_float = pd.concat([df_preg_period['プロファイル番号'], df_preg_period_float], axis=1)

#展開したdf_oheにも変な文字列も混ざっている かつ、float型にする
for i in df_preg_period_float.columns:
  df_preg_period_float[i] = df_preg_period_float[i].map(conv_2).astype(np.float64)

#ここでも全ての外れ値を消す
def outlier_2s(df):

  for i in range(len(df.columns)):

      # 列を抽出する
       col = df.iloc[:,i]

      # 平均と標準偏差
       average = np.mean(col)
       sd = np.std(col)

      # 外れ値の基準点
       outlier_min = average - (sd) * 2
       outlier_max = average + (sd) * 2

      # 範囲から外れている値を除く
       col[col < outlier_min] = None
       col[col > outlier_max] = None

  return print('作業完了')

新しい妊婦健診dataframeにconcat.
new_preg_df = pd.concat([df_preg_period_float, df_preg_period_str], axis=1)

定性検査をone hot labelへ.
#定性検査をoheにする
select_col_list = [s for s in df.columns if '尿' in s]

ce_ohe = ce.OneHotEncoder(cols=select_col_list, handle_unknown='impute')
df_ohe = ce_ohe.fit_transform(new_preg_df)

ここからは、分娩記録、助産記録dataframeをmergeする.
df_mid_deli = pd.merge(delivary_data, midwife_data, on='プロファイル番号')

df_mid_deli['母生年月日'] = pd.to_datetime(df_mid_deli['母生年月日'])
df_mid_deli['分娩記録 胎児番号1 経過 児娩出 日時'] = pd.to_datetime(df_mid_deli['分娩記録 胎児番号1 経過 児娩出 日時'])
df_mid_deli['分娩記録 経過 陣痛発来 日時'] = pd.to_datetime(df_mid_deli['分娩記録 経過 陣痛発来 日時'])
df_mid_deli['分娩予定日'] = pd.to_datetime(df_mid_deli['分娩予定日'])

labor_gas = df_mid_deli['分娩予定日'][0] - df_mid_deli['分娩記録 経過 陣痛発来 日時'][0]

df_mid_deli = df_mid_deli >> mutate(labor_gas = X['分娩予定日'] - X['分娩記録 経過 陣痛発来 日時']) >> mutate(age_days = X['分娩記録 胎児番号1 経過 児娩出 日時'] - X['母生年月日'])

df_mid_deli = df_mid_deli >> mutate(age_days = X.age_days.dt.days/365)
df_mid_deli['labor_gas'] = df_mid_deli['labor_gas'].dt.days

df_mid_deli = df_mid_deli >> filter_by(X['児1・分娩方法'] != "予定帝切") \
>> filter_by(X['labor_gas'] < 22) \
>> filter_by(X['児1・分娩胎位 分娩胎位 胎位'] != "骨盤位") \
>> filter_by(X['入院理由 コメント'] != "誘発") \
>> filter_by(X['入院理由 コメント'] != "誘発分娩") \
>> filter_by(X['入院理由 コメント'] != "計画無痛") \
>> filter_by(X['入院理由 陣痛発来'] == 1)

megeしたdataframeのデータクリーニング。
注意点は、colnameがコピペでは認識できない文字列が含まれていたり、最後にspaceが入っていたりして、読み込めないものがある。
df_mid_deli_copy = df_mid_deli.copy()

#助産録、分娩録で必要な変数を選択. 中には変なスペースが入っているcolnameも
needed_col_list = ['プロファイル番号', '母身長', '非妊時体重(kg)', '不妊治療 なし', '不妊治療 排卵誘発剤', '不妊治療 AIH', '不妊治療 IVF-ET', '不妊治療 ICSI',
                '分娩前状況 経妊', '分娩前状況 経産', '分娩前状況 経産 早産回数', '分娩前状況 流産', '分娩前状況 中絶', 
               '母体基礎疾患(今回の妊娠) 母体基礎疾患 有無', '産科合併症 後期 妊娠高血圧症候群 有無', '母体感染症(今回の妊娠) GBS', 
                  '母体感染症(今回の妊娠) クラミジアPCR', '母体感染症(今回の妊娠) インフルエンザ', '児1・性別 ', '妊娠経過 妊娠経過 最終血液所見 Hb(g/dl)',
                 '妊娠経過 妊娠経過 検査 血糖', '妊娠経過 妊娠経過 検査 50g-oGTT 空腹時', '妊娠経過 妊娠経過 検査 50g-oGTT 60分',
                 '妊娠経過 妊娠経過 検査 50g-oGTT 120分', 'labor_gas', 'age_days']

df_mid_deli_copy = df_mid_deli_copy[needed_col_list]

df_mid_deli_copy.replace('無', 0, inplace=True)
df_mid_deli_copy.replace('有', 1, inplace=True)
df_mid_deli_copy.replace('女', 0, inplace=True)
df_mid_deli_copy.replace('男', 1, inplace=True)
df_mid_deli_copy.replace(['検査取消', '不明'] , np.nan, inplace=True)

df_mid_deli_copy.describe()
#これにより母身長に外れ値があることがわかる

df_mid_deli_copy.loc[df_mid_deli_copy['母身長'] >200 , '母身長'] = np.nan

#妊娠経過 妊娠経過 検査 血糖系は文字列含まれてるので変換する
df_mid_deli_copy['妊娠経過 妊娠経過 最終血液所見 Hb(g/dl)'] = df_mid_deli_copy['妊娠経過 妊娠経過 最終血液所見 Hb(g/dl)'].astype(np.float64)
df_mid_deli_copy['妊娠経過 妊娠経過 検査 血糖'] = df_mid_deli_copy['妊娠経過 妊娠経過 検査 血糖'].astype(np.float64)
df_mid_deli_copy['妊娠経過 妊娠経過 検査 50g-oGTT 空腹時'] = df_mid_deli_copy['妊娠経過 妊娠経過 検査 50g-oGTT 空腹時'].astype(np.float64)
df_mid_deli_copy['妊娠経過 妊娠経過 検査 50g-oGTT 60分'] = df_mid_deli_copy['妊娠経過 妊娠経過 検査 50g-oGTT 60分'].astype(np.float64)
df_mid_deli_copy['妊娠経過 妊娠経過 検査 50g-oGTT 120分'] = df_mid_deli_copy['妊娠経過 妊娠経過 検査 50g-oGTT 120分'].astype(np.float64)

最後に機械学習用のデータを準備する。
df_ohe.set_index('プロファイル番号', inplace=True, drop=True)
df_mid_deli_copy.set_index('プロファイル番号', inplace=True, drop=True)
df_preparation = pd.concat([df_mid_deli_copy, df_ohe], join='inner', axis=1)
df_preparation['labor_gas_cat'] = df_preparation['labor_gas'].apply(lambda x: 0 if x > 0 else 1)
df_preparation.reset_index(drop=True, inplace=True)
df_preparation.set_index('labor_gas_cat', drop=True, inplace=True)

df_preparation = df_preparation.drop('labor_gas', axis=1)

X_train = pd.DataFrame()
X_test = pd.DataFrame()

X_train = df_preparation[0:3500]
X_test = df_preparation[3500:]

y_train = np.array(X_train.index)
y_test = np.array(X_test.index)

X_train = np.array(X_train)
X_test = np.array(X_test)

train_data = lgb.Dataset(X_train, label = y_train)
eval_data = lgb.Dataset(X_test, label=y_test, reference= train_data)

LGBMのパラメータを決定して、学習開始する。
params = {
'task': 'train',
'boosting_type': 'gbdt',
'objective': 'binary',
'metric': 'auc',
'verbose': 2,
}
gbm = lgb.train(
params,
train_data,
valid_sets=eval_data,
num_boost_round=1000,
verbose_eval=5,
)
[975] valid_0's auc: 0.572194
[980] valid_0's auc: 0.572353
[985] valid_0's auc: 0.573279
[990] valid_0's auc: 0.573223
[995] valid_0's auc: 0.573569
[1000] valid_0's auc: 0.573943

AUCを出力データとした、学習がほとんど進まない。

y_pred = gbm.predict(X_test, num_iteration=gbm.best_iteration)
fpr, tpr, thresholds = metrics.roc_curve(y_test, y_pred)
auc = metrics.auc(fpr, tpr)
print(auc)
0.5739431350542462

plt.plot(fpr, tpr, label='ROC curve (area = %.2f)'%auc)
plt.legend()
plt.title('ROC curve')
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.grid(True)
20200406_31_36w.png

lgb.plot_importance(gbm, height=0.5, max_num_features= 10, figsize=(10, 10 ))
#図は省略する.
col_list_gbm = df_preparation.columns

print(col_list_gbm[21])
print(col_list_gbm[23])
print(col_list_gbm[0])
print(col_list_gbm[19])
print(col_list_gbm[18])
print(col_list_gbm[1])
print(col_list_gbm[53])
print(col_list_gbm[51])
print(col_list_gbm[52])
print(col_list_gbm[48])
print(col_list_gbm[50])
print(col_list_gbm[41])


妊娠経過 妊娠経過 検査 50g-oGTT 60分
age_days
母身長
妊娠経過 妊娠経過 検査 血糖
妊娠経過 妊娠経過 最終血液所見 Hb(g/dl)
非妊時体重(kg)
体重_36週
血圧上_36週
血圧下_36週
体重_35週
腹囲(cm)_36週
血圧上_34週

学習が進まず、AUC0.57程度のポンコツ予測モデルだが、実際に効いてそうな変数が出てきた。
ただ、これは、欠損値が少ないだけの可能性があり。

20週から36週の妊婦健診データで機械学習(補完せずに)

#20週から36週までの全てのデータを入れてみる
#20週から36週の妊婦健診データを欠損値補完せずに作り出す
t1 = time.time()

with open("preg_period_data_20200305.csv", "r") as f:
  
   reader = csv.reader(f)
   header = next(reader) #headerに一つだけ空の列が追加されている
   header = [s.replace('\u3000', '') for s in header] #読み込みの際の不必要な文字列の削除
   header.pop(0)
   select_header = header[6:16]
   
   header_gw = []
   select_header = [s + '_' for s in select_header]
   
   for i in range(20, 37):
       header_add = list(map(lambda x: x + '{0}週'.format(i), select_header))
       header_gw.extend(header_add)
       
   a = np.zeros((midwife_data.shape[0], len(header_gw)))
   df_2 = pd.DataFrame(a, columns=header_gw)
   df_2= pd.concat([midwife_data['プロファイル番号'], df_2], axis=1)
   
   profile_num = 0 #次のfor以下のif条件からスタートするため
   tmp_df = pd.DataFrame()#次のfor以下の最初のdfへの代入で空振りしないため
   
   for row in reader:
       
       if row[3] == '':#週数がない時、これは除外していいことを確認している
           pass
       
       else:
           if profile_num != int(float(row[0])):
               df_2[df_2['プロファイル番号'] == profile_num] = tmp_df  #新しいプロファイル番号の時になった時に今までのtmp_dfでdfを更新、最初だけ意味なし
               
               #プロファイル番号が更新される処理
               profile_num = int(float(row[0]))
               tmp_df = df_2[df_2['プロファイル番号'] == profile_num].copy()
               gw_num = int(float(row[3]))
               var_start_col = (gw_num-20)*10+1
               
               if gw_num > 36 or gw_num < 20: 
                   continue
                   
               for i in range(10):
                   if row[i+7] == '':
                       tmp_df.iloc[0, var_start_col+i] = np.nan#欠損値がものすごく小さな値が入っていたため条件分岐
                   else:
                       tmp_df.iloc[0, var_start_col+i] = row[7+i]
                   
           else:
               gw_num = int(float(row[3]))
               var_start_col = (gw_num-20)*10+1
                   
               if gw_num > 36 or gw_num < 20:
                   continue
                       
               for i in range(10):
                   if row[i+7] == '':
                       tmp_df.iloc[0, var_start_col+i] = np.nan 
                   else:
                       tmp_df.iloc[0, var_start_col+i] = row[7+i]

t2 = time.time()
elapsed_time = t2-t1
print("処理所要時間:{}".format(elapsed_time))

df_2.to_csv("res_20200406_20_36w_2.csv", encoding="utf-8_sig", index=None) #index含まないようにしないとずれる!これ大事。
#この時点ですでに要素が文字列型になっている.

2000分くらい。

ここからは先ほどと同じように。
df_copy = df_2.copy()

#ざっとnp.nanに置換
df_copy.replace([0, '検査取消', '' , '.', ' '] , np.nan, inplace=True) #本当はspaceがいくつあってもnanにする方法が欲しい

#正規表現で血圧再検、非妊時体重からの..を除く
df_copy = df_copy.filter(regex='^(?!.*非妊時).*$', axis=1)
df_copy = df_copy.filter(regex='^(?!.*血圧再検).*$', axis=1)

# 訂正検査の鉄板replaceを作成する
df_copy.replace(['(-)', '(-)', '-', '-', '(-) ', '.(-)'] , '(-)', inplace=True)
df_copy.replace(['±', '(+/-)', '(±)', '-+', '+-', '+-', '(+-)', '+/-', '(±)', '(+/-)', '±-', '(+-) ', '(+-)',"'(+/-)" ] , '(+-)', inplace=True)
df_copy.replace(['(+)', '+', '1+', '1+', '+', '1'] , '(1+)', inplace=True)
df_copy.replace(['2+', '++', '2+'] , '(2+)', inplace=True)
df_copy.replace(['3+', '+++', '3+'] , '(3+)', inplace=True)
df_copy.replace(['3+', '+++', '3+'] , '(3+)', inplace=True)

df_preg_period = df_copy.copy()

df_preg_period_float = df_preg_period.filter(regex='^(.*(子宮底|腹囲|血圧|体重)).*$', axis=1)
df_preg_period_str = df_preg_period.filter(regex='^(.*尿).*$', axis=1)
df_preg_period_float = pd.concat([df_preg_period['プロファイル番号'], df_preg_period_float], axis=1)

#展開したdf_oheにも変な文字列も混ざっている かつ、float型にする
for i in df_preg_period_float.columns:
  df_preg_period_float[i] = df_preg_period_float[i].map(conv).astype(np.float64)

#外れ値をnp.nanに置換する
outlier_2s(df_preg_period_float)

new_preg_df = pd.concat([df_preg_period_float, df_preg_period_str], axis=1)

#定性検査をoheにする
select_col_list = [s for s in df.columns if '尿' in s]

ce_ohe = ce.OneHotEncoder(cols=select_col_list, handle_unknown='impute')
df_ohe = ce_ohe.fit_transform(new_preg_df)

#後は1回目に作った助産録と分娩記録のmergeデータと結合させるだけ
df_ohe.set_index('プロファイル番号', inplace=True, drop=True)

df_preparation = pd.concat([df_mid_deli_copy, df_ohe], join='inner', axis=1)

df_preparation['labor_gas_cat'] = df_preparation['labor_gas'].apply(lambda x: 0 if x > 0 else 1)
df_preparation.reset_index(drop=True, inplace=True)
df_preparation.set_index('labor_gas_cat', drop=True, inplace=True)

df_preparation = df_preparation.drop('labor_gas', axis=1)

print(df_preparation.shape)

train dataとtest dataは同じ長さのarrayにしておけば、簡単にsplitしてくれる。

#train_test_split()でデータを準備する.
answer_label = np.array(df_preparation.index)
variable_array =np.array(df_preparation)

X_train, X_test, y_train, y_test = train_test_split(variable_array, answer_label, random_state=111, test_size=0.2)

train_data = lgb.Dataset(X_train, label = y_train)
eval_data = lgb.Dataset(X_test, label=y_test, reference= train_data)

params = {
'task': 'train',
'boosting_type': 'gbdt',
'objective': 'binary',
'metric': 'auc',
'verbose': 2,
}
gbm = lgb.train(
params,
train_data,
valid_sets=eval_data,
num_boost_round=10000,
verbose_eval=50
)
ここでもやはり学習は進まず。

y_pred = gbm.predict(X_test, num_iteration=gbm.best_iteration)
fpr, tpr, thresholds = metrics.roc_curve(y_test, y_pred)
auc = metrics.auc(fpr, tpr)
print(auc)
0.589253647586981

plt.plot(fpr, tpr, label='ROC curve (area = %.2f)'%auc)
plt.legend()
plt.title('ROC curve')
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.grid(True)

妊娠経過 妊娠経過 検査 50g-oGTT 60分
age_days
妊娠経過 妊娠経過 検査 血糖
母身長
非妊時体重(kg)
妊娠経過 妊娠経過 最終血液所見 Hb(g/dl)
体重_36週
血圧下_36週
血圧上_36週
腹囲(cm)_36週

lgb.plot_importance(gbm, height=0.5, max_num_features= 10,figsize=(10, 10 ))
20200407_20_36w_sigvar.png
col_list_gbm = df_preparation.columns

print(col_list_gbm[21])
print(col_list_gbm[23])
print(col_list_gbm[19])
print(col_list_gbm[0])
print(col_list_gbm[1])
print(col_list_gbm[18])
print(col_list_gbm[108])
print(col_list_gbm[107])
print(col_list_gbm[106])
print(col_list_gbm[105])
妊娠経過 妊娠経過 検査 50g-oGTT 60分
age_days
妊娠経過 妊娠経過 検査 血糖
母身長
非妊時体重(kg)
妊娠経過 妊娠経過 最終血液所見 Hb(g/dl)
体重_36週
血圧下_36週
血圧上_36週
腹囲(cm)_36週

どの分娩にも欠損値がないものが上位にきているだけの可能性がある。
やはり、欠損値が多いデータで戦うのは難しいのか?

この後、よかったtop10 変数だけで学習させたが、AUC 0.5となってしまった...

  • 最終更新:2020-04-08 12:32:11

このWIKIを編集するにはパスワード入力が必要です

認証パスワード