Home AI Pima Indians Diabetes Database
Post
Cancel

AI Pima Indians Diabetes Database

Pima Indians Diabetes Database를 분석한 것을 적어보려 한다.

데이터 셋

1
2
df = pd.read_csv("./data/diabetes.csv", encoding="utf-8")
df.shape
1
(768, 9)

df의 데이터에는 768개의 행과 9개의 열로 구성되어 있는 것을 확인할 수 있다.

1
2
col = df.columns
print(col)
1
2
3
Index(['Pregnancies', 'Glucose', 'BloodPressure', 'SkinThickness', 'Insulin',
       'BMI', 'DiabetesPedigreeFunction', 'Age', 'Outcome'],
      dtype='object')

Pregnancies: 임신 횟수 Glucose: 포도당 부하 검사 수치 BloodPressure: 혈압 SkinThickness: 팔 삼두근 뒤쪽의 피하지방 측정값 Insulin: 혈청 인슐린 BMI: 체질량지수 DiabetesPedigreeFunction: 당뇨 내력 가중치 값 Age: 나이 Outcome: 당뇨병 여부

데이터들을 한번 확인해보자.

1
2
df.info()
df.describe()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 768 entries, 0 to 767
Data columns (total 9 columns):
 #   Column                    Non-Null Count  Dtype  
---  ------                    --------------  -----  
 0   Pregnancies               768 non-null    int64  
 1   Glucose                   768 non-null    int64  
 2   BloodPressure             768 non-null    int64  
 3   SkinThickness             768 non-null    int64  
 4   Insulin                   768 non-null    int64  
 5   BMI                       768 non-null    float64
 6   DiabetesPedigreeFunction  768 non-null    float64
 7   Age                       768 non-null    int64  
 8   Outcome                   768 non-null    int64  
dtypes: float64(2), int64(7)
memory usage: 54.1 KB
PregnanciesGlucoseBloodPressureSkinThicknessInsulinBMIDiabetesPedigreeFunctionAgeOutcome
count768.000000768.000000768.000000768.000000768.000000768.000000768.000000768.000000768.000000
mean3.845052120.89453169.10546920.53645879.79947931.9925780.47187633.2408850.348958
std3.36957831.97261819.35580715.952218115.2440027.8841600.33132911.7602320.476951
min0.0000000.0000000.0000000.0000000.0000000.0000000.07800021.0000000.000000
25%1.00000099.00000062.0000000.0000000.00000027.3000000.24375024.0000000.000000
50%3.000000117.00000072.00000023.00000030.50000032.0000000.37250029.0000000.000000
75%6.000000140.25000080.00000032.000000127.25000036.6000000.62625041.0000001.000000
max17.000000199.000000122.00000099.000000846.00000067.1000002.42000081.0000001.000000
1
df.isnull().sum()
1
2
3
4
5
6
7
8
9
10
Pregnancies                 0
Glucose                     0
BloodPressure               0
SkinThickness               0
Insulin                     0
BMI                         0
DiabetesPedigreeFunction    0
Age                         0
Outcome                     0
dtype: int64

데이터들을 확인해 봤을 때 NaN값은 존재하지 않는 것으로 보인다. 하지만 Glucose, SkinThickness, Insulin 등의 수치에서 0값(결측치)이 들어 가 있는 것이 확인된다.

1
2
3
4
5
6
7
is_null = df.iloc[:,1:5] #결측치가 존재할 것 같은 값들
sns.heatmap(is_null == 0)

is_null = is_null.replace(0, np.nan)
temp = is_null.isnull().mean()
temp    

1
2
3
4
5
Glucose          0.006510
BloodPressure    0.045573
SkinThickness    0.295573
Insulin          0.486979
dtype: float64

Glucose(포도당) 결측치 거의 없음 약 0.6% BloodPressure(혈압) 결측치 약 5% SkinThickness(팔 삼두근 뒤쪽의 피하지방 측정값) 결측치 약 30% Insulin(인슐린) 결측치 약 49%

Insulin과 SkinThickness에서 결측치가 많다는 것을 알 수 있다.

결측치 처리

결측치 처리 방법은 결측치에 평균 값을 대입해 결측치를 처리하였다.

1
2
3
4
5
6
7
8
insulin_mean = df['Insulin'].mean()
skin_mean = df['SkinThickness'].mean()
df['Insulin'] = df['Insulin'].replace(0, np.nan) # 인슐린 결측치 처리
df['Insulin'].fillna(insulin_mean, inplace=True)
df['SkinThickness'] = df['SkinThickness'].replace(0, np.nan) # 스킨 결측치 처리
df['SkinThickness'].fillna(skin_mean, inplace=True)
df.info()
df.describe()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 768 entries, 0 to 767
Data columns (total 9 columns):
 #   Column                    Non-Null Count  Dtype  
---  ------                    --------------  -----  
 0   Pregnancies               768 non-null    int64  
 1   Glucose                   768 non-null    int64  
 2   BloodPressure             768 non-null    int64  
 3   SkinThickness             768 non-null    float64
 4   Insulin                   768 non-null    float64
 5   BMI                       768 non-null    float64
 6   DiabetesPedigreeFunction  768 non-null    float64
 7   Age                       768 non-null    int64  
 8   Outcome                   768 non-null    int64  
dtypes: float64(4), int64(5)
memory usage: 54.1 KB
PregnanciesGlucoseBloodPressureSkinThicknessInsulinBMIDiabetesPedigreeFunctionAgeOutcome
count768.000000768.000000768.000000768.000000768.000000768.000000768.000000768.000000768.000000
mean3.845052120.89453169.10546926.606479118.66016331.9925780.47187633.2408850.348958
std3.36957831.97261819.3558079.63124193.0803587.8841600.33132911.7602320.476951
min0.0000000.0000000.0000007.00000014.0000000.0000000.07800021.0000000.000000
25%1.00000099.00000062.00000020.53645879.79947927.3000000.24375024.0000000.000000
50%3.000000117.00000072.00000023.00000079.79947932.0000000.37250029.0000000.000000
75%6.000000140.25000080.00000032.000000127.25000036.6000000.62625041.0000001.000000
max17.000000199.000000122.00000099.000000846.00000067.1000002.42000081.0000001.000000

이상치 제거

결측치와 마찬가지로 Insulin과 SkinThickness의 이상치를 처리하였다.

1
2
3
4
5
6
7
8
9
10
11
12
def _(df, column, threshold = 1.5):
    Q1 = df[column].quantile(0.25)
    Q3 = df[column].quantile(0.75)
    IQR = Q3 - Q1
    lower_bound = Q1 - threshold * IQR
    upper_bound = Q3 + threshold * IQR
    df_no_outliers = df[(df[column] >= lower_bound) & (df[column] <= upper_bound)]
    return df_no_outliers
columns = df.iloc[:,3:5].columns
for col in columns:
    df = _(df, col)

학습 및 결과

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
def get_clf_eval(y_test, pred=None, pred_proba=None):
    confusion = confusion_matrix(y_test, pred)
    accuracy = accuracy_score(y_test, pred)
    precision = precision_score(y_test, pred)
    recall = recall_score(y_test, pred)
    f1 = f1_score(y_test, pred)
    roc_auc = roc_auc_score(y_test, pred)
    
    print('오차행렬')
    print(confusion)
    print('정확도: {0:.4f}, 정밀도: {1:.4f}, 재현율: {2:.4f}, F1: {3:.4f}, AUC:{4:.4f}'
          .format(accuracy, precision, recall, f1, roc_auc))

# 피처 데이터 세트 X, 레이블 데이터 세트 y를 추출
# 맨 끝이 Outcome 칼럼으로 레이블 값임, 칼럼 위치 -1을 이용해 추출
X = df.iloc[:, :-1]
y = df.iloc[:, -1]

# stratify: default=None 이고, stratify 값을 target으로 지정해주면 각각의 class 비율(ratio)을 train / validation에 유지해 준다. (즉, 한 쪽에 쏠려서 분배되는 것을 방지)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=156, stratify=y)

# 로지스틱 회귀로 학습, 예측, 평가 
lr_clf = LogisticRegression()
lr_clf.fit(X_train, y_train)
pred = lr_clf.predict(X_test)
pred_proba = lr_clf.predict_proba(X_test)[:, 1]

get_clf_eval(y_test, pred, pred_proba)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
오차행렬
[[82  8]
 [15 29]]
정확도: 0.8284, 정밀도: 0.7838, 재현율: 0.6591, F1: 0.7160, AUC:0.7851


/Users/hwangdong-gyu/anaconda3/envs/test/lib/python3.9/site-packages/sklearn/linear_model/_logistic.py:460: ConvergenceWarning: lbfgs failed to converge (status=1):
STOP: TOTAL NO. of ITERATIONS REACHED LIMIT.

Increase the number of iterations (max_iter) or scale the data as shown in:
    https://scikit-learn.org/stable/modules/preprocessing.html
Please also refer to the documentation for alternative solver options:
    https://scikit-learn.org/stable/modules/linear_model.html#logistic-regression
  n_iter_i = _check_optimize_result(

결측치와 이상치 처리로 학습시킨 결과 약 84퍼센트의 정확도를 보이고 있다.

느낀점

이번 데이터를 가지고 분석 및 결측치, 이상치 처리를 해보면서 아직 데이터들을 다루는게 미숙하다는 것을 느꼈고, seaborn을 이용해 시각화를 통해 데이터에 대해 더 자세히 접근해봐야겠다고 생각이 들었다. 또한 정규화 등 스케일링을 통해 정확도를 높여봐야겠다고 느꼈다.

This post is licensed under CC BY 4.0 by the author.