파이썬 머신러닝 완벽 가이드: 다양한 캐글 예제와 함께 기초 알고리즘부터 최신 기법까지 배우는/권철민 지음을 참고하여 공부하고 정리하는 스터디 포스팅입니다.
데이터프레임(DataFrame) 다루기 ◑_◐¶
데이터프레임을 공부하려고 정리하면서 생각보다 내용이 많아서 따로 정리를 하기로 하였습니다.
1. DataFrame의 변환¶
지난 포스팅에서는 csv파일을 DataFrame으로 생성하는 방법을 배웠습니다. 기본적으로 데이터프레임은 파이썬의 리스트, 딕셔너리, 넘파이 ndarray 등 다양한 데이터로부터 생성이 될수 있는데요 이번시간에는 이 내용에 대해서 정리해 보도록 하겠습니다.
1-1 ndarray, 리스트, 딕셔너리 → DataFrame으로 변환¶
- DataFrame은 행과 열을 가지는 2차원 데이터입니다. 그렇기 때문에 2차원 이하의 데이터들만 DataFrame으로 변환할 수 있습니다. 먼저 1차원 형태인 리스트와 ndarray부터 DataFrame으로 변환을 해보겠습니다.
import numpy as np
import pandas as pd
col_name1=['col1']
list1=[1,2,3]
array1=np.array(list1)
print('array1 shape:', array1.shape)
# 리스트를 사용해서 DataFrame 만들기
df_list1=pd.DataFrame(list1,columns=col_name1)
print('1차원 리스트로 만든 DataFrame:\n',df_list1)
# ndarrary를 사용해서 DaraFrame 만들기
df_array1=pd.DataFrame(array1,columns=col_name1)
print('1차원 ndarray로 만든 DataFrame:\n',df_array1)
다음은 2차원 형태의 데이터를 사용해서 DataFrame을 만들어 보겠습니다.
col_name2=['col1','col2','col3']
# 2X3 형태의 리스트와 nadarray를 생성한 후 DataFrame으로 변환하기
list2=[[1,2,3],[4,5,6]]
array2=np.array(list2)
df_list2=pd.DataFrame(list2,columns=col_name2)
print('2차원 리스트로 만든 DataFrame:\n',df_list2)
df_array2=pd.DataFrame(array2,columns=col_name2)
print('2차원 ndarrary로 만든 DataFrame:\n',df_array2)
다음은 딕셔너리를 DataFrame으로 만들어 보겠습니다.
# Key는 문자열 컬럼명, Value는 리스트 형(or ndarray) 컬럼 데이터로 매핑
dict={'col1':[1,10],'col2':[2,20],'col3':[3,30]}
df_dict=pd.DataFrame(dict)
print('딕셔너리로 만든 DataFrame:\n',df_dict)
1-2 DataFrame → nadarray, 리스트, 딕셔너리로 변환¶
# DataFrame을 ndarray로 변환
array3=df_dict.values
print(array3)
# DataFrame을 리스트로 변환
list3=df_dict.values.tolist()
print(list3)
dict3=df_dict.to_dict('list')
print(dict3)
2. DataFrame의 컬럼 데이터 생성 및 수정¶
- 데이터프레임의 생성과 수정은 []연산자를 사용해서 할 수 있습니다. 예제 데이터는 지난 포스팅에서 사용했던 titanic데이터를 사용하겠습니다.
import pandas as pd
titanic_df=pd.read_csv("./train.csv")
titanic_df.head(3)
새로운 컬럼 Age_0을 추가하고 0 값을 넣어주겠습니다.
titanic_df['Age_0']=0
titanic_df.head(3)
위 결과를 봤을 때 기존 데이터프레임에서 0이 할당된 것을 확인할 수 있습니다.
이번에는 기존 컬럼을 활용해서 새로운 컬럼을 만들어 보도록 하겠습니다.
titanic_df['Age_by_10']=titanic_df['Age']*10
titanic_df['Family_NO']=titanic_df['SibSp']+titanic_df['Parch']+1
titanic_df.head(3)
새로운 컬럼 Age_by_10과 Family_NO가 생성된 것을 확인할 수 있습니다.
DataFrame 내의 기존 컬럼 값을 일괄적으로 업데이트할 수도 있습니다.
titanic_df['Age_by_10']=titanic_df['Age_by_10']+100
titanic_df.head(3)
3. DataFrame 데이터 삭제¶
- DataFrame에서 삭제는 drop() 함수를 사용합니다. drop()함수는 사용시 혼동이 올 수 있어 주의가 필요합니다. drop() 함수의 원형은 다음과 같습니다.
- df.drop(labels=None, axis=0, index=None, columns=None, level=None, inplace=False, errors='raise')
여기서 가장 중요한 파라미터는 labels, axis, inplace 입니다.
- axis 값에 따라서 특정 컬럼 또는 행을 drop합니다.(axis 0은 로우, axis 1은 컬럼 방햡입니다.)
- labels에 원하는 컬럼 명과 axis=1을 입력하면 지정된 컬럼을 drop하게 됩니다.
예제를 통해서 실습을 해보겠습니다.
titanic_drop_df=titanic_df.drop('Age_0',axis=1)
titanic_drop_df.head()
titanic_drop_df 결과를 보면 'Age_10'컬럼이 삭제된것을 알 수 있습니다. 다음은 inplace 파라미터에 대해서 정리해보겠습니다. 먼저 원본 데이터를 다시 살펴보겠습니다.
titanic_df.head(3)
결과를 보니 'Age_0'이 여전히 존재합니다. 그 이유는 앞에서 설명하였듯이 inplace의 원형은 False이기 때문입니다. 즉 Inplace=False이면 자기 자신의 DataFrame의 데이터는 삭제하지 않고, 삭제된 결과 DataFrame을 반환합니다. 그래서 titanic_df.drop('Age_10',axis=1)의 결과로 반환된 titanic_drop_df의 객체 변수만 'Age_0'컬럼이 삭제된 것입니다.
자신의 DataFrame을 삭제하고 싶으면 inplace=True로 설정하면 됩니다. 만약 여러 개의 컬럼을 삭제하고 싶으면 리스트 형태로 삭제하고 싶은 컬럼 명을 입력하면 됩니다.
# 'Age_10','Age_by_10','Family_NO' 삭제
drop_reusult=titanic_df.drop(['Age_0','Age_by_10','Family_NO'],axis=1,inplace=True)
titanic_df.head(3)
4. 데이터 셀력션 및 필터링¶
- 판다스의 데이터 셀력션과 필터링은 넘파이와 비슷한 부분이 있습니다. 그렇기 때문에 사용할때 헷갈리기 쉬워 주의하여야 합니다.
- 넘파이: '[]'연산자 내 단일 값 추출, 슬라이싱, 팬시 인덱싱, 불린 인덱싱
- 판다스: ix[], iloc[], loc[] 연산자 사용
4-1 DataFrame의 [] 연산자¶
- 넘파이에서 []연산자는 행위치, 열위치, 슬라이싱 범위 등을 지정해서 데이터를 가져올 수 있었습니다. 그런데 DataFrame 뒤에 있는 [] 안에는 컬럼 명 문자(또는 컬럼 명의 리스트 객체), 또는 인덱스로 변환이 가능한 표현식입니다. 쉽게 말하자면 '컬럼 지정 연산자'로 표현을 할 수 있겠습니다.
# 단일 컬럼 추출
titanic_df['Pclass'].head(2)
# 여러개의 컬럼 추출
titanic_df[['Pclass','Survived']].head(2)
titanic_df[0]
다음과 같이 숫자 값을 입력할 경우 오류가 발생합니다. 하지만 판다스의 인덱스 형태로 변환 가능한 표현식은 []안에 들어올 수 있습니다. 그 예제는 다음과 같습니다.
titanic_df[0:2]
# 불린 인덱싱도 가능
titanic_df[titanic_df['Pclass']==3].head(2)
DataFrame의 []연산자를 정리해 보겠습니다.
- DataFrame 뒤의 []연산자는 넘파이의[]나 Series의 []와 다릅니다.
- DataFrame 뒤의 [] 내 입력 값은 컬렴명(또는 컬럼의 리스트)을 지정해서 컬럼 지정 연산에 사용하거나 불린 인덱스 용도로만 사용을 하여야 합니다.
- DataFrame[0:2]와 같은 슬라이싱 연산으로 데이터 추출은 사용하지 않는 것이 좋습니다.
4-2 DataFrame의 iloc[] 연산자¶
- iloc[]는 위치 기반 인덱싱으로 행과 열 값으로 integer 또는 integer형의 슬라이싱, 팬시 리스트 값을 입력 해줘야 합니다.
data = {
"국어": [90, 60, 75, 20,70],
"영어": [80, 20, 95, 60,55],
"수학": [30, 60, 90, 80,75],
}
columns = ["국어", "영어", "수학"]
index = ["민지", "종원", "시연", "춘석","윤주"]
df = pd.DataFrame(data, index=index, columns=columns)
df
# 첫 번째 행, 열 추출
df.iloc[0,0]
df.iloc[0,'국어']
다음과 같이 위치 인덱싱이 아닌 명칭을 입력하게 되면 오류가 발생합니다.
iloc[]는 슬라이싱과 팬시 인덱싱은 제공하지만 불린 인덱싱은 제공하지 않습니다.
4-3 DataFrame loc[] 연산자¶
- loc[]는 명칭 기반 인덱싱으로 행 위치에는 index 값을, 열 위치에는 컬럼 명을 입력해 줍니다.
df
# 인덱스 값이 종원인 행의 컬럼 명이 국어인 데이터를 추출
df.loc['종원','국어']
index가 숫자 형일 수도 있기 때문에 명칭 기반이라고 무조건 문자열을 입력하다는 생각을 가져서는 안됩니다.
loc[]에 슬라이깅 기호':'를 적용할 때 유의할 점이 한가지 있습니다.
일반적으로 슬라이싱을 '시작 값: 종류 값'으로 지정을 하면 시작 값~ 종료 값-1까지의 범위를 의미합니다. 하지만 loc[]에 슬라이싱 기호를 적용하면 종료 값까지 포함되서 출력이 됩니다. 그 이유는 명칭은 숫자 형이 아닐 수 있으므로 -1을 할 수가 없기 때문입니다. 예제로 그 차이를 한번 살펴보겠습니다.
# 위치 기반 iloc[] 슬라이싱
df.iloc[0:1,0]
# 명칭 기반 loc[] 슬라이싱
df.loc['민지':'종원','국어']
iloc는 한개의 행 loc는 2개의 행이 반환된 것을 알 수 있습니다.
4-4 iloc와 loc 정리¶
- iloc는 위치 기반 인덱싱만 가능. 즉 행과 열 위치 값으로 정수형 값을 지정해 원하는 데이터를 출력합니다.
- loc는 명칭 기반 인덱싱만 가능. 즉 행 위치에 DataFrame 인덱스가 오고 열 위치에는 컬럼 명을 지정해 원하는 데이터를 출력합니다.
- 명칭 기반 인덱싱에서는 슬라이싱을 '시작점:종료점'으로 지정했을 때 시작점에서 종료점을 포함한 위치에 있는 데이터를 출력합니다.
4-5 불린 인덱싱¶
- 타이타닉 데이터를 사용해서 예제를 연습해보겠습니다.
# 나이(Age)가 60세 이상인 데이터 추출
titanic_boolean=titanic_df[titanic_df['Age']>60]
titanic_boolean.head(3)
# 나이(Age)가 60세 이상인 승객의 나이와 이름만 추출
titanic_df[titanic_df['Age']>60][['Name','Age']].head(3)
다음은 loc[]를 사용해서 출력해보겠습니다.
# 나이(Age)가 60세 이상인 승객의 나이와 이름만 추출
titanic_df.loc[titanic_df['Age']>60,['Name','Age']].head(3)
# 60세 이상이고, 선실 등급이 1등급이며, 성별이 여성인 승객 추출
titanic_df[(titanic_df['Age']>60) & (titanic_df['Pclass']==1) & (titanic_df['Sex']=='female')]
- and 조건 : &
- or 조건 : |
- Not 조건: ~
5. 정렬, Aggregation, GroupBy¶
5-1 DataFrame, Series 정렬하기 - sort_values()¶
- DataFrame과 Series를 정렬하려면 sort_values()함수를 사용합니다. sort_values()의 주요 입력 파라미터는 by, ascending, inplace입니다.
- by: 특정 컬럼 입력
- ascending: True=오름차순 정렬, False=내림차순 정렬(기본값은 True)
- inplace: False=sort_values()를 호출한 DataFrame은 그대로 유지하며 정렬된 DataFrame을 결과로 출력/ True=호출한 DataFrame의 정렬 결과를 그대로 적용(기본값은 False)
titanic_sorted=titanic_df.sort_values(by=['Name'])
titanic_sorted.head(3)
# 여러개의 컬럼 정렬하기
titanic_sorted=titanic_df.sort_values(by=['Pclass','Name'],ascending=False)
titanic_sorted.head(3)
5-2 Aggregation 함수¶
- DataFrame에서 Aggregation함수는 min(), max(),sum(),count()등이 있습니다. DataFrame에서 바로 Aggregation함수를 적용하게 되면 모든 컬럼에 함수가 적용이 됩니다.
titanic_df.count()
# 특정 컬럼만 적용하기
titanic_df[['Age','Fare']].mean()
5-3 groupby() 적용하기¶
- DataFrame에서 groupby()를 사용할때 입력 파라미터 by에 컬럼을 입력하면 대상 컬럼으로 groupby를 해줍니다. DataFrame에서 groupby()를 호출하면 DaraFrameGroupBy라는 또 다른 형태의 DataFrame을 반환합니다.
titanic_groupby=titanic_df.groupby(by='Pclass')
print(type(titanic_groupby))
titanic_groupby=titanic_df.groupby(by='Pclass').count()
titanic_groupby
SQL에서 사용되는 group by와는 다르게 DataFrame에서는 groupby()를 호출해서 나온 결과에 aggregation함수를 호출하게 되면 groupby() 대상 컬럼을 제외한 모든 컬럼에 aggregation함수를 적용하게 됩니다.
DataFrame의 groupby는 아무래도 API를 기반으로 처리를 하다 보니 SQL의 비해 유연성이 떨어지는 것은 사실입니다.
예를 들어서 서로다른 aggregation함수를 적용하고 싶다고 했을때 SQL에서는 select max(Age), min(SibSp), avg(Fare) from titanic_table group by Pclass 와 같이 쉽게 구현할 수 있지만 DataFrame에서는 복잡한 처리가 필요합니다.
DataFrame에서 groupby()는 agg()내에 입력 값으로 딕셔너리 형태로 aggregation이 적용될 컬럼들과 aggregation 함수를 입력하겨 구현할 수 있습니다.
agg_format={'Age':'max','SibSp':'min','Fare':'mean'}
titanic_df.groupby('Pclass').agg(agg_format)
6. 결손 데이터(Missing Data) 처리하기¶
- 결손 데이터(Missing Date)란 컬럼에 값이 없는 즉 NULL인 경우를 의미합니다. 이를 넘파이 에서는 NaN으로 표현합니다.
- 기본적으로 머신러닝 알고리즘은 NaN값을 처리하지 않기 때문에 다른 값으로 대체 해주는 작업이 필요합니다.
- NaN값은 평균, 총합 등의 함수 연산 시 제외되어 계산이 됩니다.
6-1 isna()를 사용해서 결손 데이터 확인하기¶
- isna()는 데이터가 NaN인이 아닌지를 알려주는 함수입니다. DataFrame에 isna()를 수행하면 모든 컬럼의 값이 NaN인지 아닌지를 True나 False로 알려줍니다.
titanic_df.isna().head(3)
# 결손 데이터 개수 구하기
titanic_df.isna().sum()
6-2 fillna()을 사용해서 결손 데이터 대체하기¶
- fillna()를 이용하면 결손 데이터를 다른 값으로 대체할 수 있습니다. 연습 예제로 타이타닉 데이터 세트의 'Cabin'컬럼의 NaN 값을 'C000'으로 대체해 보도록 하겠습니다.
titanic_df['Cabin']=titanic_df['Cabin'].fillna('C000')
titanic_df.head(3)
주의해야 할 점은 fillna()를 이용해서 반환 값을 다시 받거나 inplace=True 파라미터를 fillna()에 추가해야지 실제 DataFrame의 값이 변경 된다는 점입니다. 1) titanic_df['Cabin']=titanic_df['Cabin'].fillna('C000') 2) titanic_df['Cabin'].fillna('C000', inplace]True)
앞에서 설명한 drop()의 반환 값을 참조하면 이해하는데 도움이 될 것입니다.
# '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.isna().sum()
결손 데이터가 모두 처리된 것을 확인할 수 있습니다.
7. apply lambda 로 데이터 가공하기¶
- 판다스에서는 apply 함수에 lambda식을 결합해서 DataFrame이나 Series의 레코드별로 데이터를 가공하는 기능을 제공합니다. 판드스는 일괄적으로 데이터를 가공 하는 것이 속도 면에서 더 빠르기는 하지만 복합한 가공이 필요한 경우에는 apply lambda를 이용하면 편리합니다.
먼저 lambda식에 대해서 알아보도록 하겠습니다.
7-1 lambda란?¶
- lambda식은 파이썬에서 함수형 프로그래밍을 지원하기 위해 만들어졌습니다.
예를들어 아래와 같이 입력값의 제곱 값을 구해서 반환하는 get_square(a)라는 함수가 있다고 가정을 해보겠습니다.
def get_square(a):
return a**2
print('2의 제곱은:',get_square(2))
lamda는 위와 같은 함수의 선언과 함수 내의 처리를 한 줄의 식으로 쉽게 변환해주는 식입니다.
위 예제를 lamda를 사용하여 변경해 보도록 하겠습니다.
lambda_square=lambda X : X **2
print('2의 제곱은:',lambda_square(2))
lambda_square=lambda X : X **2 에서 ':'로 입력 인자와 반환될 입력 인자의 계산식을 분리하여 줍니다.
':'의 왼쪽에 있는 X는 입력 인자를 가리키게 되며, 오른쪽 입력 인자의 계산식입니다. 오른쪽 계산식은 결론적으로는 반환값을 의미합니다.
lamda식을 사용할 때 여러 개의 값을 입력 인자로 사용하고 싶은 경우에는 보통 map()함수를 결합해서 사용합니다.
a=[1,2,3]
squares=map(lambda X : X**2,a)
list(squares)
다음은 타이타닉 데이터를 사용해서 'Name'컬럼의 문자열의 개수를 담은 새로운 컬럼을 만들어 보겠습니다.
titanic_df['Name_length']=titanic_df['Name'].apply(lambda x : len(x))
titanic_df[['Name','Name_length']].head(3)
다음은 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(10)
lambda 식에서 if else를 사용할때 주의 해야될 부분이 있습니다. if절의 경우 if식보다 반환 값을 먼저 기술해야 주어야 하는데요. 그 이유는 lambda 식 ':'기호의 오른쪽에 반환 값이 있어야 하기 때문입니다. 따라서
x : if x <= 15 'Child' else 'Adult'가 아닌 x : 'Child' if x <= 15 else 'Adult'가 되는 것입니다.
또다른 주의해야 될 점은 if,else만 지원을 하고 if, else if, else와 같이 else if는 지원을 하지 않는다는 점입니다. 그렇기 때문에 else if를 사용하기 위해서는 else을 ()로 내포해서 ()내에서 다시 if else를 적용하여 사용해야 합니다.
마지막 예시로 나이가 15세 이하이면 Child, 15세~60세는 Adult, 61세 이상은 Elderly로 분류해주는 'Age_2'컬럼을 만들어 보도록 하겠습니다.
titanic_df['Age_2']=titanic_df['Age'].apply(lambda x : 'Child ' if x<=15 else('Adult' if x <=60 else 'Elderly'))
titanic_df['Age_2'].value_counts()
실제로 머신러닝 모델을 만들고 예측을 하는데 부분에 있어서 알고리즘이 차지하는 부분보다 데이터를 전처리하고 적절한 피처를 가공 및 추출하는 부분이 훨씬 많은 비중을 차지하게 됩니다. 따라서 넘파이, 판다스등과 같이 데이터를 다루는 다양한 패키지에 대한 이해가 중요할 것이라고 생각합니다. 그렇기 때문에 친해지도록 많이 연습을 하는것이 중요합니다. 화이팅!!
'Machine Learning' 카테고리의 다른 글
사이킷런으로 시작하는 머신러닝 - Model Selection 모듈(2)- (0) | 2020.04.09 |
---|---|
사이킷런으로 시작하는 머신러닝 - Model Selection 모듈(1)- (0) | 2020.04.09 |
사이킷런으로 시작하는 머신러닝 - 사이킷런의 기반 프레임워크 익히기- (1) | 2020.04.09 |
판다스(Pandas)의 자료구조 (0) | 2020.04.06 |
넘파이(Numpy)에 대해 알아보자! (0) | 2020.04.03 |