본문 바로가기
Data Science/머신러닝 기초

머신러닝 기초 (3) - Pandas 복습

by 싸코 2018. 1. 22.

가천대 최성철 교수님의 '밑바닥부터 시작하는 머신러닝 입문'을 수강하며 노트 필기 및 추가 내용 작성을 목적으로 포스팅합니다.


이번에 다루는 Pandas는 Python의 엑셀 버전이라고 볼 수 있는데 많은 기능적인 부분들이 엑셀 스프레드시트를 다루는 것과 비슷하고 또 R의 데이터프레임의 개념을 가져왔기 때문에 실제 사용하는 함수나 기능들이 상당히 유사합니다. 그리고 Pandas의 내부적 자료구조는 NumPy로 되어있기 때문에 일부분 많은 것을 NumPy의 그것을 공유합니다.


2017/12/06 - 파이썬으로 데이터분석하자 (4) - Pandas 기본


강의 내용 중에 교수님의 본인의 코드 스타일이 드러나는데 가끔 꿀팁들이 나와 정말 유용하게 사용할 수 있었습니다. apply나 map을 메소드로 하여 사용할 수 있다는 것을 몰랐을 때는 for문을 돌려가면서 했었고 isnull에 대해서 쉽게 통계치를 뽑는 노하우 등이 담겨 있어서 정말 유용했습니다. Pandas나 NumPy는 사전 같은 느낌이라 혼자 공부하면 지루한 내용들인데 꽤(?) 재미있게 이를 복습할 수 있어 좋았습니다.

Chapter 4. Pandas Section #1

머신러닝 기초(3) - Pandas 복습



Pandas는 정형 데이터 처리에 특화된 파이썬 라이브러리입니다. NumPy array와 통합되어 강력한 스프레드시트 처리 기능을 제공하여 쉽고 빠르게 많은 작업들을 처리해줍니다.


Series? DataFrame?

Series는 numpy array 하나를 표현하는데 데이터프레임(행렬)의 차원에서 보면 하나의 column에 해당하는 값들이 모인 것으로 볼 수 있습니다. DataFrame은 이 Series들이 여러 개 모여 하나의 매트릭스를 구성한 것으로 보면 됩니다. 각 NumPy array는 고유의 data type을 가질 수 있어 데이터프레임은 서로 다른 자료형이 모인 하나의 엑셀 스프레드시트 처럼 활용될 수 있습니다.


Series는 Index label을 가지고 있습니다. 특이한 것은 index가 중복으로도 올 수 있다는 점입니다. index에도 이름을 따로 지정해 줄 수 있다는 것도 있습니다. Series 단독으로는 잘 사용하지 않으니까 그냥 이런 것이 있다. 데이터프레임의 하나의 컬럼이 Series 이구나 정도만 알아두시면 좋을 듯 합니다. 기본적으로 NumPy의 subclass 이므로 NumPy 사용과 비슷합니다.


DataFrame은 여러 Series의 합입니다. row와 column 각각에 index를 설정할 수 있습니다. column에 원하는 이름으로 index를 달 수 있다는 것은 이해가 잘 되지만 row에도 index를 달 수 있다는 점은 흥미로운 부분입니다. 데이터를 조인하고 연산하거나 할 때 이 index들을 기준으로 이루어지기 때문에 중요합니다.




loc와 iloc의 차이점은 무엇인가요?

loc는 index location을 의미합니다. location은 index의 값들을 통해 인덱싱을 하는 방식입니다. index 순서가 다음과 같을 때 loc은 3이라는 index 값으로 인덱싱을 하는 것을 볼 수 있습니다.

s = pd.Series(np.nan, index=[10,9,8,1,2,3])
s.loc[:3]
10   NaN
9    NaN
8    NaN
1    NaN
2    NaN
3    NaN
dtype: float64
s.iloc[:3]
10   NaN
9    NaN
8    NaN
dtype: float64

iloc는 index position을 의미합니다. 3이라는 숫자는 order로 3번째에 오는 값까지 인덱싱하는 것을 볼 수 있습니다. 일반적으로 생각되는 방식이라면 iloc을 사용하는 것이 편합니다. loc은 인덱스 값이 어떻게 구성되어있는지 명확하게 알아야 하기 때문에 불편한 점이 다소 있습니다.

loc과 iloc의 차이점을 알고 필요에 따라 적절한 것을 사용하면 좋습니다.



기존 데이터프레임에 새로운 컬럼을 추가하고 싶다면?

sql에서

SELECT *, 13
FROM table

을 사용하면 기존 테이블에 새로운 컬럼이 생기고 그 값은 13으로 채워져 있는 것을 볼 수 있습니다. 이와 비슷하게 데이터 프레임에서도 특정 값이나 어떤 계산된 값들이 추가되기를 원할 때는 생각보다 간단하게 이를 해결할 수 있습니다.

df = pd.DataFrame(np.random.randint(1, 10,size=(5,3)), index = range(5), columns=['a','b','c'])
df

a

b

c

0

5

4

8

1

2

1

8

2

5

7

6

3

4

1

7

4

6

5

2



df['new'] = 13
df
a b c new
0 5 4 8 13
1 2 1 8 13
2 5 7 6 13
3 4 1 7 13
4 6 5 2 13



df[컬럼이름] = 넣고 싶은 값
을 하는 것만으로도 기존 데이터프레임에 새로운 값을 가진 컬럼이 추가되었습니다. 만약에 select에서 column간 연산에 대해서 수행하고 싶다면 똑같은 방식으로 할당하면 됩니다.

df['sum_ab'] = df['a'] + df['b']
df
a b c new sum_ab
0 5 4 8 13 9
1 2 1 8 13 3
2 5 7 6 13 12
3 4 1 7 13 5
4 6 5 2 13 11




>> df.drop([0,1,2,3], inplace = True)


데이터프레임에서 0~4번째 컬럼을 삭제합니다




데이터프레임도 산술 연산이 가능할까?

데이터프레임의 기본 데이터구조는 NumPy array이므로 산술 연산이 가능합니다. 연산을 할 때 기준은 인덱스가 됩니다. axis 파라미터로 열을 기준으로 할 것인지 행을 기준으로 할 것인지 설정할 수 있습니다.

만약 shape가 서로 다른 데이터프레임 간에 연산이 이루어진다면 남는 부분 만큼에 NaN(Not a Number) 값으로 연산 결과가 나타납니다. 만약 NaN 값이 아니라 그대로 반환하고 싶다면 fill_value라는 파라미터에 0을 넣으면 됩니다.

df1 = pd.DataFrame(np.random.randint(1, 10,size=(5,3)), index = range(5), columns=['a','b','c'])
df2 = pd.DataFrame(np.random.randint(1, 10,size=(2,3)), index = range(2), columns=['a','b','c'])df1

df1
a b c
0 5 7 2
1 2 6 4
2 5 4 1
3 5 1 2
4 4 5 6

df2
a b c
0 6 2 1
1 8 2 7

df1.add(df2)
a b c
0 11.0 9.0 3.0
1 10.0 8.0 11.0
2 NaN NaN NaN
3 NaN NaN NaN
4 NaN NaN

NaN


df1.add(df2, fill_value=0)
a b c
0 11.0 9.0 3.0
1 10.0 8.0 11.0
2 5.0 4.0 1.0
3 5.0 1.0 2.0
4 4.0 5.0 6.0




lambda, map, apply for DataFrame

lambda는 익명 함수로 간단하게 함수를 만들어서 사용할 때 사용되곤 합니다. 익명함수라는 말 그대로 일반적으로는 변수에 할당하지 않고 사용합니다.


map 함수는 list 같은 sequence형 데이터를 argument로 받아서 각 원소에 입력 받은 함수를 적용시키고 list로 반환됩니다. 위에서 말한 lambda 함수가 map의 function으로 많이 사용됩니다. 


==> Series도 numpy array이므로 sequence 자료형이라고 볼 수 있으며 map 함수의 사용이 가능합니다.


s1 = pd.Series(['M', 'M','F','F','M'])
print(s1)
gender = {'M':0, 'F': 1}
print(s1.map(gender))

0 M 1 M 2 F 3 F 4 M dtype: object 0 0 1 0 2 1 3 1 4 0 dtype: int64


map 함수는 위와 같이 어떤 값을 치환할 때 유용하게 사용할 수 있습니다.  s1이라는 시리즈는 성별에 대한 값이 있는데 분석을 위해서는 이를 binary 형태로 변형해야 하는데 이때 gender를 mapping 하는 dictionary를 만들어서 map 함수를 사용해서 간단하게 치환할 수 있습니다.


map 함수는 Series의 내장 메소드로 저장되어 있어서 유용하게 사용할 수 있습니다.

>> df1.map(lambda x: x+1) 같이도 사용할 수 있습니다.



하지만 replace를 사용하면 map 함수의 기능중에서 데이터를 변환하는 기능만 담당해주는 특화된 메소드입니다. map은 다양한 함수를 적용하여 작업을 할 수 있는 반면에 replace는 데이터의 변환만 해줍니다.

s1.replace(gender)

0    0
1    0
2    1
3    1
4    0
dtype: int64


apply는 map과 다르게 전체 column에 해당 함수를 적용합니다. map이 각 Series 데이터에 적용되는 것만과는 다릅니다. apply는 전체 통계자료를 낼 때 사용할 수 있습니다.

>> df.apply(lambda x: x.max() - x.min())

을 하면 각 컬럼의 max - min 값이 정리되어 출력됩니다.


위와 같이 scalar 값 이외에도 series 값으로도 반환이 가능합니다.

>> df.apply(lambda x: Series([x.min(), x.max()], index=["min", "max"]))

이런식으로 데이터프레임에 해당 익명함수를 적용하면 min과 max를 행의 인덱스로 하고 각 컬럼의 min, max에 해당하는 값들이 반환됩니다.




DataFrame에는 R에서 사용하는 summary 함수 비슷한 건 없나요?

describe 메소드를 사용하면 비슷하게 데이터에 대한 요악된 통계 데이터를 확인할 수 있습니다.



카테고리를 숫자로 쉽게 변환하는 방법은?

unique 함수로 컬럼에서 distinct 값을 뽑아서 이를 enumerate로 순서 index와 값을 매칭하여서 이를 활용하여 string으로 된 범주형 값을 숫자형으로 손쉽게 치환할 수 있습니다.

s2 = pd.Series(['M','M','F','E','F','M','F','E'])
s2.unique()
array(['M', 'F', 'E'], dtype=object)
dict(enumerate(sorted(s2.unique())))
{0: 'E', 1: 'F', 2: 'M'}
value = list(map(int, np.array(list(enumerate(s2.unique())))[:,0].tolist()))
value
[0, 1, 2]
key = np.array(list(enumerate(s2.unique())), dtype=str)[:, 1].tolist()
key
['M', 'F', 'E']
s2.replace(to_replace=key, value=value, inplace=True)
print(s2)
0    0
1    0
2    1
3    2
4    1
5    0
6    1
7    2
dtype: int64




Null 값은 어떻게 확인하나요? 다른 유용한 기능은?

df.isnull().sum(axis)

데이터프레임에서 isnull로 하면 True False로 반환되는 것을 sum으로 합하면 isnull 값이 True 개수만큼 합해지기 때문에 sum으로 null이 몇 개 있는지 확인할 수 있다. axis를 0으로 하면 컬럼 별로 null 값 개수를 쉽게 찾을 수 있다.

df.sort_values([정렬 기준 컬럼]) 은 order by와 같은 역할을 해서 내림차순으로 정렬해줍니다.


df.cumsum()은 증가해서 사용합니다. 시간의 순서에 따라서 거래량을 표현한다고 할 때 누적 거래량을 표현할 때 cumsum을 사용합니다.

df.cummax()는 내려가면서 cumsum 값 중에서 가장 큰 값을 찾을 수 있습니다.



corr, cov, corrwith로 상관관계 공분산을 쉽게 찾을 수 있습니다.


corrwith를 사용하면 한 컬럼에 대해서 다른 컬럼과의 상관관계를 분석할 수 있습니다.




댓글