2-5. 타이타닉 컴피티션 실습
In [1]:
# 라이브러리 불러오기
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inline

import warnings
warnings.filterwarnings('ignore')
In [2]:
# 훈련 데이터셋 확인
train = pd.read_csv('Titanic/input/train.csv')
train.head(3)
Out[2]:
PassengerId Survived Pclass Name Sex Age SibSp Parch Ticket Fare Cabin Embarked
0 1 0 3 Braund, Mr. Owen Harris male 22.0 1 0 A/5 21171 7.2500 NaN S
1 2 1 1 Cumings, Mrs. John Bradley (Florence Briggs Th... female 38.0 1 0 PC 17599 71.2833 C85 C
2 3 1 3 Heikkinen, Miss. Laina female 26.0 0 0 STON/O2. 3101282 7.9250 NaN S
In [3]:
print('\n ### 훈련 데이터셋 정보 ### \n')
print(train.info())
 ### 훈련 데이터셋 정보 ### 

<class 'pandas.core.frame.DataFrame'>
RangeIndex: 891 entries, 0 to 890
Data columns (total 12 columns):
PassengerId    891 non-null int64
Survived       891 non-null int64
Pclass         891 non-null int64
Name           891 non-null object
Sex            891 non-null object
Age            714 non-null float64
SibSp          891 non-null int64
Parch          891 non-null int64
Ticket         891 non-null object
Fare           891 non-null float64
Cabin          204 non-null object
Embarked       889 non-null object
dtypes: float64(2), int64(5), object(5)
memory usage: 83.6+ KB
None

Age, Cabin, Embarked 컬럼에서 Null 값이 있는데 머신러닝 모델에는 Null값을 허용하지 않기 때문에 이를 어떻게 처리할지 결정해야함

In [4]:
### Age는 평균값으로 대체
train['Age'].fillna(train['Age'].mean(), inplace = True)

### 나머지는 'N'으로 대체
train['Cabin'].fillna('N', inplace=True)
train['Embarked'].fillna('N', inplace=True)
print('데이터 Null 값 개수: ', train.isnull().sum().sum())
데이터 Null 값 개수:  0

Sex, Cabin, Embarked --> 세 가지 문자열 Feature에 대해 값과 데이터 수 살펴보기

In [5]:
print('Sex 값 분포 :\n', train['Sex'].value_counts())
print('\nCabin 값 분포: \n', train['Cabin'].value_counts())
print('\nEmbarked 값 분포: \n', train['Embarked'].value_counts())
Sex 값 분포 :
 male      577
female    314
Name: Sex, dtype: int64

Cabin 값 분포: 
 N                  687
G6                   4
C23 C25 C27          4
B96 B98              4
C22 C26              3
F2                   3
E101                 3
D                    3
F33                  3
B49                  2
D26                  2
B57 B59 B63 B66      2
E44                  2
B58 B60              2
D33                  2
F G73                2
E8                   2
C93                  2
D35                  2
B18                  2
B5                   2
E25                  2
D17                  2
B20                  2
C65                  2
E24                  2
D36                  2
C124                 2
C92                  2
B35                  2
                  ... 
E68                  1
B42                  1
A5                   1
D19                  1
E17                  1
B19                  1
B101                 1
E77                  1
B102                 1
D47                  1
C85                  1
E49                  1
C106                 1
C30                  1
C103                 1
C110                 1
E63                  1
A26                  1
C45                  1
A10                  1
B38                  1
C148                 1
C86                  1
B41                  1
E36                  1
C47                  1
F38                  1
C70                  1
A7                   1
C46                  1
Name: Cabin, Length: 148, dtype: int64

Embarked 값 분포: 
 S    644
C    168
Q     77
N      2
Name: Embarked, dtype: int64

Sex와 Embarked 값은 크게 문제가 없으나, Cabin의 경우 N이 687건으로 가장 많음

Cabin은 첫 번째 글자가 좌석의 등급 등과 연관이 있을 수도 있으므로 첫 글자만 추출

In [6]:
train['Cabin'] = train['Cabin'].str[:1]
print(train['Cabin'].head(3))
0    N
1    C
2    N
Name: Cabin, dtype: object

이번에는 성별에 따른 생존자 수를 비교

In [7]:
train.groupby(['Sex','Survived'])['Survived'].count().to_frame()
Out[7]:
Survived
Sex Survived
female 0 81
1 233
male 0 468
1 109

Survived는 예측의 대상이 되는 타겟 컬럼으로 0이면 사망, 1이면 생존

여자는 81/233 = 74.2% 가량 생존했고, 남자는 109/577 = 18.8% 생존

In [8]:
sns.barplot(x='Sex', y='Survived', data=train)
Out[8]:
<matplotlib.axes._subplots.AxesSubplot at 0x1a1c4f8358>

부자와 가난한 사람의 생존률 차이는 어떤지 살펴보기 --> Pclass+Sex

In [9]:
sns.barplot(x='Pclass', y='Survived', hue='Sex', data=train)
Out[9]:
<matplotlib.axes._subplots.AxesSubplot at 0x1a1c9774a8>

여성의 경우 1, 2등급 모두 생존률이 높고 3등급으로 가면 생존률이 많이 떨어짐 남성의 경우도 등급이 좋을 수록 생존률이 높으나, 남성 1등급의 생존률이 여성 3등급의 생존률보다 낮음

이번에는 Age에 따른 생존 확률

In [10]:
# Age 값에 따라 연령층을 분류하는 함수 정의. 이후 apply lambda에 이용

def get_category(age):
    cat = ''
    if age <= -1 : cat = 'Unknown'
    elif age <= 5 : cat = 'Baby'
    elif age <= 12 : cat = 'Child'
    elif age <= 18 : cat = 'Teenager'
    elif age <= 25 : cat = 'Student'
    elif age <= 35 : cat = 'Young Adult'
    elif age <= 60: cat = 'Adult'
    else : cat = 'Elderly'
    
    return cat

# barplot 크게 설정
plt.figure(figsize=(12, 6))

# X축의 값을 순차적으로 표시하기 위한 설정
group_names = ['Unknown', 'Baby', 'Child', 'Teenager', 'Student', 'Young Adult', 'Adult', 'Elderly']

# lambda 식에 위에서 생성한 get_category() 함수를 반환 값으로 지정
# get_category(x)는 입력값으로 Age 컬럼 값을 받아서 해당되는 cat 반환
train['Age_cat'] = train['Age'].apply(lambda x: get_category(x))
sns.barplot(x='Age_cat', y='Survived', hue='Sex', data=train, order=group_names)
train.drop('Age_cat', axis=1, inplace=True)

여자 Baby의 경우에는 비교적 생존률이 높고, Child의 경우 생존률이 낮음, Elderly의 경우에는 생존률이 매우 높음

Sex, Age, Pclass 등이 생존 여부에 중요한 Feature인 것으로 보임

이제 남아있는 문자열 카테고리 Feature를 숫자형으로 변환 --> 사이킷런의 LabelEncoder() 이용

  • LabelEncoder 객체는 카테고리 값의 유형수에 따라 0 ~ (유형수-1) 까지 숫자 값으로 변환
In [11]:
from sklearn.preprocessing import LabelEncoder

def encode_features(dataDF) :
    features = ['Cabin', 'Sex', 'Embarked']
    for feature in features:
        le = LabelEncoder()
        le = le.fit(dataDF[feature])
        dataDF[feature] = le.transform(dataDF[feature])
        
    return dataDF

train = encode_features(train)
train.head()
Out[11]:
PassengerId Survived Pclass Name Sex Age SibSp Parch Ticket Fare Cabin Embarked
0 1 0 3 Braund, Mr. Owen Harris 1 22.0 1 0 A/5 21171 7.2500 7 3
1 2 1 1 Cumings, Mrs. John Bradley (Florence Briggs Th... 0 38.0 1 0 PC 17599 71.2833 2 0
2 3 1 3 Heikkinen, Miss. Laina 0 26.0 0 0 STON/O2. 3101282 7.9250 7 3
3 4 1 1 Futrelle, Mrs. Jacques Heath (Lily May Peel) 0 35.0 1 0 113803 53.1000 2 3
4 5 0 3 Allen, Mr. William Henry 1 35.0 0 0 373450 8.0500 7 3

지금까지 Feature를 가공한 내역을 정리하여 함수로 다시 정리

In [12]:
# Null 처리 함수
def fillna(df):
    df['Age'].fillna(df['Age'].mean(), inplace=True)
    df['Cabin'].fillna('N', inplace=True)
    df['Embarked'].fillna('N', inplace=True)
    df['Fare'].fillna(0, inplace=True)
    return df


# 러닝 알고리즘에 불필요한 속성 제거
def drop_features(df):
    df.drop(['PassengerId', 'Name', 'Ticket'], axis=1, inplace=True)
    return df


# 라벨 인코딩 수행
def format_features(df):
    df['Cabin'] = df['Cabin'].str[:1]
    features = ['Cabin', 'Sex', 'Embarked']
    for feature in features:
        le = LabelEncoder()
        le.fit(df[feature])
        df[feature] = le.transform(df[feature])
    return df

# 앞에서 설정한 데이터 전처리 함수 호출
def transform_features(df):
    df = fillna(df)
    df = drop_features(df)
    df = format_features(df)
    return df

다시 원본 데이터를 호출해서 transform_features 함수로 가공해보기

In [13]:
# 원본 데이터 재로딩
titanic_df = pd.read_csv('Titanic/input/train.csv')
y_titanic_df = titanic_df['Survived']
X_titanic_df = titanic_df.drop(['Survived'], axis=1)

X_titanic_df = transform_features(X_titanic_df)

내려받은 학습 세트를 기반으로 하여 train_test_split() 을 이용하여 별도의 테스트 데이터 세트를 추출

In [14]:
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X_titanic_df, y_titanic_df, test_size = 0.2, random_state = 11)

ML 알고리즘인 DecisionTree, RandomForest, Logistic Regression을 이용하여 생존자 예측

  • train_test_split() 으로 분리한 학습 데이터와 테스트 데이터를 기반으로 머신러닝 모델을 학습(fit)하고, 예측(predict)
  • 성능평가는 정확도로 평가: accuracy_score() 를 사용
In [15]:
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score

# DecisionTree, RandomForest, LogisticRegression을 위한 사이킷런 Classifier 클래스 생성
dt_clf = DecisionTreeClassifier(random_state=11)
rf_clf = RandomForestClassifier(random_state=11)
lr_clf = LogisticRegression()

# DecisionTreeClassifier 학습/예측/평가
dt_clf.fit(X_train, y_train)
dt_pred = dt_clf.predict(X_test)
print('DecisionTreeClassifier 정확도 : {0:.4f}'.format(accuracy_score(y_test, dt_pred)))
      
# RandomForestClassifier 학습/예측/평가
rf_clf.fit(X_train, y_train)
rf_pred = rf_clf.predict(X_test)
print('RandomForestClassifier 정확도 : {0:.4f}'.format(accuracy_score(y_test,rf_pred)))
      
# LogisticRegression 학습/예측/평가
lr_clf.fit(X_train, y_train)
lr_pred = lr_clf.predict(X_test)
print('LogisticRegression 정확도 : {0:.4f}'.format(accuracy_score(y_test, lr_pred)))
DecisionTreeClassifier 정확도 : 0.7877
RandomForestClassifier 정확도 : 0.8324
LogisticRegression 정확도 : 0.8659

3개의 알고리즘 중 Logistioc Regression이 높은 정확도를 나타내고 있음

하지만 아직 최적화 작업을 수행하지 않았고, 데이터 양도 충분치 않기 때문에 어떤 알고리즘이 가장 좋은 성능인지 평가할 수는 없음

교차 검증을 위한 kFold 클래스를 사용

In [16]:
from sklearn.model_selection import KFold

def exec_kfold(clf, folds):
    # 폴드 세트가 5개인 kFold 객체 생성, 폴드 수 만큼 예측결과 저장을 위한 리스트 객체 생성
    kfold = KFold(n_splits=folds)
    scores = []
    
    # kFold 교차검증 수행
    for iter_count, (train_index, test_index) in enumerate(kfold.split(X_titanic_df)):
        # X_train_df 데이터에서 교차검증별로 학습과 검증 데이터를 가리키는 인덱스 생성
        X_train, X_test = X_titanic_df.values[train_index], X_titanic_df.values[test_index]
        y_train, y_test = y_titanic_df.values[train_index], y_titanic_df.values[test_index]
        # Classifier 학습, 예측, 정확도 계산
        clf.fit(X_train, y_train)
        predictions = clf.predict(X_test)
        accuracy = accuracy_score(y_test, predictions)
        scores.append(accuracy)
        print("교차검증 {0} 정확도: {1:.4f}".format(iter_count, accuracy))
        # 5개 fold에서 평균 정확도 계산
    mean_score = np.mean(scores)
    print("평균 정확도 : {0:.4f}".format(mean_score))
    
# exec_kfold 호출
exec_kfold(dt_clf, 5)
교차검증 0 정확도: 0.7542
교차검증 1 정확도: 0.7809
교차검증 2 정확도: 0.7865
교차검증 3 정확도: 0.7697
교차검증 4 정확도: 0.8202
평균 정확도 : 0.7823

교차 검증을 위한 cross_val_score() 사용

In [17]:
from sklearn.model_selection import cross_val_score

scores = cross_val_score(dt_clf, X_titanic_df, y_titanic_df, cv=5)
for iter_count, accuracy in enumerate(scores):
    print("교차검증 {0} 정확도: {1:.4f}".format(iter_count, accuracy))
    
print("평균정확도: {0: .4f}".format(np.mean(scores)))
교차검증 0 정확도: 0.7430
교차검증 1 정확도: 0.7765
교차검증 2 정확도: 0.7809
교차검증 3 정확도: 0.7753
교차검증 4 정확도: 0.8418
평균정확도:  0.7835

kFold와 cross_val_score의 점수가 다른 것은, cross_val_score 는 StratifiedKFold를 이용해 세트를 분할하기 때문

GridSearchCV를 이용하여 DecisionTreeClassifier 최적 하이퍼 파라미터를 찾고, 예측 성능을 측정

  • CV = 5로 지정하고, max_depth, min_samples_split, min_samples_leaf를 변경하면서 성능을 측정
  • 최적 하이퍼 파라미터와 그 때의 예측을 출력하고, 최적 하이퍼파라미터로 학습된 Estimator를 이용해 위의 train_test_split()으로 분리된 테스트 데이터 세트에 예측을 수행하여 정확도 측정
In [18]:
from sklearn.model_selection import GridSearchCV

parameters = {'max_depth': [2, 3, 5, 10],
                        'min_samples_split' : [2, 3, 5],
                         'min_samples_leaf' : [1, 5, 8]}

grid_dclf = GridSearchCV(dt_clf, param_grid=parameters, scoring='accuracy', cv=5)
grid_dclf.fit(X_train, y_train)

print("GridSearch 최적 하이퍼 파라미터: ", grid_dclf.best_params_)
print("GridSearch 최고 정확도:{:.4f} ".format(grid_dclf.best_score_))
best_dclf = grid_dclf.best_estimator_

# GridSearch의 최적 하이퍼파라미터로 학습된 Estimator로 예측 및 평가 수행
predictions = best_dclf.predict(X_test)
accuracy = accuracy_score(y_test, predictions)
print('테스트 세트에서의 DecisionTreeClassifier 정확도: {0:.4f}'.format(accuracy))
GridSearch 최적 하이퍼 파라미터:  {'max_depth': 3, 'min_samples_leaf': 1, 'min_samples_split': 2}
GridSearch 최고 정확도:0.7992 
테스트 세트에서의 DecisionTreeClassifier 정확도: 0.8715
/anaconda3/lib/python3.7/site-packages/sklearn/model_selection/_search.py:841: DeprecationWarning: The default of the `iid` parameter will change from True to False in version 0.22 and will be removed in 0.24. This will change numeric results when test-set sizes are unequal.
  DeprecationWarning)

최적 하이퍼 파라미터인 max_depth = 3, min_samples_leaf = 1, min_samples_split = 2 로 DecisionClassifier를 학습시킨 뒤 모델 정확도가 약 87.15%로 향상

실제로는 하이퍼 파라미터 튜닝으로 스코어가 이렇게 증가하지는 않음

2-4. 데이터 전처리
In [1]:
from IPython.display import Image

ML 알고리즘에서의 데이터 입력

  • 결측값(NaN, Null)을 허용하지 않음 --> Null 값은 고정된 다른 값으로 변환해야 함

  • 문자열 값을 입력값으로 허용하지 않음 --> 문자열 값을 인코딩해서 숫자형으로 변환해야 함

1. 데이터 인코딩

(1). 레이블 인코딩(Label Encoding)

 : 카테고리 피처를 코드형 숫자로 변환

  • LabelEncoder 클래스로 구현: 객체 생성 후 fit(), transform()
In [2]:
from sklearn.preprocessing import LabelEncoder

items = ['TV', '냉장고', '전자레인지', '컴퓨터', '선풍기', '선풍기', '믹서', '믹서']

# LabelEncoder 객체 생성한 후, fit()과 transform()으로 레이블 인코딩 수행
encoder = LabelEncoder()
encoder.fit(items)
labels = encoder.transform(items)
print('인코딩 변화값: ', labels)
인코딩 변화값:  [0 1 4 5 3 3 2 2]

TV는 0, 냉장고는 1, 믹서는 2, 선풍기는 3, 전자레인지는 4, 컴퓨터는 5로 변환

이처럼 데이터가 적을 경우에는 직관적으로 알 수 있지만, 많은 경우에는 어려움

classes_ : 변환된 인코딩 값에 대한 원본 값을 가지고 있음

In [3]:
print('인코딩 클래스: ', encoder.classes_)
인코딩 클래스:  ['TV' '냉장고' '믹서' '선풍기' '전자레인지' '컴퓨터']

inverse_transform() : 인코딩된 값을 다시 디코딩할 수 있음

In [4]:
print('디코딩 원본 값: ', encoder.inverse_transform([0, 1, 4, 5, 3, 3, 2, 2]))
디코딩 원본 값:  ['TV' '냉장고' '전자레인지' '컴퓨터' '선풍기' '선풍기' '믹서' '믹서']
  • 단점 : 일괄적인 숫자 값으로 변환이 되면서 몇몇 알고리즘에는 예측성능이 떨어지는 경우가 발생. (숫자 값의 크고 작음에 대한 특성이 반영되기 때문)

    --> 회귀 알고리즘에는 적용하면 안됨, 트리 계열의 알고리즘은 숫자의 이런 특성을 반영하지 않으므로 레이블 인코딩도 별 문제가 없음

(2) 원-핫 인코딩(One-Hot Encoding)

  • 바로 위에서 이야기한 레이블 인코딩의 문제점을 해결하기 위한 방식으로 OneHotEncoder 클래스로 쉽게 변환
  • 피처 값의 유형에 따라 새로운 피처를 추가해 고유 값에 해당하는 칼럼에만 1을 표시하고 나머지에는 0으로 표시하는 방법
  • 주의점
    • OneHotEncoder 변환하기 전에 모든 문자열 값이 숫자형 값으로 변환이 되어 있어야 함
    • 입력값으로 2차원 데이터가 필요함
In [5]:
from sklearn.preprocessing import OneHotEncoder
import numpy as np

items = ['TV', '냉장고', '전기레인지', '컴퓨터', '선풍기', '선풍기', '믹서', '믹서']

encoder = LabelEncoder()
encoder.fit(items)
labels = encoder.transform(items)
labels = labels.reshape(-1,1)
In [6]:
# 원-핫 인코딩을 적용
oh_encoder = OneHotEncoder(categories='auto')
oh_encoder.fit(labels)
oh_labels = oh_encoder.transform(labels)
print('원-핫 인코딩 데이터')
print(oh_labels.toarray())
print('\n원-핫 인코딩 데이터 차원')
print(oh_labels.shape)
원-핫 인코딩 데이터
[[1. 0. 0. 0. 0. 0.]
 [0. 1. 0. 0. 0. 0.]
 [0. 0. 0. 0. 1. 0.]
 [0. 0. 0. 0. 0. 1.]
 [0. 0. 0. 1. 0. 0.]
 [0. 0. 0. 1. 0. 0.]
 [0. 0. 1. 0. 0. 0.]
 [0. 0. 1. 0. 0. 0.]]

원-핫 인코딩 데이터 차원
(8, 6)
In [7]:
labels
Out[7]:
array([[0],
       [1],
       [4],
       [5],
       [3],
       [3],
       [2],
       [2]])
In [8]:
oh_labels
Out[8]:
<8x6 sparse matrix of type '<class 'numpy.float64'>'
	with 8 stored elements in Compressed Sparse Row format>
  • get_dummies() : 판다스에서 이용할 수 있는 원핫 인코딩 API
    • 사이킷런의 OneHotEncoder와는 달리 문자열 카테고리 값을 숫자형으로 변환할 필요 없이 바로 변환됨
In [9]:
import pandas as pd

df = pd.DataFrame({'item' : ['TV', '냉장고', '전자레인지', '컴퓨터', '선풍기', '선풍기', '믹서', '믹서']})
df
Out[9]:
item
0 TV
1 냉장고
2 전자레인지
3 컴퓨터
4 선풍기
5 선풍기
6 믹서
7 믹서
In [10]:
pd.get_dummies(df)
Out[10]:
item_TV item_냉장고 item_믹서 item_선풍기 item_전자레인지 item_컴퓨터
0 1 0 0 0 0 0
1 0 1 0 0 0 0
2 0 0 0 0 1 0
3 0 0 0 0 0 1
4 0 0 0 1 0 0
5 0 0 0 1 0 0
6 0 0 1 0 0 0
7 0 0 1 0 0 0

2. 피처 스케일링과 정규화

  • 피처 스케일링(Feature Scaling) : 서로 다른 변수의 값 범위를 일정한 수준으로 맞추는 작업(표준화, 정규화 등)

  • 표준화(Standardization) : 데이터 피처의 각각이 평균이 0이고 분산이 1인 가우시안 정규분포를 가진 값으로 변환

In [11]:
Image('image/standardization.jpg', width = 300)
Out[11]:
  • 정규화(Normalization) : 서로 다른 피처의 크기를 통일하기 위해 크기를 변환해주는 개념 (즉, 개별 데이터 크기를 모두 똑같은 단위로 변경)
In [12]:
Image('image/min-max-normalisation.jpg', width = 300)
Out[12]:

(1) StandardScaler

    : 표준화를 쉽게 지원하기 위한 클래스로 개별 피처를 평균이 0, 분산이 1인 값으로 변환
  • RBF 커널을 이용하는 서포트 벡터 머신, 선형회귀, 로지스틱 회귀는 데이터가 가우시안 분포를 가지고 있다고 가정하고 구현했기 때문에, 사전에 표준화를 적용하는 것은 예측 향상에 중요한 요소
In [13]:
from sklearn.datasets import load_iris
import pandas as pd

# 붓꽃 데이터 세트 로딩하고 데이터 프레임으로 변환
iris = load_iris()
iris_data = iris.data
iris_df = pd.DataFrame(data=iris_data, columns = iris.feature_names)
iris_df.head()
Out[13]:
sepal length (cm) sepal width (cm) petal length (cm) petal width (cm)
0 5.1 3.5 1.4 0.2
1 4.9 3.0 1.4 0.2
2 4.7 3.2 1.3 0.2
3 4.6 3.1 1.5 0.2
4 5.0 3.6 1.4 0.2
In [14]:
print('feature들의 평균 값: ')
print(iris_df.mean())

print('\nfeature들의 분산 값: ')
print(iris_df.var())
feature들의 평균 값: 
sepal length (cm)    5.843333
sepal width (cm)     3.057333
petal length (cm)    3.758000
petal width (cm)     1.199333
dtype: float64

feature들의 분산 값: 
sepal length (cm)    0.685694
sepal width (cm)     0.189979
petal length (cm)    3.116278
petal width (cm)     0.581006
dtype: float64
In [15]:
# StandardScaler를 이용하여 표준화

from sklearn.preprocessing import StandardScaler

# Scaler 객체 생성
scaler = StandardScaler()

# StandardScaler 로 데이터 세트변환, fit()과 transform() 호출
scaler.fit(iris_df)
iris_scaled = scaler.transform(iris_df)

# transform() 변환시 스케일 변환된 데이터세트가 Numpy ndarray로 반환되 이를 데이터 프레임으로 변환
iris_df_scaled = pd.DataFrame(data= iris_scaled, columns = iris.feature_names)
iris_df_scaled.head()
Out[15]:
sepal length (cm) sepal width (cm) petal length (cm) petal width (cm)
0 -0.900681 1.019004 -1.340227 -1.315444
1 -1.143017 -0.131979 -1.340227 -1.315444
2 -1.385353 0.328414 -1.397064 -1.315444
3 -1.506521 0.098217 -1.283389 -1.315444
4 -1.021849 1.249201 -1.340227 -1.315444
In [16]:
print('feature들의 평균값: ')
print(iris_df_scaled.mean())
print('\nfeature들의 분산값: ')
print(iris_df_scaled.var())
feature들의 평균값: 
sepal length (cm)   -1.690315e-15
sepal width (cm)    -1.842970e-15
petal length (cm)   -1.698641e-15
petal width (cm)    -1.409243e-15
dtype: float64

feature들의 분산값: 
sepal length (cm)    1.006711
sepal width (cm)     1.006711
petal length (cm)    1.006711
petal width (cm)     1.006711
dtype: float64

모든 컬럼 값의 평균이 0, 분산이 1에 아주 가깝게 변환되었음

(2) MinMaxScaler

    : 데이터값을 0과 1사이의 범위 값으로 변환(음수값이 있으면 -1에서 1값으로 변환)

  • 데이터의 분포가 가우시안 분포가 아닐 경우에 적용해 볼 수 있음
In [17]:
from sklearn.preprocessing import MinMaxScaler

# MinMaxScaler 객체 생성
scaler = MinMaxScaler()

# MinMaxScaler로 데이터 세트 변환. fit()과 transform() 호출
scaler.fit(iris_df)
iris_scaled = scaler.transform(iris_df)

# transform()시 스케일 변환된 데이터세트가 Numpy ndarray로 변환되어 이를 DataFrame으로 변환
iris_df_scaled = pd.DataFrame(data=iris_scaled, columns = iris.feature_names)
iris_df_scaled.head()
Out[17]:
sepal length (cm) sepal width (cm) petal length (cm) petal width (cm)
0 0.222222 0.625000 0.067797 0.041667
1 0.166667 0.416667 0.067797 0.041667
2 0.111111 0.500000 0.050847 0.041667
3 0.083333 0.458333 0.084746 0.041667
4 0.194444 0.666667 0.067797 0.041667
In [18]:
print('feature들의 최솟값: ')
print(iris_df_scaled.min())
print('\nfeature들의 최댓값: ')
print(iris_df_scaled.max())
feature들의 최솟값: 
sepal length (cm)    0.0
sepal width (cm)     0.0
petal length (cm)    0.0
petal width (cm)     0.0
dtype: float64

feature들의 최댓값: 
sepal length (cm)    1.0
sepal width (cm)     1.0
petal length (cm)    1.0
petal width (cm)     1.0
dtype: float64
2-3. Model Selection 모듈 소개

model_selection :

학습 데이터와 테스트 데이터 세트를 분리하거나 교차 검증 분할 및 평가, 그리고 Estimator의 하이퍼파라미터 튜닝을 위한 다양한 함수와 클래스를 제공

1. 학습/테스트 데이터 세트 분리 : train_test_split()

  • 테스트 데이터 세트를 이용하지 않고 학습 데이터 세트로만 학습하고 예측할 때의 문제
In [1]:
from sklearn.datasets import load_iris
from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import accuracy_score

iris = load_iris()
dt_clf = DecisionTreeClassifier()
train_data = iris.data
train_label = iris.target
dt_clf.fit(train_data, train_label)

# 학습된 데이터로 예측 수행
pred = dt_clf.predict(train_data)
print('예측 정확도: ', accuracy_score(train_label, pred))
예측 정확도:  1.0

위의 예측결과가 100% 정확한 이유는 이미 학습한 데이터 세트 기반으로 예측했기 때문

실제 예측에서는 학습하지 않은 데이터를 기반으로 예측을 해야함

때문에 학습데이터 자체를 학습데이터와 테스트 데이터로 분할하여 모델이 얼마나 잘 예측하는지 평가해봄

train_test_split() 의 파라미터

  • test_size : 전체 데이터 세트에서 테스트 데이터 세트 크기를 얼마로 샘플링할 것인지 결정, 기본값 : 0.25
  • train_size : 전체 데이터 세트에서 학습용 데이터 세트 크기를 얼마로 샘플링 할 것인지 결정, test_size를 주로 사용해서 잘 쓰는 파라미터는 아님
  • shuffle : 데이터를 분리하기 전에 데이터를 미리 섞을지 결정, 기본값은 True, 데이터를 분산시켜 보다 효율적인 학습/테스트 데이터 세트를 만드는데 사용
  • random_state : 난수 값을 지정하면 여러번 다시 수행해도 동일한 결과가 나오게 해줌

반환 값은 튜플 형태: (1) 학습용 데이터 피처 데이터 셋 (2) 테스트용 데이터 피처 데이터셋, (3) 학습용 데이터 레이블 데이터 셋, (4) 훈련용 데이터 레이블 데이터셋

In [7]:
from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import accuracy_score
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split

dt_clf = DecisionTreeClassifier()
iris_data = load_iris()

X_train, X_test, y_train, y_test = train_test_split(iris_data.data, iris_data.target, test_size = 0.3, random_state=121)
In [8]:
# 학습용 데이터를 기반으로 DecisionTreeClassifier를 학습하고 모델을 이용해 예측 정확도를 측정
dt_clf.fit(X_train, y_train)
pred = dt_clf.predict(X_test)
print('예측 정확도: {0:.4f}'.format(accuracy_score(y_test, pred)))
예측 정확도: 0.9556

2. 교차 검증(Cross Validation)

위의 방법으로 고정된 학습용 데이터와 테스트 데이터를 나누어 모델을 평가할 때는 과적합(Overfitting)에 취약함

교차 검증 : 결국 모의고사를 많이 보는 것 --> 데이터 편중을 막기 위해 별도의 여러 세트로 구성된 학습 데이터 셋과 검증 데이터 셋에서 학습과 평가를 수행

이 결과를 바탕으로 하이퍼 파라미터 튜닝 등의 모델 최적화를 더욱 손쉽게 할 수 있음

(1) K-Fold 교차검증(K-Fold Cross Validation)

K개의 데이터 폴드 세트를 만들어서 K번만큼 각 폴드 세트에 학습과 검증평가를 반복적으로 수행하는 방법

사이킷런에서는 KFoldStratifiedKFold를 제공함

In [16]:
from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import accuracy_score
from sklearn.model_selection import KFold
import numpy as np

iris = load_iris()
features = iris.data
label = iris.target
dt_clf = DecisionTreeClassifier()

# 5개의 폴드세트를 분리하는 kFold 객체와 폴드 세트별 정확도를 담을 리스트 객체 생성
kfold = KFold(n_splits=5)
cv_accuracy = []
print('붓꽃 데이터세트 크기: ', features.shape[0])
붓꽃 데이터세트 크기:  150

KFold(n_splits= 5)로 객체를 생성했기 때문에 150개의 데이터세트에서 120개의 학습용 데이터, 30개의 테스트 데이터 세트를 5번 만든다

  • KFold 객체는 split() 을 호출하면 학습용/검증용 데이터로 분할할 수 있는 인덱스를 반환하므로 실제 분할된 데이터 추출은 코드로 직접 수행해야함
In [21]:
n_iter = 0 

# KFold 객체의 split() 을 호출하면 폴드 별 학습용, 검증용 테스트의 로우 인덱스를 array로 반환
for train_index, test_index in kfold.split(features):
    # kfold.split() 으로 반환된 인덱스를 이용해 학습용, 검증용 테스트 데이터 세트 추출
    X_train, X_test = features[train_index], features[test_index]
    y_train, y_test = label[train_index], label[test_index]
    # 학습 및 예측
    dt_clf.fit(X_train, y_train)
    pred = dt_clf.predict(X_test)
    n_iter += 1
    # 반복 시 마다 정확도 측정
    accuracy = np.round(accuracy_score(y_test, pred), 4)
    train_size = X_train.shape[0]
    test_size = X_test.shape[0]
    print('\n{0} 교차검증 정확도 : {1}, 학습 데이터 크기 : {2}, 검증 데이터 크기 : {3}'.format(n_iter, accuracy, train_size, test_size))
    print('#{0} 검증 세트 인덱스: {1}'.format(n_iter, test_index))
    cv_accuracy.append(accuracy)
    
# 개별 iteration별 정확도를 합하여 평균 정확도 계산
print('\n## 평균검증 정확도: ', np.mean(cv_accuracy))
1 교차검증 정확도 : 1.0, 학습 데이터 크기 : 120, 검증 데이터 크기 : 30
#1 검증 세트 인덱스: [ 0  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 29]

2 교차검증 정확도 : 1.0, 학습 데이터 크기 : 120, 검증 데이터 크기 : 30
#2 검증 세트 인덱스: [30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
 54 55 56 57 58 59]

3 교차검증 정확도 : 0.8333, 학습 데이터 크기 : 120, 검증 데이터 크기 : 30
#3 검증 세트 인덱스: [60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
 84 85 86 87 88 89]

4 교차검증 정확도 : 0.9333, 학습 데이터 크기 : 120, 검증 데이터 크기 : 30
#4 검증 세트 인덱스: [ 90  91  92  93  94  95  96  97  98  99 100 101 102 103 104 105 106 107
 108 109 110 111 112 113 114 115 116 117 118 119]

5 교차검증 정확도 : 0.7333, 학습 데이터 크기 : 120, 검증 데이터 크기 : 30
#5 검증 세트 인덱스: [120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137
 138 139 140 141 142 143 144 145 146 147 148 149]

## 평균검증 정확도:  0.9044266666666668

(2) Stratified KFold

불균형한(imbalanced) 분포도를 가진 레이블 데이터 집합을 위한 KFold 방식

--> 레이블 분포를 먼저 고려한 뒤 이 분포와 동일하게 학습과 검증 데이터 세트를 분배함

  • 기존 KFold의 문제점 확인
In [22]:
import pandas as pd

iris = load_iris()
iris_df = pd.DataFrame(data=iris.data, columns=iris.feature_names)
iris_df['label']  = iris.target
iris_df['label'].value_counts()
Out[22]:
2    50
1    50
0    50
Name: label, dtype: int64

레이블 값은 모두 50개로 동일

In [26]:
kfold = KFold(n_splits=3)
n_iter = 0

for train_index, test_index in kfold.split(iris_df):
    n_iter+=1
    label_train = iris_df['label'].iloc[train_index]
    label_test = iris_df['label'].iloc[test_index]
    print('\n##교차 검증: {0}'.format(n_iter))
    print('학습 레이블 데이터 분포: \n', label_train.value_counts())
    print('검증 레이블 데이터 분포: \n', label_test.value_counts())
##교차 검증: 1
학습 레이블 데이터 분포: 
 2    50
1    50
Name: label, dtype: int64
검증 레이블 데이터 분포: 
 0    50
Name: label, dtype: int64

##교차 검증: 2
학습 레이블 데이터 분포: 
 2    50
0    50
Name: label, dtype: int64
검증 레이블 데이터 분포: 
 1    50
Name: label, dtype: int64

##교차 검증: 3
학습 레이블 데이터 분포: 
 1    50
0    50
Name: label, dtype: int64
검증 레이블 데이터 분포: 
 2    50
Name: label, dtype: int64

위 결과를 보면, 3개의 폴드 세트로 생성된 학습 레이블과 검증 레이블이 완전히 다른 값으로 추출됨

이런 상태에서 학습모델로 검증데이터를 예측하면 정확도가 0이 나오게 됨

StratifiedKFold 는 분할된 데이터 세트가 전체 레이블 값의 분포를 반영하지 못하는 문제를 해결 해줌

실행방법은 거의 비슷하지만 split 메서드에 피처 데이터 세트뿐만 아니라 레이블 데이터 세트도 반드시 필요함

In [27]:
from sklearn.model_selection import StratifiedKFold

skf = StratifiedKFold(n_splits=3)
n_iter = 0

for train_index, test_index in skf.split(iris_df, iris_df['label']):
    n_iter += 1
    label_train = iris_df['label'].iloc[train_index]
    label_test = iris_df['label'].iloc[test_index]
    print('\n## 교차검증: {0}'.format(n_iter))
    print('학습 레이블 데이터 분포: \n', label_train.value_counts())
    print('검증 레이블 데이터 분포: \n', label_test.value_counts())
## 교차검증: 1
학습 레이블 데이터 분포: 
 2    33
1    33
0    33
Name: label, dtype: int64
검증 레이블 데이터 분포: 
 2    17
1    17
0    17
Name: label, dtype: int64

## 교차검증: 2
학습 레이블 데이터 분포: 
 2    33
1    33
0    33
Name: label, dtype: int64
검증 레이블 데이터 분포: 
 2    17
1    17
0    17
Name: label, dtype: int64

## 교차검증: 3
학습 레이블 데이터 분포: 
 2    34
1    34
0    34
Name: label, dtype: int64
검증 레이블 데이터 분포: 
 2    16
1    16
0    16
Name: label, dtype: int64

출력 결과를 보면 학습 레이블과 검증 레이블의 데이터 값 분포가 동일하게 할당됐음을 알 수 있음

In [32]:
# 붓꽃 데이터 세트에서 Stratified Kfold 를 이용한 검증

dt_clf = DecisionTreeClassifier(random_state=156)

skfold = StratifiedKFold(n_splits=3)
n_iter = 0
cv_accuracy = []

# StratifiedKFold의 split 호출 시 반드시 레이블 데이터 세트도 추가 입력 필요
for train_index, test_index in skfold.split(features, label):
    #split()으로 반환된 인덱스를 이용해 학습용, 검증용 테스트 데이터 추출
    X_train, X_test = features[train_index], features[test_index]
    y_train, y_test = label[train_index], label[test_index]
    # 학습 및 예측
    dt_clf.fit(X_train, y_train)
    pred = dt_clf.predict(X_test)
    # 반복 시마다 정확도 측정
    n_iter += 1
    accuracy = np.round(accuracy_score(y_test, pred),4)
    train_size = X_train.shape[0]
    test_size = X_test.shape[0]
    print('{0} 교차검증 정확도: {1}, 학습데이터 크기: {2}, 검증데이터 크기: {3}'.format(n_iter, accuracy, train_size, test_size))
    cv_accuracy.append(accuracy)
    
# 교차검증별 정확도 및 평균정확도 계산
print('\n 교차검증별 정확도: ', cv_accuracy)
print('## 평균 검증 정확도: ', np.mean(cv_accuracy))
1 교차검증 정확도: 0.9804, 학습데이터 크기: 99, 검증데이터 크기: 51
2 교차검증 정확도: 0.9216, 학습데이터 크기: 99, 검증데이터 크기: 51
3 교차검증 정확도: 0.9792, 학습데이터 크기: 102, 검증데이터 크기: 48

 교차검증별 정확도:  [0.9804, 0.9216, 0.9792]
## 평균 검증 정확도:  0.9604
  • 일반적으로 분류에서의 교차검증은 KFold가 아니라 Stratified Kfold로 분할이 되어야함

  • 반면, 회귀에서는 Stratified Kfold가 지원되지 않음

    • 결정값이 이산형의 레이블이 아니라 연속된 숫자 값이기 때문

(3) cross_val_score() : 교차 검증을 보다 간단히게

  • 앞서 수행한 Kfold로 데이터를 학습하고, 예측하는 코드를 보면 아래와 같은 프로세스로 검증이 진행되었음

    (1) 폴드 세트 설정 (2) for 루프 반복으로 학습 및 테스트 데이터의 인덱스를 추출 (3) 반복적으로 학습과 예측을 수행하고 예측수행을 반환

  • cross_val_score() 는 이 과정을 한꺼번에 수행해주는 API

cross_val_score() API의 선언형태

--> cross_val_score(estimator, X, y=None, scoring=None, cv=None, n_jobs=1, verbose =0, fit_params=None, pre_dispatch = '2*n_jobs')

  • estimator : 예측모델
  • X : 피처 데이터 세트
  • y : 레이블 데이터 세트,
  • scoring : 예측성능평가 지표
  • cv : 교차검증 폴드 수 (estimator로 classifier가 입력되면 Stratified Kfold 방식으로 학습/테스트 데이터 세트 분할)
  • 수행 후 scoring 파라미터 값으로 지정된 성능 지표를 배열 형태로 반환
In [34]:
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import cross_val_score, cross_validate
from sklearn.datasets import load_iris

iris_data = load_iris()
dt_clf = DecisionTreeClassifier(random_state=156)

data = iris_data.data
label = iris_data.target

# 성능지표는 정확도(accuracy), 교차검증 세트는 3개
scores = cross_val_score(dt_clf, data, label, cv =3 , scoring = 'accuracy')
print('교차 검증별 정확도: ', np.round(scores, 4))
print('평균 검증 정확도: ', np.round(np.mean(scores),4))
교차 검증별 정확도:  [0.9804 0.9216 0.9792]
평균 검증 정확도:  0.9604
  • cross_val_score() API는 내부에서 estimator를 학습, 예측, 평가시켜주므로 간단히 교차검증을 수행할 수 있음
  • 이와 유사하게 cross_validate() 은 여러개의 평가지표를 반환하며, 성능 평가지표와 수행시간도 같이 제공

(4) GridSearchCV - 교차검증과 하이퍼파라미터 튜닝을 한번에

GridSearchCV를 이용하여 모델에 사용되는 하이퍼 파라미터를 순차적으로 입력하면서 최적의 파라미터를 도출

In [35]:
grid_parameters = {'max_depth': [1, 2, 3], 
                              'min_samples_split': [2, 3]}

위와 같이 파라미터를 설정했다면 GridSearch에서는 아래와 같이 순차적으로 파라미터 조합을 적용하여 실행

In [37]:
pd.read_csv('Grid.csv', index_col= '순번')
Out[37]:
max_depth min_samples_split
순번
1 1 2
2 1 3
3 2 2
4 2 3
5 3 2
6 3 3

교차검증을 기반으로 실행되기 때문에 수행시간이 오래 걸림 (가령, 위의 예에서 cv=3으로 수행한다면 "cv 3회 * 6개 파라미터조합 = 18회"의 학습/평가가 이루어짐

GridSearch의 주요 파라미터

  • estimator : 적용 알고리즘 모델로 classifier, regressor, pipeline 등이 사용
  • param_grid : ket+리스트 값을 가지는 딕셔너리가 주어짐. estimator의 튜닝을 위해 파라미터명과 사용될 여러 파라미터 값을 지어
  • scoring : 예측 성능을 평가할 평가 방법을 지정, 문자열로 사이킷런의 성능평가 지표를 입력하나 별도의 함수 지정도 가능
  • refit : 기본값은 True이며 True로 생성시 가장 최적의 하이퍼 파라미터를 찾은 뒤 입력된 estimator 객체를 해당 하이퍼 파리미터로 재학습시킴
In [45]:
from sklearn.datasets import load_iris
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import GridSearchCV

# 데이터를 로딩하고 학습데이터와 테스트 데이터 분리
iris = load_iris()
X_train, X_test, y_train, y_test = train_test_split(iris_data.data, iris_data.target, test_size = 0.2, random_state=121)

dtree = DecisionTreeClassifier()

## 파라미터를 딕셔너리 형태로 설정
parameters = {'max_depth' : [1, 2, 3], 'min_samples_split' : [2, 3]}

학습 데이터 세트를 GridSearchCV 객체의 fit 메서드에 인자로 입력

--> 학습데이터를 cv에 입력된 폴딩 수로 분할

--> param_grid에 기술된 하이퍼파라미터로 순차적으로 변경하며 학습/평가 수행

--> 개별 평가결과 : cvresults 에 기록

--> 최고 성능 파라미터 : bestparams 에 기록

--> 최고 점수 : bestscores 에 기록

In [49]:
# param_grid의  하이퍼 파라미터를 3개의 train, test set fold로 나누어 테스트 수행 설정
## refit=True가 기본 설정으로 True이면 가장 좋은 파라미터 설정으로 재학습 시킴

grid_dtree = GridSearchCV(dtree, param_grid=parameters, cv=3, refit=True)

# 붓꽃 학습 데이터로 param_grid의 하이퍼 파라미터를 순차적으로 학습, 평가
grid_dtree.fit(X_train, y_train)

# GridSearchCV 결과를 추출해 데이터 프레임으로 반환
scores_df = pd.DataFrame(grid_dtree.cv_results_)
scores_df[['params', 'mean_test_score', 'rank_test_score', 'split0_test_score', 'split1_test_score', 'split2_test_score']]
/anaconda3/lib/python3.7/site-packages/sklearn/utils/deprecation.py:125: FutureWarning: You are accessing a training score ('split0_train_score'), which will not be available by default any more in 0.21. If you need training scores, please set return_train_score=True
  warnings.warn(*warn_args, **warn_kwargs)
/anaconda3/lib/python3.7/site-packages/sklearn/utils/deprecation.py:125: FutureWarning: You are accessing a training score ('split1_train_score'), which will not be available by default any more in 0.21. If you need training scores, please set return_train_score=True
  warnings.warn(*warn_args, **warn_kwargs)
/anaconda3/lib/python3.7/site-packages/sklearn/utils/deprecation.py:125: FutureWarning: You are accessing a training score ('split2_train_score'), which will not be available by default any more in 0.21. If you need training scores, please set return_train_score=True
  warnings.warn(*warn_args, **warn_kwargs)
/anaconda3/lib/python3.7/site-packages/sklearn/utils/deprecation.py:125: FutureWarning: You are accessing a training score ('mean_train_score'), which will not be available by default any more in 0.21. If you need training scores, please set return_train_score=True
  warnings.warn(*warn_args, **warn_kwargs)
/anaconda3/lib/python3.7/site-packages/sklearn/utils/deprecation.py:125: FutureWarning: You are accessing a training score ('std_train_score'), which will not be available by default any more in 0.21. If you need training scores, please set return_train_score=True
  warnings.warn(*warn_args, **warn_kwargs)
Out[49]:
params mean_test_score rank_test_score split0_test_score split1_test_score split2_test_score
0 {'max_depth': 1, 'min_samples_split': 2} 0.700000 5 0.700 0.7 0.70
1 {'max_depth': 1, 'min_samples_split': 3} 0.700000 5 0.700 0.7 0.70
2 {'max_depth': 2, 'min_samples_split': 2} 0.958333 3 0.925 1.0 0.95
3 {'max_depth': 2, 'min_samples_split': 3} 0.958333 3 0.925 1.0 0.95
4 {'max_depth': 3, 'min_samples_split': 2} 0.975000 1 0.975 1.0 0.95
5 {'max_depth': 3, 'min_samples_split': 3} 0.975000 1 0.975 1.0 0.95
  • params : 적용한 개별 하이퍼 파라미터 값
  • rank_test_score : 성능이 좋게 나온 score 순위
  • mean_test_score : 개별 하이퍼파라미터별로 CV 폴딩 테스트의 평가 평균 값
In [53]:
print('GridSearch 최적 파라미터: ', grid_dtree.best_params_)
print('GridSearch 최고 점수: ', grid_dtree.best_score_)
GridSearch 최적 파라미터:  {'max_depth': 3, 'min_samples_split': 2}
GridSearch 최고 점수:  0.975

refit = True 이면, GridSearchCV가 이미 최적 성능을 나타내는 하이퍼파라미터로 Estimator를 학습해 bestestimator 로 저장했음

In [54]:
# GridSearchCV의 refit으로 학습된 estimator 반환
estimator = grid_dtree.best_estimator_

# GridSearchCV의 best_estmator_ 는 이미 최적 학습이 됐으므로 별도 학습이 필요 없음
pred = estimator.predict(X_test)
print('테스트 데이터세트 정확도: {0: .4f}'.format(accuracy_score(y_test, pred)))
테스트 데이터세트 정확도:  0.9667
2-2. 사이킷런의 기반 프레임워크

1. Estimator 클래스 및 fit(), predict() 메서드

기본 Estimator 클래스 : Classifier와 Regressor로 나뉨

각각의 Estimator는 내부에서 fit()과 predict()를 내부에서 구현

  • fit() : 모델 학습
  • predict() : 학습된 모델의 예측
  • transform() : 입력된 데이터의 형태에 맞추어 데이터를 변환

cross_val_score() (evaluation 함수)나 GridSearchCV (하이퍼파라미터 튜닝) 같은 클래스의 경우 Estimator를 인자로 받고 cross_val_score(), GridSearchCV.fit() 함수 내에서 fit과 predict를 호출해서 평가하거나 튜닝을 수행

2. 사이킷런의 주요 모듈

In [1]:
import pandas as pd
pd.read_csv('scikit-learn의 모듈.csv')
Out[1]:
분류 모듈명 설명
0 예제 데이터 sklearn.datasets 사이킷런 내장 예제 데이터 세트
1 피처 처리 sklearn.preprocessing 데이터 전처리 필요한 다양한 가공기능(인코딩, 정규화, 스케일링 등)
2 피처 처리 sklearn.feature_selection 알고리즘에 큰 영향을 미치는 피처를 우선순위대로 셀렉션하는 기능 제공
3 피처 처리 sklearn.feature_extraction 텍스트, 이미지 데이터의 벡터화된 피처를 추출하는데 사용
4 차원축소 sklearn.decomposition 차원 축소와 관련된 알고리즘을 지원(PCA, NMF, Truncated SVD 등)
5 모델 선택 sklearn.model_selection 훈련, 테스트 데이터 분리, 그리드 서치 등의 기능 제공
6 평가 sklearn.metrics 다양한 모델의 성능평가 측정방법 제공(Accuracy, ROC-AUC, RMSE 등
7 알고리즘 sklearn.ensemble 앙상블 알고리즘 제공(RandomForest, AdaBoost, Gradient B...
8 알고리즘 sklearn.linear_model 회귀 관련 알고리즘 제공(linear, Ridge, Lasso, Logistic 등)
9 알고리즘 sklearn.naive_bayes 나이브 베이즈 알고리즘 제공(Gaussian NB emd)
10 알고리즘 sklearn.neighbors 최근접이웃 알고리즘 제공(K-NN 등)
11 알고리즘 sklearn.svm 서포트 벡터 머신 알고리즘
12 알고리즘 sklearn.tree 의사결정 트리 알고리즘 제공
13 알고리즘 sklearn.cluster 비지도 클러스터링 알고리즘 제공
14 유틸리티 sklearn.pipeline 피처처리 등의 변환과 ML 알고리즘 학습, 예측 등을 함께 묶어서 실행하는 유틸리티 제공

3. 내장된 예제 데이터 세트 살펴보기

In [2]:
from sklearn.datasets import load_iris

iris_data = load_iris()
print(type(iris_data))
<class 'sklearn.utils.Bunch'>
In [3]:
keys = iris_data.keys()
print('붓꽃 데이터 세트의 키들: ', keys)
붓꽃 데이터 세트의 키들:  dict_keys(['data', 'target', 'target_names', 'DESCR', 'feature_names', 'filename'])
In [4]:
print('feature_names의 type: ', type(iris_data.feature_names))
print('feature_names의 shape: ', len(iris_data.feature_names))
print('feature_names: ', iris_data.feature_names)

print('\ntarget_names의 type: ', type(iris_data.target_names))
print('target_names의 shape: ', len(iris_data.target_names))
print('target_names: ', iris_data.target_names)

print('\ndata의 type: ', type(iris_data.data))
print('data의 shape: ', iris_data.data.shape)
print('data: \n', iris_data['data'])

print('\ntarget의 type: ', type(iris_data.target))
print('target의 shape: ', iris_data.target.shape)
print('target: \n', iris_data['target'])
feature_names의 type:  <class 'list'>
feature_names의 shape:  4
feature_names:  ['sepal length (cm)', 'sepal width (cm)', 'petal length (cm)', 'petal width (cm)']

target_names의 type:  <class 'numpy.ndarray'>
target_names의 shape:  3
target_names:  ['setosa' 'versicolor' 'virginica']

data의 type:  <class 'numpy.ndarray'>
data의 shape:  (150, 4)
data: 
 [[5.1 3.5 1.4 0.2]
 [4.9 3.  1.4 0.2]
 [4.7 3.2 1.3 0.2]
 [4.6 3.1 1.5 0.2]
 [5.  3.6 1.4 0.2]
 [5.4 3.9 1.7 0.4]
 [4.6 3.4 1.4 0.3]
 [5.  3.4 1.5 0.2]
 [4.4 2.9 1.4 0.2]
 [4.9 3.1 1.5 0.1]
 [5.4 3.7 1.5 0.2]
 [4.8 3.4 1.6 0.2]
 [4.8 3.  1.4 0.1]
 [4.3 3.  1.1 0.1]
 [5.8 4.  1.2 0.2]
 [5.7 4.4 1.5 0.4]
 [5.4 3.9 1.3 0.4]
 [5.1 3.5 1.4 0.3]
 [5.7 3.8 1.7 0.3]
 [5.1 3.8 1.5 0.3]
 [5.4 3.4 1.7 0.2]
 [5.1 3.7 1.5 0.4]
 [4.6 3.6 1.  0.2]
 [5.1 3.3 1.7 0.5]
 [4.8 3.4 1.9 0.2]
 [5.  3.  1.6 0.2]
 [5.  3.4 1.6 0.4]
 [5.2 3.5 1.5 0.2]
 [5.2 3.4 1.4 0.2]
 [4.7 3.2 1.6 0.2]
 [4.8 3.1 1.6 0.2]
 [5.4 3.4 1.5 0.4]
 [5.2 4.1 1.5 0.1]
 [5.5 4.2 1.4 0.2]
 [4.9 3.1 1.5 0.2]
 [5.  3.2 1.2 0.2]
 [5.5 3.5 1.3 0.2]
 [4.9 3.6 1.4 0.1]
 [4.4 3.  1.3 0.2]
 [5.1 3.4 1.5 0.2]
 [5.  3.5 1.3 0.3]
 [4.5 2.3 1.3 0.3]
 [4.4 3.2 1.3 0.2]
 [5.  3.5 1.6 0.6]
 [5.1 3.8 1.9 0.4]
 [4.8 3.  1.4 0.3]
 [5.1 3.8 1.6 0.2]
 [4.6 3.2 1.4 0.2]
 [5.3 3.7 1.5 0.2]
 [5.  3.3 1.4 0.2]
 [7.  3.2 4.7 1.4]
 [6.4 3.2 4.5 1.5]
 [6.9 3.1 4.9 1.5]
 [5.5 2.3 4.  1.3]
 [6.5 2.8 4.6 1.5]
 [5.7 2.8 4.5 1.3]
 [6.3 3.3 4.7 1.6]
 [4.9 2.4 3.3 1. ]
 [6.6 2.9 4.6 1.3]
 [5.2 2.7 3.9 1.4]
 [5.  2.  3.5 1. ]
 [5.9 3.  4.2 1.5]
 [6.  2.2 4.  1. ]
 [6.1 2.9 4.7 1.4]
 [5.6 2.9 3.6 1.3]
 [6.7 3.1 4.4 1.4]
 [5.6 3.  4.5 1.5]
 [5.8 2.7 4.1 1. ]
 [6.2 2.2 4.5 1.5]
 [5.6 2.5 3.9 1.1]
 [5.9 3.2 4.8 1.8]
 [6.1 2.8 4.  1.3]
 [6.3 2.5 4.9 1.5]
 [6.1 2.8 4.7 1.2]
 [6.4 2.9 4.3 1.3]
 [6.6 3.  4.4 1.4]
 [6.8 2.8 4.8 1.4]
 [6.7 3.  5.  1.7]
 [6.  2.9 4.5 1.5]
 [5.7 2.6 3.5 1. ]
 [5.5 2.4 3.8 1.1]
 [5.5 2.4 3.7 1. ]
 [5.8 2.7 3.9 1.2]
 [6.  2.7 5.1 1.6]
 [5.4 3.  4.5 1.5]
 [6.  3.4 4.5 1.6]
 [6.7 3.1 4.7 1.5]
 [6.3 2.3 4.4 1.3]
 [5.6 3.  4.1 1.3]
 [5.5 2.5 4.  1.3]
 [5.5 2.6 4.4 1.2]
 [6.1 3.  4.6 1.4]
 [5.8 2.6 4.  1.2]
 [5.  2.3 3.3 1. ]
 [5.6 2.7 4.2 1.3]
 [5.7 3.  4.2 1.2]
 [5.7 2.9 4.2 1.3]
 [6.2 2.9 4.3 1.3]
 [5.1 2.5 3.  1.1]
 [5.7 2.8 4.1 1.3]
 [6.3 3.3 6.  2.5]
 [5.8 2.7 5.1 1.9]
 [7.1 3.  5.9 2.1]
 [6.3 2.9 5.6 1.8]
 [6.5 3.  5.8 2.2]
 [7.6 3.  6.6 2.1]
 [4.9 2.5 4.5 1.7]
 [7.3 2.9 6.3 1.8]
 [6.7 2.5 5.8 1.8]
 [7.2 3.6 6.1 2.5]
 [6.5 3.2 5.1 2. ]
 [6.4 2.7 5.3 1.9]
 [6.8 3.  5.5 2.1]
 [5.7 2.5 5.  2. ]
 [5.8 2.8 5.1 2.4]
 [6.4 3.2 5.3 2.3]
 [6.5 3.  5.5 1.8]
 [7.7 3.8 6.7 2.2]
 [7.7 2.6 6.9 2.3]
 [6.  2.2 5.  1.5]
 [6.9 3.2 5.7 2.3]
 [5.6 2.8 4.9 2. ]
 [7.7 2.8 6.7 2. ]
 [6.3 2.7 4.9 1.8]
 [6.7 3.3 5.7 2.1]
 [7.2 3.2 6.  1.8]
 [6.2 2.8 4.8 1.8]
 [6.1 3.  4.9 1.8]
 [6.4 2.8 5.6 2.1]
 [7.2 3.  5.8 1.6]
 [7.4 2.8 6.1 1.9]
 [7.9 3.8 6.4 2. ]
 [6.4 2.8 5.6 2.2]
 [6.3 2.8 5.1 1.5]
 [6.1 2.6 5.6 1.4]
 [7.7 3.  6.1 2.3]
 [6.3 3.4 5.6 2.4]
 [6.4 3.1 5.5 1.8]
 [6.  3.  4.8 1.8]
 [6.9 3.1 5.4 2.1]
 [6.7 3.1 5.6 2.4]
 [6.9 3.1 5.1 2.3]
 [5.8 2.7 5.1 1.9]
 [6.8 3.2 5.9 2.3]
 [6.7 3.3 5.7 2.5]
 [6.7 3.  5.2 2.3]
 [6.3 2.5 5.  1.9]
 [6.5 3.  5.2 2. ]
 [6.2 3.4 5.4 2.3]
 [5.9 3.  5.1 1.8]]

target의 type:  <class 'numpy.ndarray'>
target의 shape:  (150,)
target: 
 [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2
 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
 2 2]
2-1. Tutorial - 붗꽃 품종 예측하기
In [1]:
from IPython.core.display import display, HTML
display(HTML("<style> .container{width:90% !important;}</style>"))

2-1. Tutorial - 붗꽃 품종 예측하기

붗꽃 데이터 세트로 붓꽃의 품종을 분류하는 예제 데이터 세트를 사이킷런에서 받아 간단한 머신러닝 구현

  • 꽃잎의 길이, 너비 꽆받침의 길이와 너비를 Feature로 꽃의 품종을 예측
In [2]:
from sklearn.datasets import load_iris
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import train_test_split
import pandas as pd
In [3]:
# 붓꽃 데이터 세트를 로딩
iris = load_iris()

# iris.data는 Iris 데이터 세트에서 feature만으로 된 데이터를 numpy로 가지고 있습니다.
iris_data = iris.data

# iris.target은 붓꽃 데이터 세트에서 레이블(결정 값) 데이터를 numpy로 가지고 있습니다.
iris_label = iris.target
print('iris target값: ', iris_label)
print('iris target명: ', iris.target_names)

# 붓꽃 데이터 세트를 자세히 보기 위해 DataFrame으로 변환합니다.
iris_df = pd.DataFrame(data=iris_data, columns=iris.feature_names)
iris_df ['label'] = iris.target
iris_df.head(3)
iris target값:  [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2
 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
 2 2]
iris target명:  ['setosa' 'versicolor' 'virginica']
Out[3]:
sepal length (cm) sepal width (cm) petal length (cm) petal width (cm) label
0 5.1 3.5 1.4 0.2 0
1 4.9 3.0 1.4 0.2 0
2 4.7 3.2 1.3 0.2 0
  • Feature : sepal length (cm), sepal width (cm), petal length (cm), petal width (cm)
  • Target(Label) : 0 - 'setosa', 1 - 'versicolor', 2-'virginica'
  • Iris 데이터를 학습용 데이터와 테스트용 데이터를 분리
    → 학습 데이터로 학습한 모델의 성능 측정용으로 테스트 세트 사용

  • train_test_split(피처데이터셋, 라벨 데이터셋, test_size =값 , random_state = 값 )
    → test_size 입력 값의 비율로 데이터를 쉽게 분할

In [4]:
X_train, X_test, y_train, y_test = train_test_split(iris_data, iris_label, test_size=0.2, random_state=11)

학습 데이터를 확보했으니 이를 기반으로 Decision Tree를 활용하여 학습과 예측

In [5]:
# DecisionTreeClassifier 객체 생성
dt_clf = DecisionTreeClassifier(random_state=11)

# 학습수행
dt_clf.fit(X_train, y_train)
Out[5]:
DecisionTreeClassifier(class_weight=None, criterion='gini', max_depth=None,
            max_features=None, max_leaf_nodes=None,
            min_impurity_decrease=0.0, min_impurity_split=None,
            min_samples_leaf=1, min_samples_split=2,
            min_weight_fraction_leaf=0.0, presort=False, random_state=11,
            splitter='best')

예측은 반드시 학습 데이터가 아닌 다른 데이터를 이용하며 일반적으로 테스트 데이터 세트를 이용

predict()에 테스트용 피처데이터를 입력하여 예측 값을 반환

In [6]:
# 학습이 완료된 DecisionTreeClassifier 객체에서 테스트 데이터 세트로 예측 수행
y_pred = dt_clf.predict(X_test)

학습된 모델에 대한 예측 성능을 평가 : iris 데이터셋에서는 이번에 정확도로 측정

accuracy_score(실제 레이블 세트, 예측 레이블 세트) : 정확도를 측정하기 위한 사이킷런의 함수

In [7]:
from sklearn.metrics import accuracy_score
print('예측정확도 : {0: .4f}'.format(accuracy_score(y_test, y_pred)))
예측정확도 :  0.9333

학습한 DecisionTree의 정확도가 0.9333으로 측정

정리 : 예측 프로세스


(1) 데이터 세트 분리 : 학습데이터와 테스트데이터로 분리 (train_test_split())

(2) 모델 학습 : 학습데이터를 기반으로 머신러닝 알고리즘을 적용하여 모델을 학습

(3) 예측 수행 : 학습된 모델을 이용하여 테스트 데이터의 분류를 예측

(4) 평가 : 예측 결과값과 실제 결과값을 비교하여 모델 성능을 평가

In [ ]:
 
1-2. Pandas 개요
  • Pandas : 행과 열로 이루어진 2차원 데이터을 가공/처리할 수 있는 다양한 기능을 제공
    • DataFrame : 여러 개의 행과 열로 이뤄진 2차원 데이터를 담는 데이터 구조체
    • Index : RDBMS의 PK처럼 개별 데이터를 고유하게 식별하는 키 값, 데이터프레임과 시리즈 모두 인덱스를 키 값으로 가짐
    • Series : 데이터프레임과 가장 큰 차이는 컬럼이 하나뿐인 데이터 구조체라는 것

판다스 시작 - 파일 로딩, 기본 API

In [1]:
# 판다스 모듈의 불러오기
import pandas as pd
  • 판다스는 다양한 포멧으로 된 파일을 데이터프레임으로 로딩할 수 있는 편리한 API 제공

    • read.csv() (콤마 구분 파일), read_table() (탭구분 파일), read_fwf() 등

  • read.csv() 의 인자

    • sep : 구분 문자를 입력하는 인자로 default는 콤마 (예시 : 탭 구분 sep = '\t')
    • filepath : 파일 경로 입력 / 파일명만 입력되면 실행파일이 있는 디렉터리와 동일한 디렉터리에 있는 파일명을 로딩
In [2]:
titanic_df = pd.read_csv('Titanic/input/train.csv')
print('titanic_df 변수 type: ', type(titanic_df))
titanic_df 변수 type:  <class 'pandas.core.frame.DataFrame'>
  • DataFrame.head() : 데이터 프레임의 맨 앞에 있는 일부 행을 출력, 기본 값으로 5개를 출력함
In [3]:
titanic_df.head(3)
Out[3]:
PassengerId Survived Pclass Name Sex Age SibSp Parch Ticket Fare Cabin Embarked
0 1 0 3 Braund, Mr. Owen Harris male 22.0 1 0 A/5 21171 7.2500 NaN S
1 2 1 1 Cumings, Mrs. John Bradley (Florence Briggs Th... female 38.0 1 0 PC 17599 71.2833 C85 C
2 3 1 3 Heikkinen, Miss. Laina female 26.0 0 0 STON/O2. 3101282 7.9250 NaN S
  • DataFrame.shape : 데이터프레임의 행과 열을 튜플 형태로 출력
In [4]:
titanic_df.shape
Out[4]:
(891, 12)
  • DataFrame.info() : 데이터프레임의 총 데이터건수, 데이터타입, Null 건수를 출력
In [5]:
titanic_df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 891 entries, 0 to 890
Data columns (total 12 columns):
PassengerId    891 non-null int64
Survived       891 non-null int64
Pclass         891 non-null int64
Name           891 non-null object
Sex            891 non-null object
Age            714 non-null float64
SibSp          891 non-null int64
Parch          891 non-null int64
Ticket         891 non-null object
Fare           891 non-null float64
Cabin          204 non-null object
Embarked       889 non-null object
dtypes: float64(2), int64(5), object(5)
memory usage: 83.6+ KB
  • DataFrame.describe() : 칼럼별 숫자형 데이터 값의 n-percentile분포, 평균, 최댓값, 최솟값을 나타냄 (object타입은 출력에서 제외)

    → 숫자형 컬럼에 대한 개략적인 분포를 알 수 있음
    count : null이 아닌 데이터 건수를 나타냄

In [6]:
titanic_df.describe()
Out[6]:
PassengerId Survived Pclass Age SibSp Parch Fare
count 891.000000 891.000000 891.000000 714.000000 891.000000 891.000000 891.000000
mean 446.000000 0.383838 2.308642 29.699118 0.523008 0.381594 32.204208
std 257.353842 0.486592 0.836071 14.526497 1.102743 0.806057 49.693429
min 1.000000 0.000000 1.000000 0.420000 0.000000 0.000000 0.000000
25% 223.500000 0.000000 2.000000 20.125000 0.000000 0.000000 7.910400
50% 446.000000 0.000000 3.000000 28.000000 0.000000 0.000000 14.454200
75% 668.500000 1.000000 3.000000 38.000000 1.000000 0.000000 31.000000
max 891.000000 1.000000 3.000000 80.000000 8.000000 6.000000 512.329200
  • Series.value_counts() : 지정된 컬럼의 데이터 값 건수를 반환

    → value_counts()는 Series 형태의 데이터에만 사용이 가능(데이터프레임에는 사용이 불가)

In [7]:
titanic_df['Pclass'].value_counts()
Out[7]:
3    491
1    216
2    184
Name: Pclass, dtype: int64
  • type() : 괄호안의 데이터가 어떤 데이터 타입인지 나타내줌
In [8]:
type(titanic_df['Pclass'])
Out[8]:
pandas.core.series.Series
  • Series는 인덱스와 단 하나의 컬럼으로 구성된 데이터 세트
In [9]:
titanic_df['Pclass'].head()
Out[9]:
0    3
1    1
2    3
3    1
4    3
Name: Pclass, dtype: int64

→ 왼쪽의 0, 1, 2, 3, 4는 인덱스 번호를(원 데이터 프레임의 인덱스와 동일), 오른쪽의 3, 1, 3, 1, 3은 Series의 데이터 값을 나타냄

In [10]:
value_counts = titanic_df['Pclass'].value_counts()
print(type(value_counts))
print(value_counts)
<class 'pandas.core.series.Series'>
3    491
1    216
2    184
Name: Pclass, dtype: int64

→ value_counts() 에 의해 반환되는 데이터 타입 역시 Series 객체로 좌측의 3, 1, 2도 인덱스임
→ 위에서 볼 수 있듯이 인덱스는 0, 1, 2, 3 ...과 같이 순차적인 값만 가능한게 아니라 고유성이 된다면 고유 칼럼 값도 인덱스로 가질 수 있다(문자열도 가능)

데이터프레임, 리스트, 딕셔너리, 넘파이 ndarray 상호변환

  • 데이터프레임은 파이썬의 리스트, 딕셔너리, 넘파이 ndarray 등 다양한 데이터로부터 생성이 가능, 그 반대 방향으로도 변환 가능
  • 사이킷런의 많은 API들은 데이터프레임을 인자로 입력받을 수 있지만, 기본적으로 넘파이의 ndarray를 입력 인자로 사용하는 경우가 대부분

넘파이 ndarray, 리스트, 딕셔너리 --> 데이터프레임 변환

  • 데이터프레임은 ndarray, 리스트와 달리 컬럼명을 가지고 있음
    → ndarray, 리스트, 딕셔너리로 데이터를 입력받고, 컬럼명 리스트로 컬럼명을 입력받아서 데이터프레임을 생성
In [11]:
import numpy as np

col_name1 = ['col1']
list1 = [1, 2, 3]
array1 = np.array(list1)
print('array1 shape :', array1.shape)

# 리스트를 이용해 데이터프레임 생성
df_list1 = pd.DataFrame(list1, columns=col_name1)
print('\n1차원 리스트로 만든 DataFrame: \n', df_list1)

# 넘파이 ndarray를 이용해 데이터프레임 생성
df_array1 = pd.DataFrame(array1, columns=col_name1)
print('\n1차원 ndarray로 만든 DataFrame: \n', df_array1)
array1 shape : (3,)

1차원 리스트로 만든 DataFrame: 
    col1
0     1
1     2
2     3

1차원 ndarray로 만든 DataFrame: 
    col1
0     1
1     2
2     3

위의 예시에서는 1차원 형태 데이터를 기반으로 데이터프레임을 생성했기 때문에, 컬럼명도 하나만 가능

In [12]:
# 2행 3열 형태의 리스트와 ndarray를 기반으로 2차원 형태 데이터프레임 생성

#3개의 컬럼명이 필요
col_name2 = ['col1', 'col2', 'col3']

# 2행 3열 형태의 리스트와 ndarray를 생성한 뒤 데이터프레임으로 반환
list2 = [[1, 2, 3],
            [11, 12, 13]]
array2 = np.array(list2)
print('array2 shape: ', array2.shape)

df_list2 = pd.DataFrame(list2, columns = col_name2)
print('\n2차원 리스트로 만든 DataFrame: \n',df_list2)

df_array2 = pd.DataFrame(array2, columns = col_name2)
print('\n2차원 리스트로 만든 DataFrame: \n', df_array2)
array2 shape:  (2, 3)

2차원 리스트로 만든 DataFrame: 
    col1  col2  col3
0     1     2     3
1    11    12    13

2차원 리스트로 만든 DataFrame: 
    col1  col2  col3
0     1     2     3
1    11    12    13
  • 딕셔너리 : 키는 컬럼명으로, 값은 키에 해당하는 컬럼 데이터로 전환
In [13]:
# 키(key)는 문자열 컬럼명으로 매핑, 값(value)은 리스트형(또는 ndarray) 컬럼 데이터로 매핑
dic = {'col1' : [1, 11], 'col2' : [2, 22], 'col3' : np.array([3, 33])}
df_dict = pd.DataFrame(dic)
print('딕셔너리로 만든 DataFrame: \n', df_dict)
딕셔너리로 만든 DataFrame: 
    col1  col2  col3
0     1     2     3
1    11    22    33

데이터프레임 --> 넘파이 ndarray, 리스트, 딕셔너리 변환

  • DataFrame.values : 데이터프레임을 넘파이 ndarray로 변환
    → 많은 머신러닝 패키지가 기본 데이터형으로 넘파이 ndarray를 사용하기 때문에 중요함
In [14]:
# 데이터프레임을 ndarray로 변환
array3 = df_dict.values
print('df_dict.values 타입: ', type(array3), '\ndf_dict.valuse shape: ', array3.shape)
print(array3)
df_dict.values 타입:  <class 'numpy.ndarray'> 
df_dict.valuse shape:  (2, 3)
[[ 1  2  3]
 [11 22 33]]

→ 데이터프레임의 컬럼명 없이 값만으로 ndarray가 생성됨

→ 위에서 변환된 ndarray에 tolist() 를 호출하면 리스트로 변환

In [15]:
# 데이터프레임을 리스트로 변환
list3 = array3.tolist()
print('df_dict.vlaues.tolist() 타입: ', type(list3))
print(list3)
df_dict.vlaues.tolist() 타입:  <class 'list'>
[[1, 2, 3], [11, 22, 33]]
  • DataFrame.to_dict() : 데이터프레임을 딕셔너리로 반환, 인자로 'list'를 입력하면 딕셔너리의 값이 리스트 형으로 반환
In [16]:
# 데이터프레임을 리스트로 변환
dict3 = df_dict.to_dict('list')
print('df_dict.to_dict() 타입: ', type(dict3))
dict3
df_dict.to_dict() 타입:  <class 'dict'>
Out[16]:
{'col1': [1, 11], 'col2': [2, 22], 'col3': [3, 33]}

데이터프레임의 컬럼 데이터 세트 생성과 수정

  • [ ] 연산자를 이용하여 컬럼 데이터 세트의 생성과 수정 가능
In [17]:
# 타이타닉 데이터프레임에 Age_0 이라는 새로운 컬럼을 추가하고 일괄적으로 0을 할당
titanic_df['Age_0'] = 0
titanic_df.head(3)
Out[17]:
PassengerId Survived Pclass Name Sex Age SibSp Parch Ticket Fare Cabin Embarked Age_0
0 1 0 3 Braund, Mr. Owen Harris male 22.0 1 0 A/5 21171 7.2500 NaN S 0
1 2 1 1 Cumings, Mrs. John Bradley (Florence Briggs Th... female 38.0 1 0 PC 17599 71.2833 C85 C 0
2 3 1 3 Heikkinen, Miss. Laina female 26.0 0 0 STON/O2. 3101282 7.9250 NaN S 0
In [18]:
# Age의 각 값에 10을 곱한 Age_by_10 컬럼 생성
titanic_df['Age_by_10'] = titanic_df['Age']*10

# Parch와 SibSp의 값과 1을 더한 Family_No 컬럼 생성
titanic_df['Family_No'] = titanic_df['Parch'] + titanic_df['SibSp']+1

titanic_df.head(3)
Out[18]:
PassengerId Survived Pclass Name Sex Age SibSp Parch Ticket Fare Cabin Embarked Age_0 Age_by_10 Family_No
0 1 0 3 Braund, Mr. Owen Harris male 22.0 1 0 A/5 21171 7.2500 NaN S 0 220.0 2
1 2 1 1 Cumings, Mrs. John Bradley (Florence Briggs Th... female 38.0 1 0 PC 17599 71.2833 C85 C 0 380.0 2
2 3 1 3 Heikkinen, Miss. Laina female 26.0 0 0 STON/O2. 3101282 7.9250 NaN S 0 260.0 1
  • 데이터 프레임 내의 기존 컬럼도 이같은 방법으로 쉽게 업데이트 가능
In [19]:
# Age_by_10 컬럼 값에 일괄적으로 + 100 처리
titanic_df['Age_by_10'] = titanic_df['Age_by_10'] + 100
titanic_df.head(3)
Out[19]:
PassengerId Survived Pclass Name Sex Age SibSp Parch Ticket Fare Cabin Embarked Age_0 Age_by_10 Family_No
0 1 0 3 Braund, Mr. Owen Harris male 22.0 1 0 A/5 21171 7.2500 NaN S 0 320.0 2
1 2 1 1 Cumings, Mrs. John Bradley (Florence Briggs Th... female 38.0 1 0 PC 17599 71.2833 C85 C 0 480.0 2
2 3 1 3 Heikkinen, Miss. Laina female 26.0 0 0 STON/O2. 3101282 7.9250 NaN S 0 360.0 1

데이터프레임 데이터 삭제

  • drop() : 데이터삭제를 하는데 사용

    DataFrame.drop(labels=None, axis=0, index=None, columns = None, level = None, inplace = False, errors='raise')

→ 이 중 가장 중요한 파라미터는 labels, axis, inplace

  • label : 삭제할 컬럼명 지정, 여러개를 지울 때는 리스트로 넣을 수 있음
  • axis = 0 : 행 드롭 --> label을 자동으로 인덱스로 간주
  • axis = 1 : 열 드롭 --> label에 원하는 컬럼명과 axis =1을 입력하면 지정된 컬럼을 드롭
    • 주로, 컬럼 단위로 많이 삭제를 하게 되지만, 아웃라이어를 지울 때는 로우 단위로 삭제를 함
  • inplace : default값은 False로 자신의 데이터프레임은 삭제하지 않고, 삭제된 결과를 데이터프레임으로 반환. True로 하면 원본에서 삭제하고 반환되는 값 없음

In [20]:
titanic_drop_df = titanic_df.drop('Age_0', axis=1)
titanic_drop_df.head(3)
Out[20]:
PassengerId Survived Pclass Name Sex Age SibSp Parch Ticket Fare Cabin Embarked Age_by_10 Family_No
0 1 0 3 Braund, Mr. Owen Harris male 22.0 1 0 A/5 21171 7.2500 NaN S 320.0 2
1 2 1 1 Cumings, Mrs. John Bradley (Florence Briggs Th... female 38.0 1 0 PC 17599 71.2833 C85 C 480.0 2
2 3 1 3 Heikkinen, Miss. Laina female 26.0 0 0 STON/O2. 3101282 7.9250 NaN S 360.0 1

titanic_drop_df에는 'Age_0'이 삭제되었지만, titanic_df을 조회해보면 삭제되지 않았음

In [21]:
titanic_df.head(3)
Out[21]:
PassengerId Survived Pclass Name Sex Age SibSp Parch Ticket Fare Cabin Embarked Age_0 Age_by_10 Family_No
0 1 0 3 Braund, Mr. Owen Harris male 22.0 1 0 A/5 21171 7.2500 NaN S 0 320.0 2
1 2 1 1 Cumings, Mrs. John Bradley (Florence Briggs Th... female 38.0 1 0 PC 17599 71.2833 C85 C 0 480.0 2
2 3 1 3 Heikkinen, Miss. Laina female 26.0 0 0 STON/O2. 3101282 7.9250 NaN S 0 360.0 1
In [22]:
# inplace = True 로 두어 원본 데이터프레임에서도 삭제
drop_result = titanic_df.drop(['Age_0', 'Age_by_10','Family_No'], axis=1, inplace = True)
print('inplace = True로 drop 후 반환된 값: ', drop_result)
titanic_df.head(3)
inplace = True로 drop 후 반환된 값:  None
Out[22]:
PassengerId Survived Pclass Name Sex Age SibSp Parch Ticket Fare Cabin Embarked
0 1 0 3 Braund, Mr. Owen Harris male 22.0 1 0 A/5 21171 7.2500 NaN S
1 2 1 1 Cumings, Mrs. John Bradley (Florence Briggs Th... female 38.0 1 0 PC 17599 71.2833 C85 C
2 3 1 3 Heikkinen, Miss. Laina female 26.0 0 0 STON/O2. 3101282 7.9250 NaN S

inplace = True 는 반환하는 값이 없기 때문에 아래처럼 코드를 짜면 안됨 (원본 데이터 프레임이 그냥 날아가게 됨)

titanic_df = titanic_df.drop(['Age_0', 'Age_10', 'Family_No'], inplace = True)

In [23]:
### axis = 0으로 설정하여 index 0, 1, 2 로우를 삭제
titanic_df.drop([0, 1, 2], axis=0, inplace=True)
titanic_df.head(3)
Out[23]:
PassengerId Survived Pclass Name Sex Age SibSp Parch Ticket Fare Cabin Embarked
3 4 1 1 Futrelle, Mrs. Jacques Heath (Lily May Peel) female 35.0 1 0 113803 53.1000 C123 S
4 5 0 3 Allen, Mr. William Henry male 35.0 0 0 373450 8.0500 NaN S
5 6 0 3 Moran, Mr. James male NaN 0 0 330877 8.4583 NaN Q

Index 객체

  • 판다스의 Index객체는 RDBMS의 PK와 유사하게 데이터프레임, 시리즈의 레코드를 고유하게 식별하는 객체
  • DataFrame.index 또는 Series.index 를 통해 인덱스 객체만 추출할 수 있음
    → 반환된 Index 객체의 값은 넘파이 1차원 ndarray와 같은 형태
In [24]:
# 원본파일 다시 로딩
titanic_df = pd.read_csv('Titanic/input/train.csv')

# Index 객체 추출
index = titanic_df.index

# Index 객체는 1차원 ndarray임
print('Index 객체의 array 데이터:')
index.values
Index 객체의 array 데이터:
Out[24]:
array([  0,   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,  29,  30,  31,  32,  33,  34,  35,  36,  37,  38,
        39,  40,  41,  42,  43,  44,  45,  46,  47,  48,  49,  50,  51,
        52,  53,  54,  55,  56,  57,  58,  59,  60,  61,  62,  63,  64,
        65,  66,  67,  68,  69,  70,  71,  72,  73,  74,  75,  76,  77,
        78,  79,  80,  81,  82,  83,  84,  85,  86,  87,  88,  89,  90,
        91,  92,  93,  94,  95,  96,  97,  98,  99, 100, 101, 102, 103,
       104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116,
       117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129,
       130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142,
       143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155,
       156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168,
       169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181,
       182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194,
       195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207,
       208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220,
       221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233,
       234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246,
       247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259,
       260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272,
       273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285,
       286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298,
       299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311,
       312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324,
       325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337,
       338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350,
       351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363,
       364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376,
       377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389,
       390, 391, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402,
       403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415,
       416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428,
       429, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441,
       442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454,
       455, 456, 457, 458, 459, 460, 461, 462, 463, 464, 465, 466, 467,
       468, 469, 470, 471, 472, 473, 474, 475, 476, 477, 478, 479, 480,
       481, 482, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493,
       494, 495, 496, 497, 498, 499, 500, 501, 502, 503, 504, 505, 506,
       507, 508, 509, 510, 511, 512, 513, 514, 515, 516, 517, 518, 519,
       520, 521, 522, 523, 524, 525, 526, 527, 528, 529, 530, 531, 532,
       533, 534, 535, 536, 537, 538, 539, 540, 541, 542, 543, 544, 545,
       546, 547, 548, 549, 550, 551, 552, 553, 554, 555, 556, 557, 558,
       559, 560, 561, 562, 563, 564, 565, 566, 567, 568, 569, 570, 571,
       572, 573, 574, 575, 576, 577, 578, 579, 580, 581, 582, 583, 584,
       585, 586, 587, 588, 589, 590, 591, 592, 593, 594, 595, 596, 597,
       598, 599, 600, 601, 602, 603, 604, 605, 606, 607, 608, 609, 610,
       611, 612, 613, 614, 615, 616, 617, 618, 619, 620, 621, 622, 623,
       624, 625, 626, 627, 628, 629, 630, 631, 632, 633, 634, 635, 636,
       637, 638, 639, 640, 641, 642, 643, 644, 645, 646, 647, 648, 649,
       650, 651, 652, 653, 654, 655, 656, 657, 658, 659, 660, 661, 662,
       663, 664, 665, 666, 667, 668, 669, 670, 671, 672, 673, 674, 675,
       676, 677, 678, 679, 680, 681, 682, 683, 684, 685, 686, 687, 688,
       689, 690, 691, 692, 693, 694, 695, 696, 697, 698, 699, 700, 701,
       702, 703, 704, 705, 706, 707, 708, 709, 710, 711, 712, 713, 714,
       715, 716, 717, 718, 719, 720, 721, 722, 723, 724, 725, 726, 727,
       728, 729, 730, 731, 732, 733, 734, 735, 736, 737, 738, 739, 740,
       741, 742, 743, 744, 745, 746, 747, 748, 749, 750, 751, 752, 753,
       754, 755, 756, 757, 758, 759, 760, 761, 762, 763, 764, 765, 766,
       767, 768, 769, 770, 771, 772, 773, 774, 775, 776, 777, 778, 779,
       780, 781, 782, 783, 784, 785, 786, 787, 788, 789, 790, 791, 792,
       793, 794, 795, 796, 797, 798, 799, 800, 801, 802, 803, 804, 805,
       806, 807, 808, 809, 810, 811, 812, 813, 814, 815, 816, 817, 818,
       819, 820, 821, 822, 823, 824, 825, 826, 827, 828, 829, 830, 831,
       832, 833, 834, 835, 836, 837, 838, 839, 840, 841, 842, 843, 844,
       845, 846, 847, 848, 849, 850, 851, 852, 853, 854, 855, 856, 857,
       858, 859, 860, 861, 862, 863, 864, 865, 866, 867, 868, 869, 870,
       871, 872, 873, 874, 875, 876, 877, 878, 879, 880, 881, 882, 883,
       884, 885, 886, 887, 888, 889, 890])
  • 반환된 Index의 array는 ndarray와 유사하게 단일 값 반환 및 슬라이싱도 가능
In [25]:
print(type(index.values))
print(index.values.shape)
print(index[:5].values)
print(index.values[:5])
print(index[6])
<class 'numpy.ndarray'>
(891,)
[0 1 2 3 4]
[0 1 2 3 4]
6
  • 한 번 만들어진 Index 객체는 함부로 변경할 수 없음.
In [26]:
# 인덱스는 함부로 변경할 수 없기 때문에 아래같이 인덱스 객체 값을 변경하는 작업을 수행할 수 없음
index[0] = 5
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-26-ace474fffa0d> in <module>
      1 # 인덱스는 함부로 변경할 수 없기 때문에 아래같이 인덱스 객체 값을 변경하는 작업을 수행할 수 없음
----> 2 index[0] = 5

/anaconda3/lib/python3.7/site-packages/pandas/core/indexes/base.py in __setitem__(self, key, value)
   3936 
   3937     def __setitem__(self, key, value):
-> 3938         raise TypeError("Index does not support mutable operations")
   3939 
   3940     def __getitem__(self, key):

TypeError: Index does not support mutable operations
  • Series 객체는 Index 객체를 포함하지만 Series 객체에 연산 함수를 적용할 때, Index는 연산에서 제외됨 (오로지 식별용으로 사용)
In [27]:
series_fair = titanic_df['Fare']
print('Fair Series max 값: ', series_fair.max())
print('Fair Series sum 값: ', series_fair.sum())
print('sum() Fair Series: ', sum(series_fair))
print('Fair Series + 3: \n', (series_fair+3).head(3))
Fair Series max 값:  512.3292
Fair Series sum 값:  28693.9493
sum() Fair Series:  28693.949299999967
Fair Series + 3: 
 0    10.2500
1    74.2833
2    10.9250
Name: Fare, dtype: float64
  • reset_index(): 새로운 index를 연속 숫자형으로 할당하고, 기존 index는 index라는 새로운 컬럼명으로 추가
                  → 기존 index가 연속형 int 숫자형이 아닐 경우 이를 다시 연속형 int 숫자로 만들 때 주로 사용
    
    • Series에 reset_index를 사용하면 데이터프레임이 반환됨
    • 파라미터 중 drop = True 로 설정하면 기존 인덱스는 새로운 컬럼으로 유지되지 않고 삭제됨
In [28]:
titanic_reset_df = titanic_df.reset_index(inplace=False)
titanic_reset_df.head(3)
Out[28]:
index PassengerId Survived Pclass Name Sex Age SibSp Parch Ticket Fare Cabin Embarked
0 0 1 0 3 Braund, Mr. Owen Harris male 22.0 1 0 A/5 21171 7.2500 NaN S
1 1 2 1 1 Cumings, Mrs. John Bradley (Florence Briggs Th... female 38.0 1 0 PC 17599 71.2833 C85 C
2 2 3 1 3 Heikkinen, Miss. Laina female 26.0 0 0 STON/O2. 3101282 7.9250 NaN S
In [29]:
print('### before reset_index ###')
value_counts = titanic_df['Pclass'].value_counts()
print(value_counts)
print('value_counts 객체 변수 타입: ', type(value_counts))
new_value_counts = value_counts.reset_index(inplace=False)
print('\n### after reset_index ###')
print(new_value_counts)
print('new_value_counts 객체 변수 타입: ', type(new_value_counts))
### before reset_index ###
3    491
1    216
2    184
Name: Pclass, dtype: int64
value_counts 객체 변수 타입:  <class 'pandas.core.series.Series'>

### after reset_index ###
   index  Pclass
0      3     491
1      1     216
2      2     184
new_value_counts 객체 변수 타입:  <class 'pandas.core.frame.DataFrame'>

데이터 셀렉션 및 필터링

  • ix[ ] , iloc[ ], loc[ ] 를 활용해서 데이터를 추출할 수 있음

(1) DataFrame[ ]

DataFrame['컬럼명'] : 해당 컬럼에 해당하는 데이터를 조회, 여러 개의 컬럼을 조회할 때는 컬럼의 리스트 객체를 이용

In [30]:
# 단일컬럼 데이터 추출
titanic_df['Pclass'].head(3)
Out[30]:
0    3
1    1
2    3
Name: Pclass, dtype: int64
In [31]:
# 여러컬럼 데이터 추출
titanic_df[['Survived', 'Pclass', 'Name']].head(3)
Out[31]:
Survived Pclass Name
0 0 3 Braund, Mr. Owen Harris
1 1 1 Cumings, Mrs. John Bradley (Florence Briggs Th...
2 1 3 Heikkinen, Miss. Laina

DataFrame[불린인덱싱] : 해당 조건에 맞는 데이터를 불린인덱싱 기반으로 불러옴, 유용하게 자주쓰는 방법

In [32]:
titanic_df[titanic_df['Pclass']==3].head(3)
Out[32]:
PassengerId Survived Pclass Name Sex Age SibSp Parch Ticket Fare Cabin Embarked
0 1 0 3 Braund, Mr. Owen Harris male 22.0 1 0 A/5 21171 7.250 NaN S
2 3 1 3 Heikkinen, Miss. Laina female 26.0 0 0 STON/O2. 3101282 7.925 NaN S
4 5 0 3 Allen, Mr. William Henry male 35.0 0 0 373450 8.050 NaN S

(2) DataFrame.iloc[ row index , column index ]

위치 기반 인덱싱 방법으로 행과 열의 위치를 지정해서 데이터를 불러옴

In [33]:
titanic_df.iloc[0, 4]
Out[33]:
'male'
  • 위치 인덱스 슬라이싱으로도 검색 가능
In [34]:
titanic_df.iloc[4:7, 2:5]
Out[34]:
Pclass Name Sex
4 3 Allen, Mr. William Henry male
5 3 Moran, Mr. James male
6 1 McCarthy, Mr. Timothy J male
  • 리스트를 활용해 특정 위치를 지정해서도 사용 가능
In [35]:
titanic_df.iloc[[4,5,6], [1, 2, 5]]
Out[35]:
Survived Pclass Age
4 0 3 35.0
5 0 3 NaN
6 0 1 54.0
In [36]:
# 전체 반환
titanic_df[:]
Out[36]:
PassengerId Survived Pclass Name Sex Age SibSp Parch Ticket Fare Cabin Embarked
0 1 0 3 Braund, Mr. Owen Harris male 22.0 1 0 A/5 21171 7.2500 NaN S
1 2 1 1 Cumings, Mrs. John Bradley (Florence Briggs Th... female 38.0 1 0 PC 17599 71.2833 C85 C
2 3 1 3 Heikkinen, Miss. Laina female 26.0 0 0 STON/O2. 3101282 7.9250 NaN S
3 4 1 1 Futrelle, Mrs. Jacques Heath (Lily May Peel) female 35.0 1 0 113803 53.1000 C123 S
4 5 0 3 Allen, Mr. William Henry male 35.0 0 0 373450 8.0500 NaN S
5 6 0 3 Moran, Mr. James male NaN 0 0 330877 8.4583 NaN Q
6 7 0 1 McCarthy, Mr. Timothy J male 54.0 0 0 17463 51.8625 E46 S
7 8 0 3 Palsson, Master. Gosta Leonard male 2.0 3 1 349909 21.0750 NaN S
8 9 1 3 Johnson, Mrs. Oscar W (Elisabeth Vilhelmina Berg) female 27.0 0 2 347742 11.1333 NaN S
9 10 1 2 Nasser, Mrs. Nicholas (Adele Achem) female 14.0 1 0 237736 30.0708 NaN C
10 11 1 3 Sandstrom, Miss. Marguerite Rut female 4.0 1 1 PP 9549 16.7000 G6 S
11 12 1 1 Bonnell, Miss. Elizabeth female 58.0 0 0 113783 26.5500 C103 S
12 13 0 3 Saundercock, Mr. William Henry male 20.0 0 0 A/5. 2151 8.0500 NaN S
13 14 0 3 Andersson, Mr. Anders Johan male 39.0 1 5 347082 31.2750 NaN S
14 15 0 3 Vestrom, Miss. Hulda Amanda Adolfina female 14.0 0 0 350406 7.8542 NaN S
15 16 1 2 Hewlett, Mrs. (Mary D Kingcome) female 55.0 0 0 248706 16.0000 NaN S
16 17 0 3 Rice, Master. Eugene male 2.0 4 1 382652 29.1250 NaN Q
17 18 1 2 Williams, Mr. Charles Eugene male NaN 0 0 244373 13.0000 NaN S
18 19 0 3 Vander Planke, Mrs. Julius (Emelia Maria Vande... female 31.0 1 0 345763 18.0000 NaN S
19 20 1 3 Masselmani, Mrs. Fatima female NaN 0 0 2649 7.2250 NaN C
20 21 0 2 Fynney, Mr. Joseph J male 35.0 0 0 239865 26.0000 NaN S
21 22 1 2 Beesley, Mr. Lawrence male 34.0 0 0 248698 13.0000 D56 S
22 23 1 3 McGowan, Miss. Anna "Annie" female 15.0 0 0 330923 8.0292 NaN Q
23 24 1 1 Sloper, Mr. William Thompson male 28.0 0 0 113788 35.5000 A6 S
24 25 0 3 Palsson, Miss. Torborg Danira female 8.0 3 1 349909 21.0750 NaN S
25 26 1 3 Asplund, Mrs. Carl Oscar (Selma Augusta Emilia... female 38.0 1 5 347077 31.3875 NaN S
26 27 0 3 Emir, Mr. Farred Chehab male NaN 0 0 2631 7.2250 NaN C
27 28 0 1 Fortune, Mr. Charles Alexander male 19.0 3 2 19950 263.0000 C23 C25 C27 S
28 29 1 3 O'Dwyer, Miss. Ellen "Nellie" female NaN 0 0 330959 7.8792 NaN Q
29 30 0 3 Todoroff, Mr. Lalio male NaN 0 0 349216 7.8958 NaN S
... ... ... ... ... ... ... ... ... ... ... ... ...
861 862 0 2 Giles, Mr. Frederick Edward male 21.0 1 0 28134 11.5000 NaN S
862 863 1 1 Swift, Mrs. Frederick Joel (Margaret Welles Ba... female 48.0 0 0 17466 25.9292 D17 S
863 864 0 3 Sage, Miss. Dorothy Edith "Dolly" female NaN 8 2 CA. 2343 69.5500 NaN S
864 865 0 2 Gill, Mr. John William male 24.0 0 0 233866 13.0000 NaN S
865 866 1 2 Bystrom, Mrs. (Karolina) female 42.0 0 0 236852 13.0000 NaN S
866 867 1 2 Duran y More, Miss. Asuncion female 27.0 1 0 SC/PARIS 2149 13.8583 NaN C
867 868 0 1 Roebling, Mr. Washington Augustus II male 31.0 0 0 PC 17590 50.4958 A24 S
868 869 0 3 van Melkebeke, Mr. Philemon male NaN 0 0 345777 9.5000 NaN S
869 870 1 3 Johnson, Master. Harold Theodor male 4.0 1 1 347742 11.1333 NaN S
870 871 0 3 Balkic, Mr. Cerin male 26.0 0 0 349248 7.8958 NaN S
871 872 1 1 Beckwith, Mrs. Richard Leonard (Sallie Monypeny) female 47.0 1 1 11751 52.5542 D35 S
872 873 0 1 Carlsson, Mr. Frans Olof male 33.0 0 0 695 5.0000 B51 B53 B55 S
873 874 0 3 Vander Cruyssen, Mr. Victor male 47.0 0 0 345765 9.0000 NaN S
874 875 1 2 Abelson, Mrs. Samuel (Hannah Wizosky) female 28.0 1 0 P/PP 3381 24.0000 NaN C
875 876 1 3 Najib, Miss. Adele Kiamie "Jane" female 15.0 0 0 2667 7.2250 NaN C
876 877 0 3 Gustafsson, Mr. Alfred Ossian male 20.0 0 0 7534 9.8458 NaN S
877 878 0 3 Petroff, Mr. Nedelio male 19.0 0 0 349212 7.8958 NaN S
878 879 0 3 Laleff, Mr. Kristo male NaN 0 0 349217 7.8958 NaN S
879 880 1 1 Potter, Mrs. Thomas Jr (Lily Alexenia Wilson) female 56.0 0 1 11767 83.1583 C50 C
880 881 1 2 Shelley, Mrs. William (Imanita Parrish Hall) female 25.0 0 1 230433 26.0000 NaN S
881 882 0 3 Markun, Mr. Johann male 33.0 0 0 349257 7.8958 NaN S
882 883 0 3 Dahlberg, Miss. Gerda Ulrika female 22.0 0 0 7552 10.5167 NaN S
883 884 0 2 Banfield, Mr. Frederick James male 28.0 0 0 C.A./SOTON 34068 10.5000 NaN S
884 885 0 3 Sutehall, Mr. Henry Jr male 25.0 0 0 SOTON/OQ 392076 7.0500 NaN S
885 886 0 3 Rice, Mrs. William (Margaret Norton) female 39.0 0 5 382652 29.1250 NaN Q
886 887 0 2 Montvila, Rev. Juozas male 27.0 0 0 211536 13.0000 NaN S
887 888 1 1 Graham, Miss. Margaret Edith female 19.0 0 0 112053 30.0000 B42 S
888 889 0 3 Johnston, Miss. Catherine Helen "Carrie" female NaN 1 2 W./C. 6607 23.4500 NaN S
889 890 1 1 Behr, Mr. Karl Howell male 26.0 0 0 111369 30.0000 C148 C
890 891 0 3 Dooley, Mr. Patrick male 32.0 0 0 370376 7.7500 NaN Q

891 rows × 12 columns

(3) DataFrame.loc[ row index , column 명 ]

명칭 기반으로 데이터를 추출, 행위치에는 index 값(인덱스가 숫자형이면 숫자, 문자형이면 문자)을, 열위치에는 컬렴명을 입력

In [37]:
titanic_df.loc[3, 'Name']
Out[37]:
'Futrelle, Mrs. Jacques Heath (Lily May Peel)'

주의사항 :

일반적으로 슬라이싱 기호 ' : ' 를 적용할 때의 범위는 '시작값 ~ 끝값-1' 이지만

loc[ ]에서의 슬라이싱 결과는 '시작값~끝값'

→ 명칭기반의 인덱싱이기 때문에(명칭은 숫자형이 아닐수도 있기 때문에) -1을 할수가 없음

→ 인덱스가 숫자일 때, 범위 지정에 조심해야함

In [38]:
#명칭기반의 인덱싱인 loc에서의 결과
titanic_df.loc[221:224, ]
Out[38]:
PassengerId Survived Pclass Name Sex Age SibSp Parch Ticket Fare Cabin Embarked
221 222 0 2 Bracken, Mr. James H male 27.0 0 0 220367 13.0000 NaN S
222 223 0 3 Green, Mr. George Henry male 51.0 0 0 21440 8.0500 NaN S
223 224 0 3 Nenkoff, Mr. Christo male NaN 0 0 349234 7.8958 NaN S
224 225 1 1 Hoyt, Mr. Frederick Maxfield male 38.0 1 0 19943 90.0000 C93 S
In [39]:
#위치기반의 인덱싱인 iloc에서의 결과
titanic_df.iloc[221:224, ]
Out[39]:
PassengerId Survived Pclass Name Sex Age SibSp Parch Ticket Fare Cabin Embarked
221 222 0 2 Bracken, Mr. James H male 27.0 0 0 220367 13.0000 NaN S
222 223 0 3 Green, Mr. George Henry male 51.0 0 0 21440 8.0500 NaN S
223 224 0 3 Nenkoff, Mr. Christo male NaN 0 0 349234 7.8958 NaN S

(4) 불린 인덱싱

DataFrame[ ] , DataFrame.loc[ ] 에서 사용 가능

In [40]:
titanic_boolean = titanic_df[titanic_df['Age']>60]
print(type(titanic_boolean))
print(titanic_boolean.shape)
titanic_boolean.head(3)
<class 'pandas.core.frame.DataFrame'>
(22, 12)
Out[40]:
PassengerId Survived Pclass Name Sex Age SibSp Parch Ticket Fare Cabin Embarked
33 34 0 2 Wheadon, Mr. Edward H male 66.0 0 0 C.A. 24579 10.5000 NaN S
54 55 0 1 Ostby, Mr. Engelhart Cornelius male 65.0 0 1 113509 61.9792 B30 C
96 97 0 1 Goldschmidt, Mr. George B male 71.0 0 0 PC 17754 34.6542 A5 C

[ ]에 불린 인덱싱을 적용하면 반환되는 객체도 데이터 프레임으로 원하는 컬럼명만 별도로 추출할 수 있음

In [41]:
titanic_df[titanic_df['Age']>60][['Name', 'Pclass','Survived']].head(3)
Out[41]:
Name Pclass Survived
33 Wheadon, Mr. Edward H 2 0
54 Ostby, Mr. Engelhart Cornelius 1 0
96 Goldschmidt, Mr. George B 1 0

loc[ ] 를 이용해도 동일하게 적용이 가능

In [42]:
titanic_df.loc[titanic_df['Age']>60,['Name', 'Pclass', 'Survived']].head(3)
Out[42]:
Name Pclass Survived
33 Wheadon, Mr. Edward H 2 0
54 Ostby, Mr. Engelhart Cornelius 1 0
96 Goldschmidt, Mr. George B 1 0

여러 개의 복합 조건을 이용해서도 불린 인덱싱이 가능

  • and 조건.: &
  • or 조건 : |
  • Not 조건 : ~
In [43]:
titanic_df[ (titanic_df['Age']>60) & (titanic_df['Pclass']==1) & (titanic_df['Sex']=='female')]
Out[43]:
PassengerId Survived Pclass Name Sex Age SibSp Parch Ticket Fare Cabin Embarked
275 276 1 1 Andrews, Miss. Kornelia Theodosia female 63.0 1 0 13502 77.9583 D7 S
829 830 1 1 Stone, Mrs. George Nelson (Martha Evelyn) female 62.0 0 0 113572 80.0000 B28 NaN

개별 조건을 변수에 할당해서, 이 변수들을 결합해서 동일한 불린 인덱싱 수행 가능

In [44]:
cond1 = titanic_df['Age'] > 60
cond2 = titanic_df['Pclass'] == 1
cond3 = titanic_df['Sex'] == 'female'

titanic_df[cond1 & cond2 & cond3]
Out[44]:
PassengerId Survived Pclass Name Sex Age SibSp Parch Ticket Fare Cabin Embarked
275 276 1 1 Andrews, Miss. Kornelia Theodosia female 63.0 1 0 13502 77.9583 D7 S
829 830 1 1 Stone, Mrs. George Nelson (Martha Evelyn) female 62.0 0 0 113572 80.0000 B28 NaN

정렬, Aggregate 함수, GroupBy 적용

DataFrame, Series의 정렬 - sort_values()

  • 주요 파라미터 : by, ascending, inplace
    • by : 특정 컬럼을 입력하면 해당 컬럼 기준으로 정렬 수행
    • ascending : True -> 오름차순 / False -> 내림차순 (기본값은 True)
    • inplace : False -> 원본 데이터프레임은 유지하고, 정렬된 데이터를 반환 / True -> 원본 데이터 프레임을 정렬 (기본값은 False)
In [45]:
titanic_sorted = titanic_df.sort_values(by=['Name'])
titanic_sorted.head(3)
Out[45]:
PassengerId Survived Pclass Name Sex Age SibSp Parch Ticket Fare Cabin Embarked
845 846 0 3 Abbing, Mr. Anthony male 42.0 0 0 C.A. 5547 7.55 NaN S
746 747 0 3 Abbott, Mr. Rossmore Edward male 16.0 1 1 C.A. 2673 20.25 NaN S
279 280 1 3 Abbott, Mrs. Stanton (Rosa Hunt) female 35.0 1 1 C.A. 2673 20.25 NaN S
  • 여러 개의 컬럼으로 정렬하려면 리스트 형식으로 by에 컬럼명 입력
In [46]:
titanic_sorted = titanic_df.sort_values(by=['Pclass', 'Name'], ascending = False)
titanic_sorted.head(3)
Out[46]:
PassengerId Survived Pclass Name Sex Age SibSp Parch Ticket Fare Cabin Embarked
868 869 0 3 van Melkebeke, Mr. Philemon male NaN 0 0 345777 9.5 NaN S
153 154 0 3 van Billiard, Mr. Austin Blyler male 40.5 0 2 A/5. 851 14.5 NaN S
282 283 0 3 de Pelsmaeker, Mr. Alfons male 16.0 0 0 345778 9.5 NaN S

Aggregation 함수 적용 : min(), max(), sum(), count()

  • 데이터프레임에 바로 적용하면 모든 컬럼에 해당 aggregation을 적용
In [47]:
# count()는 null을 제외하고 count 함
titanic_df.count()
Out[47]:
PassengerId    891
Survived       891
Pclass         891
Name           891
Sex            891
Age            714
SibSp          891
Parch          891
Ticket         891
Fare           891
Cabin          204
Embarked       889
dtype: int64
In [48]:
titanic_df[['Age', 'Fare']].mean()
Out[48]:
Age     29.699118
Fare    32.204208
dtype: float64

groupby() 적용

  • groupby() 사용시 입력 파라미터 by에 컬럼을 입력하면 대상 컬럼으로 groupby 됨
  • 데이터프레임에 groupby 를 호출하면 DataFrameGroupBy 라는 또 다른 형태의 데이터프레임을 반환
In [49]:
titanic_groupby = titanic_df.groupby(by='Pclass')
titanic_groupby
Out[49]:
<pandas.core.groupby.generic.DataFrameGroupBy object at 0x11e225208>
  • SQL groupby와는 달리 groupby()를 호출해 반환된 결과에 aggregation 함수를 적용하면 groupby 대상컬럼을 제외한 모든 컬럼에 해당 aggregation 함수를 반환
In [50]:
titanic_groupby = titanic_df.groupby('Pclass').count()
titanic_groupby
Out[50]:
PassengerId Survived Name Sex Age SibSp Parch Ticket Fare Cabin Embarked
Pclass
1 216 216 216 216 186 216 216 216 216 176 214
2 184 184 184 184 173 184 184 184 184 16 184
3 491 491 491 491 355 491 491 491 491 12 491

=> SQL에서는 여러개의 컬럼에 aggregation 함수를 적용하려면 대상 컬럼을 모두 select에 나열 했었음

  Select count(PassengerId), count(Survived), count(Name) ..... 
  from titanic 
  group by Pclass
  • 특정컬럼에 대해서만 aggregation 함수를 적용하려면 groupby()로 반환된 DataFrameGroupBy() 객체에 해당컬럼을 지정하여 aggregation 함수 적용
In [51]:
titanic_groupby = titanic_df.groupby('Pclass')[['PassengerId', 'Survived']].count()
titanic_groupby
Out[51]:
PassengerId Survived
Pclass
1 216 216
2 184 184
3 491 491
  • 여러개의 aggregation 함수를 적용하기 위해서는 DataFrameGroupBy 객체에 agg 내에 인자로 입력해서 사용
In [52]:
titanic_df.groupby('Pclass')['Age'].agg(['max','min'])
Out[52]:
max min
Pclass
1 80.0 0.92
2 70.0 0.67
3 74.0 0.42
  • 여러 개의 컬럼에 서로 다른 aggregation 함수를 적용할 때 agg 인자에 딕셔너리를 활용
    => agg( {컬럼명: 'mean', '컬럼명2': 'max', '컬렴명3': min}) 
In [53]:
agg_format = { 'Age' : 'max', 'SibSp' : 'mean', 'Fare' : 'max'}
titanic_df.groupby('Pclass').agg(agg_format)
Out[53]:
Age SibSp Fare
Pclass
1 80.0 0.416667 512.3292
2 70.0 0.402174 73.5000
3 74.0 0.615071 69.5500

결손데이터 처리하기

  • 판다스에서의 NULL 값은 넘파이의 NaN으로 표시
  • 기본적으로 머신러닝 알고리즘은 NaN값을 처리하지 않으므로 이 값을 다른 값으로 대체해야 함
  • 또한, NaN값은 평균, 총합 등의 함수 연산에서 제외됨
  • isnull() 또는 isna() : NaN 값 여부를 확인
In [54]:
titanic_df.isnull().head()
Out[54]:
PassengerId Survived Pclass Name Sex Age SibSp Parch Ticket Fare Cabin Embarked
0 False False False False False False False False False False True False
1 False False False False False False False False False False False False
2 False False False False False False False False False False True False
3 False False False False False False False False False False False False
4 False False False False False False False False False False True False
  • isnull().sum() 을 통해 NaN 갯수를 셀 수 있음
In [55]:
titanic_df.isnull().sum()
Out[55]:
PassengerId      0
Survived         0
Pclass           0
Name             0
Sex              0
Age            177
SibSp            0
Parch            0
Ticket           0
Fare             0
Cabin          687
Embarked         2
dtype: int64
  • fillna() : 결손 데이터 대체하기
In [56]:
titanic_df['Cabin'] = titanic_df['Cabin'].fillna('C000')
titanic_df.head(3)
Out[56]:
PassengerId Survived Pclass Name Sex Age SibSp Parch Ticket Fare Cabin Embarked
0 1 0 3 Braund, Mr. Owen Harris male 22.0 1 0 A/5 21171 7.2500 C000 S
1 2 1 1 Cumings, Mrs. John Bradley (Florence Briggs Th... female 38.0 1 0 PC 17599 71.2833 C85 C
2 3 1 3 Heikkinen, Miss. Laina female 26.0 0 0 STON/O2. 3101282 7.9250 C000 S

fillna()를 이용해 반환 값을 다시 받거나, inplace=True 파라미터를 추가해야 실제 데이터 값이 변경됨에 유의

In [57]:
# Age컬럼의 NaN를 평균값, Embarked컬럼의 NaN 값을 S로 대체
titanic_df['Age'] = titanic_df['Age'].fillna(titanic_df['Age'].mean())
titanic_df['Embarked'] = titanic_df['Embarked'].fillna('S')
titanic_df.isnull().sum()
Out[57]:
PassengerId    0
Survived       0
Pclass         0
Name           0
Sex            0
Age            0
SibSp          0
Parch          0
Ticket         0
Fare           0
Cabin          0
Embarked       0
dtype: int64

apply lambda 식으로 데이터 가공

  • 복잡한 데이터 가공이 필요할 경우에 활용
  • lambda 식은 파이썬에서 함수 프로그래밍을 지원하기 위한 것
In [58]:
# 입력값의 제곱값을 구해서 반환하는 함수 설정
def get_square(a):
    return a**2

print('3의 제곱은: ', get_square(3))
3의 제곱은:  9

위의 함수는 def get_square(a): 와 같이 함수명과 입력인자를 먼저 선언하고 함수 내에서 입력 인자를 가공한 뒤, 결과 값을 return으로 반환

lambda는 함수의 선언과 함수 내의 처리를 한 줄의 식으로 쉽게 변환 하는 식

In [59]:
# 위의 함수를 lambda식으로 변환
lambda_square = lambda x : x**2
print('3의 제곱은: ', lambda_square(3))
3의 제곱은:  9

lambda x : x 2 에서 앞의 x는 입력인자, 뒤의 x2를 기반으로 한 계산식으로 호출시 이 결과가 반환

  • 여러 개의 값을 입력인자로 사용해야할 경우, map() 함수를 결합해서 사용
In [60]:
a = [1, 2, 3]
squares = map(lambda x : x**2, a)
list(squares)
Out[60]:
[1, 4, 9]
  • 판다스 데이터프레임의 lambda 식은 파이썬의 lambda를 그대로 적용한 것으로 apply에 lambda를 적용하는 식으로 사용
In [61]:
# Name컬럼의 문자열 개수를 별도의 컬럼인 Name_len 에 생성
titanic_df['Name_len']  = titanic_df['Name'].apply(lambda x : len(x))
titanic_df[['Name', 'Name_len']].head(3)
Out[61]:
Name Name_len
0 Braund, Mr. Owen Harris 23
1 Cumings, Mrs. John Bradley (Florence Briggs Th... 51
2 Heikkinen, Miss. Laina 22
In [62]:
# if else 절을 활용하여 나이가 15세 이하면 Child 그렇지 않으면 Adult로 구분하는 새로운 컬럼 Child_Adult를 추가
titanic_df['Child_Adult'] = titanic_df['Age'].apply(lambda x : 'Child' if x < 15 else 'Adult')
titanic_df[['Age', 'Child_Adult']].head(8)
Out[62]:
Age Child_Adult
0 22.000000 Adult
1 38.000000 Adult
2 26.000000 Adult
3 35.000000 Adult
4 35.000000 Adult
5 29.699118 Adult
6 54.000000 Adult
7 2.000000 Child
  • lambda식에서 if else를 지원할 때, if절의 경우 if 식보다 반환 값이 먼저 기술됨에 주의, else의 경우에는 else 뒤에 반환값이 오면 됨

    lambda 식 ' : ' 기호 오른편에 반환값이 있어야 하기 때문
    따라서 lambda x : if x <=15 'Child' else 'Adult' 가 아니라 lambda x : 'Child' if x<=15 else 'Adult' 가 됨

  • lambda 는 if, else만 지원하고 else if는 지원하지 않음, else if를 이용하기 위해서는 else 절을 () 로 내포해서 () 안에서 다시 if else를 적용

    • else() 안에 다시 if else를 사용할 때에도 if 앞에 반환값을 사용함
    • 하지만 이 방법은 else if 조건이 너무 많을 때는 코드가 복잡해짐
In [63]:
### 15세 이하는 Child, 15~60세 사이는 Adult, 61세 이상은 Elderly로 분류하는 'Age_Cat' 컬럼을 생성
titanic_df['Age_Cat'] = titanic_df['Age'].apply(lambda x : 'Child' if x<=15 else('Adult' if x<=60 else 'Elderly'))
titanic_df['Age_Cat'].value_counts()
Out[63]:
Adult      786
Child       83
Elderly     22
Name: Age_Cat, dtype: int64
In [64]:
### 나이에 따라 세분화된 분류를 수행하는 함수 생성
def get_category(age):
    cat = ''
    if age <= 5 : 
        cat = 'Baby'
    elif age<= 12 :
        cat = 'Child'
    elif age<=18 :
        cat = 'Teenager'
    elif age <=25 :
        cat = 'Student'
    elif age <=35 :
        cat = 'Young Adult'
    elif age <=60 :
        cat = 'Elderly'
    return cat

# lambda 식에 위에서 생성한 get_category( ) 함수를 반환값으로 지정
# get_category(x)는 'Age' 컬럼 값을 입력값으로 받아서 해당하는 cat 반환
titanic_df['Age_cat'] = titanic_df['Age'].apply(lambda x : get_category(x))
titanic_df[['Age', 'Age_cat']].head()
Out[64]:
Age Age_cat
0 22.0 Student
1 38.0 Elderly
2 26.0 Young Adult
3 35.0 Young Adult
4 35.0 Young Adult
In [65]:
titanic_df['Age_cat'] = titanic_df['Age'].apply(get_category)
titanic_df[['Age', 'Age_cat']].head()
Out[65]:
Age Age_cat
0 22.0 Student
1 38.0 Elderly
2 26.0 Young Adult
3 35.0 Young Adult
4 35.0 Young Adult
1-1. NumPy 개요
In [1]:
from IPython.core.display import display, HTML
display(HTML("<style> .container{width:90% !important;}</style>"))

Numpy(넘파이)

: 파이썬에서 선형대수 기반의 프로그램을 쉽게 만들 수 있도록 지원하는 패키지입니다.
→ 많은 머신러닝 알고리즘이 넘파이 기반으로 작성되어 있고, 데이터 입출력도 넘파이 배열 타입을 사용하기 때문에 중요한 패키지입니다.

1. ndarray 개요

넘파이의 기반 데이터 타입으로 ndarray를 이용해서 넘파이에서 다차원 베열을 쉽게 생성하고 다양한 연산을 수행할 수 있습니다.

In [2]:
# 넘파이 모듈 불러오기, 관례적으로 np라는 축약어로 넘파이모듈을 표현해줍니다.
import numpy as np

array( ) 함수 : 파이썬의 리스트와 같은 다양한 인자를 입력받아 ndarray로 변환하는 기능을 수행합니다.
→ 생성된 ndarray 배열의 shape변수는 ndarray의 행과 열의 수를 튜플 형태로 가지고 있고, 이를 통해 ndarray 배열의 차원을 알 수 있습니다.

In [3]:
array1 = np.array([1, 2, 3])  ## 1차원 array
print('array1 type: ', type(array1))
print('array1의 array 형태', array1.shape)

array2 = np.array([[1,2,3], 
                            [2,3,4]]) ## 2차원 array
print('\narray2 type:', type(array2))
print('array2의 array 형태', array2.shape)

array3 = np.array([[1,2,3]]) ## array1과 동일하지만 이 경우에는 명확히 열과 행을 가진 2차원 array
print('\narray3 type:', type(array3))
print('array3의 array 형태', array3.shape)
array1 type:  <class 'numpy.ndarray'>
array1의 array 형태 (3,)

array2 type: <class 'numpy.ndarray'>
array2의 array 형태 (2, 3)

array3 type: <class 'numpy.ndarray'>
array3의 array 형태 (1, 3)


위의 예제에서 array1과 array3은 명백히 다른 차원을 가지고 있습니다
: [ ](대괄호를 한 번만 사용하면) : 1차원 , [[ ]](대괄호를 두 번 사용하면) : 2차원
→ 알고리즘 데이터 입출력, 변환시 명확히 1차원 또는 다차원 데이터를 요구하는 경우가 빈번하기 때문에 이 차이를 인지하는 것은 매우 중요합니다.

In [4]:
# 각 array의 차원을 ndarray.ndim을 이용해 확인
print('array1: {:0}차원'.format(array1.ndim))
print('array2: {:0}차원'.format(array2.ndim))
print('array3: {:0}차원'.format(array3.ndim))
array1: 1차원
array2: 2차원
array3: 2차원

2. ndarray의 데이터 타입

ndarray 내의 데이터 값은 숫자, 문자열, 불 값 모두 가능합니다.
→ 단, 하나의 array 내의 데이터 타입은 같은 타입만 가능합니다. 심지어 숫자형인 int 와 float도 함께 있을 수 없습니다.

In [5]:
list1 = [1, 2, 3]
print(type(list1))
print('-----------------')
array1 = np.array(list1)
print(type(array1))
print(array1, array1.dtype)
<class 'list'>
-----------------
<class 'numpy.ndarray'>
[1 2 3] int64


서로 다른 데이터 유형이 섞여있는 리스트를 ndarray로 변경하려고 하면 데이터 크기가 더 큰 타입으로 형 변환을 일괄 적용합니다.
→ str > float > int

In [6]:
### int와 str 형이 섞여 있는 리스트
list2 = [1, 2, 'test']
array2 = np.array(list2)
print(array2, array2.dtype)
print('유니코드 문자열값으로 데이터가 변환 되었음')
['1' '2' 'test'] <U21
유니코드 문자열값으로 데이터가 변환 되었음
In [7]:
## int와 float형이 섞여 있는 리스트
list3 = [1, 2, 3.0]
array3 = np.array(list3)
print(array3, array3.dtype)
print('float형으로 데이터가 변환되었음')
[1. 2. 3.] float64
float형으로 데이터가 변환되었음


ndarray 내의 데이터 타입 변경도 astype() 를 이용하면 가능
→ 이를 이용하여 메모리를 절약할 수 있습니다

가령, int형만으로도 충분한 float타입의 데이터라면 int형으로 바꿔서 메모리를 절약할 수 있습니다.

In [8]:
array_int = np.array([1, 2, 3])
array_float = array_int.astype('float64')
print(array_float, array_float.dtype)
[1. 2. 3.] float64
In [9]:
array_int1 = array_float.astype('int32')
print(array_int1, array_int1.dtype)
[1 2 3] int32
In [10]:
array_float1 = np.array([1.1, 2.1, 3.1])
array_int2 = array_float1.astype('int32')
print(array_int2, array_int2.dtype)
print('int형으로 변환되면서 소수점이 사라짐')
[1 2 3] int32
int형으로 변환되면서 소수점이 사라짐

3. ndarray 편리하게 생성하기

특정 크기, 차원을 가진 ndarray를 연속값이나 0, 1로 초기화해서 쉽게 생성해야할 경우,
또는 테스트용 데이터를 만들거나 대규모 데이터를 일괄적으로 초기화해야할 때 arange( ), zeros( ), ones( ) 함수를 사용할 수 있습니다.

arange( ): 파이썬의 range와 비슷하게 0부터 '인자값-1'의 값을 순차적으로 ndarray의 값으로 변환

In [11]:
sequence_array = np.arange(10)
print(sequence_array)
print(sequence_array.dtype)
print(sequence_array.shape)
[0 1 2 3 4 5 6 7 8 9]
int64
(10,)
In [12]:
sequence_array2 = np.arange(3, 10) # 시작값을 설정할 수 도 있음
print(sequence_array2)
[3 4 5 6 7 8 9]

zeros( ) : 튜플형태의 shape 값을 입력하면, 모든 값을 0으로 채운 ndarray를 반환
ones( ) : 튜플형태의 shape 값을 입력하면, 모든 값을 1으로 채운 ndarray를 반환
→ 함수 인자로 dtpye을 설정하지 않으면 default로 float64형의 데이터로 ndarray 생성해줍니다.

In [13]:
zero_array = np.zeros((3, 2), dtype='int32')
print(zero_array)
print(zero_array.dtype)
print(zero_array.shape)
[[0 0]
 [0 0]
 [0 0]]
int32
(3, 2)
In [14]:
one_array = np.ones((2,3), dtype='int32')
print(one_array)
print(one_array.dtype)
print(one_array.shape)
[[1 1 1]
 [1 1 1]]
int32
(2, 3)

4. ndarray의 차원과 크기를 변경하는 reshape( )

In [15]:
array1 = np.arange(10)
print('array1: \n', array1)
array1: 
 [0 1 2 3 4 5 6 7 8 9]
In [16]:
array2 = array1.reshape(2, 5)
print('array2: \n', array2)
array2: 
 [[0 1 2 3 4]
 [5 6 7 8 9]]
In [17]:
array3 = array1.reshape(5, 2)
print('array3: \n', array3)
array3: 
 [[0 1]
 [2 3]
 [4 5]
 [6 7]
 [8 9]]

reshape() 는 지정된 사이즈로 변경이 불가능하면 오류를 발생시킵니다.

In [18]:
array1.reshape(4,3)
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-18-a40469ec5825> in <module>
----> 1 array1.reshape(4,3)

ValueError: cannot reshape array of size 10 into shape (4,3)

reshape( ) 의 인자를 -1로 적용하면, 원래 ndarray와 호환되는 새로운 ndarray로 변환해줍니다

In [19]:
array1 = np.arange(10)
print(array1)
print('--------------------')
array2 = array1.reshape(-1, 5)
print(array2, '  -->   shape : ', array2.shape)
print('--------------------')
array3 = array1.reshape(5, -1)
print(array3, '  -->  shape: ', array3.shape)
[0 1 2 3 4 5 6 7 8 9]
--------------------
[[0 1 2 3 4]
 [5 6 7 8 9]]   -->   shape :  (2, 5)
--------------------
[[0 1]
 [2 3]
 [4 5]
 [6 7]
 [8 9]]   -->  shape:  (5, 2)


-1을 인자로 사용하더라도, 호환될 수 없는 형태로는 변환될 수 없습니다.

In [20]:
array4 = array1.reshape(-1, 4)
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-20-7e1461ea4d71> in <module>
----> 1 array4 = array1.reshape(-1, 4)

ValueError: cannot reshape array of size 10 into shape (4)

reshape(-1,1) : 원본 ndarray가 어떤 형태라도 2차원이고, 여러개의 로우, 1개의 컬럼을 가진 ndarray로 변환됩니다.

In [21]:
array1 = np.arange(8)
print('1차원 array: \n', array1)

# 1차원 ndarray를 3차원으로 변환
array3d = array1.reshape((2,2,2))
print('\n3차원 array: \n', array3d)
print('\nlist로 변환된 array3d: \n', array3d.tolist()) # tolist() : list 형태로 변환


# 3차원의 ndarray를 2차원으로 변환
array5 = array3d.reshape(-1, 1)
print('\narray5: \n', array5)
print('array5 shape: ', array5.shape)

# 1차원의 ndarray를 2차원으로 변환
array6 = array1.reshape(-1,1)
print('\narray6: \n', array6)
1차원 array: 
 [0 1 2 3 4 5 6 7]

3차원 array: 
 [[[0 1]
  [2 3]]

 [[4 5]
  [6 7]]]

list로 변환된 array3d: 
 [[[0, 1], [2, 3]], [[4, 5], [6, 7]]]

array5: 
 [[0]
 [1]
 [2]
 [3]
 [4]
 [5]
 [6]
 [7]]
array5 shape:  (8, 1)

array6: 
 [[0]
 [1]
 [2]
 [3]
 [4]
 [5]
 [6]
 [7]]

4. 넘파이의 ndarray의 데이터세트 선택하기 - 인덱싱(indexing)

(1) 특정 데이터만 추출 : 원하는 위치의 인덱스 값을 지정하면 해당 위치의 데이터가 반환
(2) 슬라이싱(slicing) : 연속된 인덱스 상의 ndarray를 추출하는 방식
(3) 팬시 인덱싱(fancy indexing) : 일정한 인덱싱 집합을 리스트 또는 ndarray 형태로 지정해 해당 위치에 있는 데이터의 ndarray를 반환
(4) 불린 인덱싱(boolean indexing) : True/False 값 인덱싱 집합을 기반으로 True에 해당하는 인덱스 위치에 있는 데이터의 ndarray를 반환

(1) 특정 데이터만 추출

: ndarray 객체에 해당하는 위치의 인덱스 값을 [] 안에 입력

In [22]:
# 1부터 9까지의 1차원 ndarray 생성
array1 = np.arange(start=1, stop=10)
array1
Out[22]:
array([1, 2, 3, 4, 5, 6, 7, 8, 9])
In [23]:
# index는 0부터 시작하므로 array1[2]는 3번째 index 위치의 데이터 값을 의미
value = array1[2]
print('value: ', value)
print(type(value))
value:  3
<class 'numpy.int64'>
In [24]:
# 인덱스 -1은 맨 뒤의 데이터 값, -2는 맨뒤에서 두번째에 있는 데이터 값
print('맨 뒤의 값: ', array1[-1])
print('맨 뒤에서 두번째 값: ', array1[-2])
맨 뒤의 값:  9
맨 뒤에서 두번째 값:  8

단일 인덱스를 이용해 ndarray 내의 데이터 값을 수정할 수 있습니다.

In [25]:
array1[0] = 9
array1[8] = 0
array1
Out[25]:
array([9, 2, 3, 4, 5, 6, 7, 8, 0])

2차원 ndarray의 경우에는 (row 위치, column 위치)로 로우와 컬럼의 위치 인덱스를 통해 접근

In [26]:
array1d = np.arange(start=1, stop = 10)
array2d = array1d.reshape(3, 3)
print(array2d)
[[1 2 3]
 [4 5 6]
 [7 8 9]]
In [27]:
print('(row=0, col =0) index 가리키는 값: ', array2d[0,0])
print('(row=0, col=1) index 가리키는 값: ', array2d[0,1])
print('(row=1, col=0) index 가리키는 값: ', array2d[1,0])
print('(row=2, col=2) index 가리키는 값', array2d[2,2])
(row=0, col =0) index 가리키는 값:  1
(row=0, col=1) index 가리키는 값:  2
(row=1, col=0) index 가리키는 값:  4
(row=2, col=2) index 가리키는 값 9

(2) 슬라이싱

' : ' 기호를 이용

  • ' : ' 기호 앞에 시작 인덱스를 생략하면 자동으로 맨 처음인 0인 인덱스로 간주
  • ' : ' 기호 뒤에 종료 인덱스를 생략하면 자동으로 맨 마지막 인덱스로 간주
  • ' : ' 기호 앞뒤에 시작/종료 인덱스를 생략하면 자동으로 맨 처음/맨 마지막 인덱스로 간주 (결국, 전체 선택)
In [28]:
array1 = np.arange(1, 10)
array3 = array1[0:3]
print(array3)
print(type(array3))
[1 2 3]
<class 'numpy.ndarray'>
In [29]:
array4 = array1[:3]
print(array4)

array5 = array1[3:]
print(array5)

array6 = array1[:]
print(array6)
[1 2 3]
[4 5 6 7 8 9]
[1 2 3 4 5 6 7 8 9]
In [30]:
# 2차원 ndarray에서 슬라이싱으로 데이터 접근하기
array2d = array1.reshape(3, 3)
print('array2d: \n', array2d)

print('array2d[0:2, 0:2] \n', array2d[0:2, 0:2])
print('array2d[1:3, 0:3] \n', array2d[1:3, 0:3])
print('array2d[1:3, :] \n', array2d[1:3, :])
print('array2d[:,:] \n', array2d[:,:])
print('array2d[:2, 1:] \n', array2d[:2, 1:])
print('array2d[:2, 0] \n', array2d[:2,0])
array2d: 
 [[1 2 3]
 [4 5 6]
 [7 8 9]]
array2d[0:2, 0:2] 
 [[1 2]
 [4 5]]
array2d[1:3, 0:3] 
 [[4 5 6]
 [7 8 9]]
array2d[1:3, :] 
 [[4 5 6]
 [7 8 9]]
array2d[:,:] 
 [[1 2 3]
 [4 5 6]
 [7 8 9]]
array2d[:2, 1:] 
 [[2 3]
 [5 6]]
array2d[:2, 0] 
 [1 4]
In [31]:
# 2차원 ndarray에서 뒤에 오는 인덱스를 없애면 1차원 인덱스를 반환
print(array2d[0])
print(array2d[1])
print('array2d[0] shape: ', array2d[0].shape, 'array2d[1] shape: ', array2d[1].shape)
[1 2 3]
[4 5 6]
array2d[0] shape:  (3,) array2d[1] shape:  (3,)

(3) 팬시 인덱싱

리스트나 ndarray로 인덱스 집합을 지정하면 해당 위치의 인덱스에 해당하는 ndarray를 반환하는 인덱싱 방식

In [32]:
array1d = np.arange(1, 10)
array2d = array1d.reshape(3,3)

array3 = array2d[[0, 1], 2]
print('array2d[[0,1], 2] ==> ', array3.tolist())

array4 = array2d[[0, 1], 0:2]
print('array2d[[0,1], 0:2] ==>', array4.tolist())

array5 = array2d[[0,1]]
print('array2d[[0,1]] ==>', array5.tolist())
array2d[[0,1], 2] ==>  [3, 6]
array2d[[0,1], 0:2] ==> [[1, 2], [4, 5]]
array2d[[0,1]] ==> [[1, 2, 3], [4, 5, 6]]

(4) 불린 인덱싱

: 조건 필터링과 검색을 동시에 할 수 있기 때문에, 매우 자주 사용되는 인덱싱 방식

ndarray의 인덱스를 지정하는 [ ] 내에 조건문을 그대로 기재

In [33]:
array1d = np.arange(1, 10)
# array1d > 5 조화결과
print(array1d > 5)
# [ ] 안에 array1d > 5 Boolean indexing을 적용
array3 = array1d[array1d > 5]
print('array1d > 5 불린 인덱싱 결과 값: ', array3)
[False False False False False  True  True  True  True]
array1d > 5 불린 인덱싱 결과 값:  [6 7 8 9]
In [34]:
# array1d > 5와 동일한 ndarray를 array1d[ ]안에 입력하면 동일한 데이터 세트가 반환
boolean_indices = np.array([False, False, False, False, False, True, True, True, True])
array3 = array1d[boolean_indices]
print(array3)
[6 7 8 9]

5. 행렬의 정렬 -sort( )와 argsort( )

행렬 정렬

np.sort( ) : 넘파이에서 sort()를 호출 → 원행렬은 그대로 유지한 채, 정렬된 행렬을 반환합니다.
ndarray.sort( ) : 원 행렬 자체를 정렬한 형태로 변환하며 별도로 반환하는 값은 없습니다.

In [35]:
org_array = np.array( [3, 1, 9, 5])
print('원본 행렬: ', org_array)

# np.sort()로 정렬
sort_array1 = np.sort(org_array)
print('np.sort() 호출 후 반환된 정렬 행렬: ', sort_array1)
print('np.sort() 호출 후 원본 행렬: ', org_array)

#ndarray.sort()로 정렬
sort_array2 = org_array.sort()
print('org_array.sort() 호출 후 반환된 행렬: ', sort_array2)
print('org_array.sort() 호출 후 원본 행렬: ', org_array)
원본 행렬:  [3 1 9 5]
np.sort() 호출 후 반환된 정렬 행렬:  [1 3 5 9]
np.sort() 호출 후 원본 행렬:  [3 1 9 5]
org_array.sort() 호출 후 반환된 행렬:  None
org_array.sort() 호출 후 원본 행렬:  [1 3 5 9]


기본적으로 오름차순으로 행렬 내 원소를 정렬합니다.
→ 내림차순으로 정렬하기 위해서는 [::-1] 을 적용하면 됩니다.

In [36]:
sort_array1_desc = np.sort(org_array)[::-1]
print('내림차순으로 정렬: ', sort_array1_desc)
내림차순으로 정렬:  [9 5 3 1]

행렬이 2차원 이상일 경우 axis 축 값 설정을 통해 로우 방향, 또는 컬럼 방향으로 정렬을 수행할 수 있습니다.

In [37]:
array2d = np.array([[8, 12], 
                              [7, 1]])

sort_array2d_axis0 = np.sort(array2d, axis = 0)
print('로우 방향으로 정렬 : \n', sort_array2d_axis0)

sort_array2d_axis1 = np.sort(array2d, axis = 1)
print('컬럼 방향으로 정렬 : \n', sort_array2d_axis1)
로우 방향으로 정렬 : 
 [[ 7  1]
 [ 8 12]]
컬럼 방향으로 정렬 : 
 [[ 8 12]
 [ 1  7]]

정렬된 행렬의 인덱스 반환하기

np.argsort( ) : 원본 행렬이 정렬되었을 때, 기존 원본 행렬의 원소에 대한 인덱스가 필요할 때 사용하며 원본 행렬의 인덱스를 ndarray 형태로 반환합니다.

In [38]:
org_array = np.array( [ 3, 1, 9, 5])
sort_indices = np.argsort(org_array)
print(type(sort_indices))
print('행렬 정렬 시 원본 행렬의 인덱스: ', sort_indices)
<class 'numpy.ndarray'>
행렬 정렬 시 원본 행렬의 인덱스:  [1 0 3 2]


내림차순으로 정렬 시 [::-1] 을 적용합니다.

In [39]:
sort_indices_desc = np.argsort(org_array)[::-1]
print('행렬 내림차순 정렬 시 원본 행렬의 인덱스: ', sort_indices_desc)
행렬 내림차순 정렬 시 원본 행렬의 인덱스:  [2 3 0 1]

6. 선형대수 연산 - 행렬 내적과 전치행렬 구하기

행렬 내적(행렬곱)

행렬 A와 행렬 B의 내적은 np.dot( ) 을 이용해 계산합니다.

In [40]:
A = np.array([[1, 2, 3, ], 
                        [4, 5, 6]])
B = np.array([[7,8],
                     [9,10],
                     [11,12]])

dot_product = np.dot(A, B)
print('행렬 A와 B의 내적: \n', dot_product)
행렬 A와 B의 내적: 
 [[ 58  64]
 [139 154]]

전치 행렬

원행렬의 행과 열 위치를 바꾼 행렬로 np.transpose( )를 이용합니다.

In [41]:
A = np.array([[1, 2],
                     [3, 4]])

transpose_mat = np.transpose(A)
print('A의 전치행렬 : \n', transpose_mat)
A의 전치행렬 : 
 [[1 3]
 [2 4]]

+ Recent posts