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

머신러닝 기초 (4) - 데이터 전처리 with Pandas

by 싸코 2018. 1. 27.

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


Pandas를 사용하면 엑셀의 스프레드시트나 SQL의 테이블을 사용하듯이 파이썬에서 데이터를 다루는 것처럼 소개하였는데 이전 시간에는 이런 내용을 별로 다루지 않았습니다. Pandas 그 자체에 대해서 파이썬의 관점에서 주로 다루었던 것 같습니다. map 함수나 기타 방법을 통해서 변수를 변환하고 데이터 처리 작업을 하는 것은 일반적으로 우리가 사용하는 방법과는 거리가 멀었던 것은 사실입니다.


이번에는 SQL에서 많이 사용하는 group by 나 merge(JOIN 과 같습니다.), concat(UNION 과 비슷합니다) 같은 Pandas DataFrame의 메소드를 배우게 됩니다. 나아가 실제 데이터베이스에 접속하여 데이터를 불러오는 것도 다룹니다.


이번 포스팅에서 다루는 Aggregation, Transformation, Filteration 이 세 가지만 알고 간다면 Pandas를 이용해 데이터를 전처리할 때 충분히 많은 도움이 될 것 같습니다.



Chapter 5. Pandas Section #2

머신러닝 기초 (4) - Data Handling with Pandas



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


Group By의 Hierarchical index

groupby는 sql의 GROUP BY와 동일하게 작용되어 설정한 컬럼에 대해서 그룹별로 묶어주는 작업을 합니다. 일반적인 SQL과 비슷하게 Aggregate 함수를 사용해서 그룹별로 집계를 위한 작업을 해주어야 합니다.

그리고 groupby 명령의 결과물도 dataframe이기 때문에 두 개의 group by를 하면 index가 두 개가 생성됩니다.


unstack()을 사용하면 group으로 묶여진 데이터를 matrix 형태로 전환해줍니다. 

h_index.unstack()
Year 2014 2015 2016 2017
Team
Devils 863.0 673.0 NaN NaN
Kings 741.0 NaN 756.0 788.0
Riders 876.0 789.0 694.0 690.0
Royals 701.0 804.0 NaN NaN
kings NaN 812.0 NaN NaN


h_index.unstack().fillna(0)
Year 2014 2015 2016 2017
Team
Devils 863.0 673.0 0.0 0.0
Kings 741.0 0.0 756.0 788.0
Riders 876.0 789.0 694.0 690.0
Royals 701.0 804.0 0.0 0.0
kings 0.0 812.0 0.0 0.0


swaplevel은 index의 level을 변경할 수 있습니다.
h_index.swaplevel()
Year  Team  
2014  Devils    863
2015  Devils    673
2014  Kings     741
2016  Kings     756
2017  Kings     788
2014  Riders    876
2015  Riders    789
2016  Riders    694
2017  Riders    690
2014  Royals    701
2015  Royals    804
      kings     812
Name: Points, dtype: int64


sort_index은 group by의 index level로 정렬을 하는 것을 말합니다.
h_index.sort_index()
Team    Year
Devils  2014    863
        2015    673
Kings   2014    741
        2016    756
        2017    788
Riders  2014    876
        2015    789
        2016    694
        2017    690
Royals  2014    701
        2015    804
kings   2015    812
Name: Points, dtype: int64



Group By를 통해 split 된 데이터를 각기 볼 수 있을까?

group 별로 split 된 데이터를 볼 수 있습니다. groupby 메소드를 적용한 DataFrame은 <pandas.core.groupby.DataFrameGroupBy object> 라고 나옵니다. 그룹별로 split된 상태로 저장되어 있는 것이며 일종의 튜플 형식이기 때문에 for문 등을 통해서 구분하면 group 별로 데이터를 볼 수도 있습니다.

이를 보기 위해서는 split된 데이터에 대해서 apply가 필요하며 Aggregation(집계), Transformation(변환), Filtration(필터) 세 가지에 대해서 작업을 할 수 있습니다. 

Aggregation

특정 컬럼에 aggreation function을 복수개 사용할 수도 있습니다.
grouped['Points'].agg([np.sum, np.mean, np.std])
sum mean std
Team
Devils 1536 768.000000 134.350288
Kings 2285 761.666667 24.006943
Riders 3049 762.250000 88.567771
Royals 1505 752.500000 72.831998
kings 812 812.000000 NaN


Transformation

group 별로 transform에 사용하는 함수를 적용하여 변환 작업을 합니다. max 또는 min을 사용했을 때 전체 데이터에 대해서 최대, 최소를 구하는 것이 아니라 각 그룹에서의 최대, 최소를 구분하고 합쳐서 결과를 반환합니다.
score = lambda x: (x.mean())/x.std()
grouped.transform(score)
Points Rank Year
0 8.606404 3.500000 1561.199587
1 8.606404 3.500000 1561.199587
2 5.716400 3.535534 2848.933221
3 5.716400 3.535534 2848.933221
4 31.726932 1.443376 1319.563582
5 NaN NaN NaN
6 31.726932 1.443376 1319.563582
7 31.726932 1.443376 1319.563582
8 8.606404 3.500000 1561.199587
9 10.331997 1.178511 2848.933221
10 10.331997 1.178511 2848.933221
11 8.606404 3.500000 1561.199587


Filteration

lambda 함수를 적용해서 필터링 하고자 하는 조건의 함수를 적용하여 해당 그룹만 나오도록 합니다.
df.groupby('Team').filter(lambda x: len(x) >= 3)
Points Rank Team Year
0 876 1 Riders 2014
1 789 2 Riders 2015
4 741 3 Kings 2014
6 756 1 Kings 2016
7 788 1 Kings 2017
8 694 2 Riders 2016
11 690 2 Riders 2017



중요한 꿀팁

dateutils를 사용하면 datetime 컬럼에 대해서 group by를 하고 사용하기가 훨씬 매우 편합니다. dateutils을 통해 datetime의 형식을 맞춰주어 전처리 시에 활용하기 좋습니다.

import dateutil
df_phone['date'].apply(dateutil.parser.parse, dayfirst=True)
df_phone.head()
index date duration item month network network_type
0 0 15/10/14 06:58 34.429 data 2014-11 data data
1 1 15/10/14 06:58 13.000 call 2014-11 Vodafone mobile
2 2 15/10/14 14:46 23.000 call 2014-11 Meteor mobile
3 3 15/10/14 14:48 4.000 call 2014-11 Tesco mobile
4 4 15/10/14 17:27 4.000 call 2014-11 Tesco mobile
df_phone.groupby('month')['duration'].sum()
month
2014-11    26639.441
2014-12    14641.870
2015-01    18223.299
2015-02    15522.299
2015-03    22750.441
Name: duration, dtype: float64
df_phone.groupby(['month', 'item'])['date'].count().unstack()
item call data sms
month
2014-11 107 29 94
2014-12 79 30 48
2015-01 88 31 86
2015-02 67 31 39
2015-03 47 29 25



Aggregation 하나 더!

하나의 컬럼에 aggregation을 여러개 적용할 수도 있습니다. dictionary 형태로 key 값을 컬럼으로 value를 적용하고 싶은 집계함수를 사용하면 한 컬럼에 대해서 다양한 집계 함수를 적용하여 결과를 확인할 수 있습니다.

df_phone.groupby(['month', 'item']).agg({'duration': [min, max, sum], 
                                         'network_type': 'count',
                                         'date': [min, 'first', 'nunique']})
duration network_type date
min max sum count min first nunique
month item
2014-11 call 1.000 1940.000 25547.000 107 01/11/14 15:13 15/10/14 06:58 104
data 34.429 34.429 998.441 29 01/11/14 06:58 15/10/14 06:58 29
sms 1.000 1.000 94.000 94 03/11/14 08:40 16/10/14 22:18 79
2014-12 call 2.000 2120.000 13561.000 79 02/12/14 11:40 14/11/14 17:24 76
data 34.429 34.429 1032.870 30 01/12/14 06:58 13/11/14 06:58 30
sms 1.000 1.000 48.000 48 01/12/14 12:51 14/11/14 17:28 41
2015-01 call 2.000 1859.000 17070.000 88 02/01/15 11:27 15/12/14 20:03 84
data 34.429 34.429 1067.299 31 01/01/15 06:58 13/12/14 06:58 31
sms 1.000 1.000 86.000 86 02/01/15 23:26 15/12/14 19:56 58
2015-02 call 1.000 1863.000 14416.000 67 01/02/15 13:33 15/01/15 10:36 67
data 34.429 34.429 1067.299 31 01/02/15 06:58 13/01/15 06:58 31
sms 1.000 1.000 39.000 39 02/02/15 17:35 15/01/15 12:23 27
2015-03 call 2.000 10528.000 21727.000 47 01/03/15 12:19 12/02/15 20:15 47
data 34.429 34.429 998.441 29 01/03/15 06:58 13/02/15 06:58 29
sms 1.000 1.000 25.000 25 02/03/15 09:19 19/02/15 18:46 17



grouped = df_phone.groupby('month').agg({"duration":[min,max,np.mean]})
grouped.columns = grouped.columns.droplevel(level=0)
grouped.rename(columns={"min": "min_duration", "max": "max_duration", "mean": "mean_duration"})
min_duration max_duration mean_duration
month
2014-11 1.0 1940.0 115.823657
2014-12 1.0 2120.0 93.260318
2015-01 1.0 1859.0 88.894141
2015-02 1.0 1863.0 113.301453
2015-03 1.0 10528.0 225.251891




add_prefix를 사용하면 컬럼명을 쉽게 구분할 수 있도록 prefix를 추가하여 볼 수 있습니다.

grouped.add_prefix("duration_")
duration_min duration_max duration_mean
month
2014-11 1.0 1940.0 115.823657
2014-12 1.0 2120.0 93.260318
2015-01 1.0 1859.0 88.894141
2015-02 1.0 1863.0 113.301453
2015-03 1.0 10528.0 225.251891




Pandas로 데이터베이스의 테이블을 불러올 수 있을까?

빅데이터 분석 작업을 하다보면 데이터베이스에 저장해놓은 대용량의 파일에 접근하여 필요한 데이터만 추출해서 사용하게 될 때가 많아집니다. Pandas에는 sql 쿼리를 사용하여 바로 pandas 데이터프레임으로 해당 DB의 테이블을 불러올 수 있는 방법이 있습니다.

pandas에서 테이블의 데이터를 가져오기에 앞서 데이터베이스에 접속을 해야 합니다. pymysql과 같은 파이썬으로 데이터베이스에 접속을 도와주는 라이브러리를 사용하는 것이 좋습니다.

데이터를 가져오고자 하는 데이터베이스와 연결된 connection 객체가 생성되면 이를 pandas의 read_sql_query 함수를 이용해 쿼리문을 날려 데이터를 가져올 수 있습니다.

df_routes = pd.read_sql_query("select * from routes;", conn)
df_routes.head()
index airline airline_id source source_id dest dest_id codeshare stops equipment
0 0 2B 410 AER 2965 KZN 2990 None 0 CR2
1 1 2B 410 ASF 2966 KZN 2990 None 0 CR2
2 2 2B 410 ASF 2966 MRV 2962 None 0 CR2
3 3 2B 410 CEK 2968 KZN 2990 None 0 CR2
4 4 2B 410 CEK 2968 OVB 4078 None 0 CR2




댓글