어느덧 혼공학습단 절반까지 왔다.

확실히 혼자 공부하는 것보다 열심히 하게 되는 것이 있다.

(마감기한이 있어 어떻게든 해내는 느낌이랄까...)

 이번주만 잘 하고 나면 설날이 다가온다.. 뭔가 휴일을 앞두고 있어서 그런지

아님 작심삼일의 마법이 나에게도 적용되는 건지는 잘 모르겠지만 뭔가 점점 게을러지는 기분이 들기 시작한다.

그래도 혼공단만큼은 6주 내내 열심히 공부해서 잘 마무리 했으면 좋겠다.

 

 지난 시간까지 우리는 데이터를 수집하는 과정에 대해 이야기했다. 수집하고자하는 데이터가 API를 지원한다면 이를 통해 JSON, XML 등의 파일형식으로 내려받아 활용하면 되고, 만약 이를 지원하지 않는다면 웹사이트의 HTML 코드에서 원하는 정보를 찾아 긁어오는 웹스크래핑을 배웠다.

 

 그런데, 불러온 데이터는 바로 데이터 분석에 활용하기에는 너무 양이 방대하고 불필요한 정보도 많다. 하여 이번에는 데이터를 필요한 정보만을 뽑아내는 정제에 대하여 학습을 해보자. 우리가 사용한 데이터는 1차시에 활용했던 '도서대출 정보'이다. 만약 기억이 나지않는다면 돌아가서 확인하자.

 

1. 불필요한 데이터 삭제하기

 우리는 도서관 이용자들이 과연 어떤 책을 많이 읽는지 한번 알아보고 싶은 생각이 들었다고 하자. 그래서 이를 알아보기 위해 API를 통해 해당 데이터를 요청하여 데이터 프레임으로 만든 것까지는 가정하겠다. 필자는 1주차에 내려받은 파일을 'data'라는 디렉토리에 저장해두었으므로, 바로 경로를 입력하여 불러오도록 하겠다.

import pandas as pd

filePath = "data/남산도서관 장서 대출목록 (2021년 4월).csv"

ns_df = pd.read_csv(filePath, encoding='EUC-KR', low_memory=False)

ns_df.head()
# 만약, head()는 기본적으로 첫행부터 5번째 행까지만을 출력한다. 따라서, 
# 데이터프레임의 전문을 보고싶다면 show() 메소드를 활용하면 된다.
더보기

[ 출력결과 ]

 성공적으로 데이터를 불러왔다. 불러온 데이터로 분석을 하려고 했더니, 필요치 않은 정보가 너무나도 많고, 중간에 보이는 'NaN이라는 값들도 보인다. 그래서 그런지 데이터를 분석하는데 혼란스러움이 많다. 딱 필요한 정보들만 보였으면 한다. 어떡하면 좋을까?

더보기

지나가는 데이터 분석 이야기

 

" Garbage in, garbage out (a.k.a GIGB) "

 

"쓰레기가 들어가면, 쓰레기가 나온다." 데이터 분석을 조금이라도 공부했던 사람이라면 다들 아는 말들 일 것이다. 이 것이 무슨 의미인가를 예를 들어 말하면, 방금전 데이터 분석을 위해 데이터프레임을 만들었으나 불필요한 정보들이 너무나도 많다. 이 상태로 만약 분석을 한다고 하자. 그렇다면 불필요한 정보들의 영향으로 되게 혼란스럽고 무의미한 결과가 나오지 않을까? 바로 그 상황을 이야기하는 것이다.  다시 말하면, 유의미하고 영향력있는 분석 결과가 나오기 위해선 우리가 분석하기 위한 데이터가 의미가 있는지를 먼저 신경써야한다는 말이다.

 

한마디로 정리하자면, 데이터 분석을 하는 것도 중요하지만 데이터를 잘 정제하는 것 또한 중요하다는 말이다.

 

(1) 데이터 슬라이싱

 우리는 첫번째로 지난시간에 배운 loc 메서드를 사용하여 필요한 열과 행만을 골라서 볼 수 있다. 기본적으로 loc 메서드는 매개변수로 행과 열에 대한 정보를 받으면 해당되는 정보만을 다시 반환하여 준다. 또는 원하는 행만을 보고싶다면 [] 연산자를 활용하여도 좋다. 이때, loc 메서드는 열에 대한 정보는 각 열의 Index 값을 넘겨주어야하므로, 우리가 불러온 데이터의 열에 Index 이름을 확인하고 싶다면 'colums' 메서드를 활용하여 미리 알아보는 것이 좋다.

print(ns_df.colums) 
# ns_df의 열에 대한 인덱스 값을 반환하여 출력한다. 
# 만약, 특정열의 인데스만 보고다면 예를들어, ns_df.colums[0] 처럼 0번째 인덱스만을 반환받을 수 있다.

ns_book = ns_df.loc[:, '번호':'등록일자']
#loc 함수는 기본적으로 숫자로 던져주면 행을 나타내고, 열 인덱스를 넘겨주면 열을 반환한다.

ns_book.head()

 

더보기

[출력결과]

 

(2) 불리언 인덱싱

 loc 메서드를 사용하는 법을 배웠는데, 사용하다보니 필요한 정보만 추출하기 위해서는 모든 정보를 알아야할 귀찮음이 있다. 그냥 내가 원하는 정보외에는 일괄적으로 지울 수는 없을까? 이때는 불리언 인덱싱을 사용할 수 있다.

 데이터 타입중에 "Boolean"을 알것이다. True, False 두가지의 값인데, 이를 활용하여 각 행과 열에 대해서, True 면 반환하고 False라면 반환하지 않는 것을 '불리언 인덱싱'이라고 한다. 즉, 모든 정보를 알지 않아도 필요한 정보가 아니라면 다 False 값을 넘겨주면 알아서 제외시킨다는 것이다. 이는 참거짓을 판단하기 위해 !=, >, <, and 와 같은 비교 연산자를 함께 사용한다. 즉, 열 데이터처럼 고유이름이 있는 경우에 사용하면 좋다.

selected_colums = ns_df.colums != 'Unnamed: 13'
#각 열 인덱스의 이름이 'Unnamed: 13'이 아니라면 True, 맞다면 False를 반환하여 배열로 만들어 반환한다.

ns_book = ns_df.loc[:, selected_colums]
#ns_df의 모든 행과 불리언 배열에서 True에 해당하는 열만 반환한다.

ns_book.head()
#ns_book 출력
더보기

[출력결과]

 

(3) drop, dropna

 필요없는 열과 행을 제외는 해봤지만, 아예 그냥 삭제를 하고싶은 생각이 있다. 그리고 데이터들 중에서 'NaN' 이라는 값이 무엇인지는 몰라도 심히 보기 좋지않다. 이는 어떻게 제거를 해야할까?

 그전에 'NaN'이라는 값은 'Not A Number' 의 약자로 말 그대로 숫자가 아니다는 뜻이다. 판다스에서는 '비어있는 값' 즉, 결측값의 의미를 가지고 있다고 생각하면 된다. 즉 분석에서 가장 문제가 되는 녀석인데, 이를 잘 처리하는 것이 데이터 분석에서의 첫번째 미션이다.

 결측값을 가장 쉽게 해결하는 방법은, 무식하지만 그냥 결측값이 있는 열이나 행을 통채로 삭제하면 된다. drop() 메서드에서 행과 열의 인덱스를 넘겨주면 해당되는 행과 열을 제거해준다.

 하지만, 좀 더 쉽게 지우고 싶다면 dropna 메서드가 더 효과적이다. 이 함수는 각 행과 열에 NaN 값이 있는지 알아서 검사하고 있다면, 지정하는 방식에 따라 행과 열을 지워버린다. 이때 행과 열은 axis 매개변수를 통해 지정해주면 된다. 만약, 행을 지우고 싶다면 '0' 열을 지우고 싶다면 '1'로 지정해주면 된다.

ns_book = ns_df.drop('Unname: 13', axis= 1)
# Unname: 13에 해당하는 열 전체를 삭제한다. 만약 여러 열을 삭제하고 싶다면 인덱스들을 리스트로 만들어 넘기면 된다.
# inplace=True 매개변수를 사용하면, 새롭게 데이터프레임을 만들지 않고 해당 데이터프레임에서 바로 삭제한다.

ns_book = ns_df.dropna(axis=1)
# 각 열에서 NaN(결측값)이 있는 열을 삭제한다. 이때, 각 열에 결측값이 하나라도 있으면 삭제가 된다.
# 만약, 각 열에 모든값이 결측값일 때만 삭제를 하고싶다면 how 매개변수를 all 지정해서 넘겨주면 된다.

 

2. 중복된 데이터 제거하기

 필요없는 데이터는 얼추 정리하는 방법을 익혔다. 그래서 이제는 어느정도 분석을 시작해도 괜찮겠지? 라는 생각도 잠시, 이번엔 중복된 데이터가 눈에 보였다. 인기도서의 경우 도서관에선 여러권을 구비한다는 사실이 머릿속에서 스쳐갔다. 이럴땐 어떻게 중복된 데이터를 처리해야할까?

 

(1) duplicated

 판다스에서 지원하는 중복된 정보를 찾아내는 메서드이다. 기본적으로 모든 열의 정보가 일치하면 해당 열을 True로 반환해준다. 대부분 모든 열에 대해서 검사를 할 때도 있지만, 일부 열의 정보만 중복된 상황을 검사해야할 때도 있다. 이때는 메서드의 subset 이라는 매개변수에 검사하고자하는 기준 열의 인덱스 값을 리스트로 넘겨주면, 부분적으로 검사하여 중복된 행을 True로 반환한다.

ns_book.duplicated()
#해당 데이터 프레임 각 행의 데이터 값을 비교하여 모든 열의 정보가 일치하는 행이 있다면
#True, 그렇지 않으면 False로 반환한다.
#만약 일부분의 열로만 비교하고 싶다면 subset 매개변수에 기준 열의 인덱스를 넘겨주면 된다.
#중복된 행을 불리언 배열로 반환받고 싶은경우, keep의 매개변수를 False로 지정하면 된다.

sum(ns_book.duplicated())
#중복된 열의 개수가 총 몇개인지 확인한다.
#Boolean 타입은 True는 정수 1의 값을 가지고 False는 0을 가진다.

 

 

(2) groupby

 어떤 도서가 여러가지가 있는지 확인을 했다. 그래서 같은 도서는 대출건수를 한번에 합쳐서 확인하고 싶다. 이는 groupby() 메서드를 사용하면 된다. 해당 메서드는 합칠 행의 기준이 되는 열 정보를 리스트나 문자열로 받으면,  해당 정보가 같은 열들 끼리 묶어서 반환한다. 이때, 매개변수로 넘겨받은 열 인덱스 중에 하나라도 'NaN' 가 있으면 해당 행은 삭제 처리하는 것이 기본값이므로, 이를 삭제하지 않기를 원한다면 dropna 매개변수를 False로 지정해주면 된다. 그리고 마지막으로 대출건수끼리 합칠 예정이므로 sum() 함수를 사용하면 된다.

count_df = ns_book[['도서명', '저자', 'ISBM', '권', '대출건수']]
# 필요한 열만 []연산자를 사용하여 추출

group_df = count_df.groupby(by=['도서명', '저자', 'ISBM', '권'], dropna= False)
loan_count = group_df.sum() # groupby()로 분류된 항목끼리의 대출건수를 하나로 합친다.

# 보통 위의 두 과정을 합쳐서 loan_df = count_df.groupby(by=['도서명', '저자', 'ISBM', '권'], dropna= False).sum()
# 로 표현하기도 한다.

loan_df.head()

 

3. 잘못된 데이터 수정하기

이때까지, 결측값을 삭제하여 분석을 시도하였는데 결측값이 몇개 없는 열을 날리기에는 아까운 느낌이 많이 든다. 그리고 데이터 값이 오타로 표현된 부분도 보기 좋게 수정을 하고, 혹은 결측값이 다양한 형태로 있는데 이를 그냥 하나로 표현해주고 싶은 생각이 든다. 굳이 삭제할 필요가 없거나, 오히려 삭제하면 데이터 분석에 문제가 될 것 같은 경우에는 수정이 더 나은 선택지가 아닐까?

 

(1) 결측값 파악하기

 일단 수정을 하려면, 결측값이 얼마나 있는지 파악하는 것이 가장 첫번째 임무이다. 해서 우선적으로 다음과 같이 살펴보자.

import pandas as pd

filePath = "data/남산도서관 장서 대출목록 (2021년 4월).csv"
ns_book= pd.read_csv(filePath, encoding='EUC-KR', low_memory=False)

ns_book.head()

ns_book.info() 
# 해당 데이터 프레임의 정보(Colum, 누락된 값이 없는 행 수, 열데이터 타입 등)를 요약하여 알려준다.
# 데이터프레임의 메모리 사용량을 알고싶다면 memory_usage= 'deep'로 지정해주면 된다.

ns_book.isna().sum() # 결측값을 각 열마다 몇개가 존재하는지에 대한 정보를 반환한다.

 

(2) 결측값 수정하기

우선적으로 데이터의 결측값이 얼마나 있는지 파악을 완료하였으니 이제는 수정하는 방법을 알아보자. 첫번째로 결측값을 찾아 불리언 배열로 만든 뒤에, 해당 값은 전부 빈 문자열로 만들어보자. 

set_inbn_na_rows = ns_book['세트 ISBN'].isna()
# 세트 ISBN 열에 각 행마다 결측값이 있는지 검사하고 이를 불리언 배열로 반환한다.

ns_book.loc[set_inbn_na_rows, '세트 ISBN'] = ''
# 결측값이 있는 행을 빈 문자열로 바꾼다.

ns_book['세트 ISBN'].isna().sum()
# 결측값이 해당 열에 몇개가 있는지를 반환한다. 이때 NaN의 개수를 센다.

 

fillna() 메서드를 활용하면 더 간단하게 수정도 가능하다.

ns_book.fillna('없음').isna().sum()
# 데이터프레임 전체를 검사하여 결측값이 있다면 '없음'이라는 문자열로 대체한다.

ns_book['부가기호'].fillna('없음').isna().sum()
# '부가기호'열만 검사하여 결측값을 '없음'이라는 문자열로 대체한다.

ns_book.fillna({'부가기호' : '없음'}).isna().sum()
# 만약, 기준 열만 수정한 뒤 데이터 프레임 전체의 정보를 반환 받고 싶다면, 
# key값을 해당 열의 이름으로 하고 대체하고자 하는 값을 value 하는 딕셔너리 형태로 전달하면 된다.

 

 

두번째로는 repalce() 메서드를 사용해도 된다.

import numpy as np
# 판다스 자체에서는 NaN 값에 대한 기준이 명확하게 없으므로, 넘파이 패키지에 nan 메서드를 활용한다.

ns_book.replace(np.nan, '없음')
# 데이터 프레임에서 결측값을 모두 '없음'이라는 문자열로 대체한다.

ns_book.replace([np.nan, '2021'], ['없음', '21'])
# 결측값은 '없음'으로 '2021'이라는 문자열은 '21'라는 문자열로 대체한다.
# replace([기존 값], [수정 값])
# 딕셔너리로 ns_book.replace({ np.nan: '없음', '2021':'21'}) 해도 동일한 결과를 반환한다.

ns_book.replace({'부가기호':np.nan}, '없음')
# 바꾸고자하는 열의 특정 값을 딕셔너리로 만들고, 바꾸고자 하는 값을 주면 열마다 다른 값으로도 수정할 수 있다.

 

 

4. 기본숙제_p.182

2. 1번문제의 데이터 프레임에서 'col1' 열의 합을 계산하는 명령으로 올바르지 않은 것을 고르시오.

① df['col1'].sum()

② df[['col1']].sum()

③ df.loc[: , df.columns == 'col1'].sum()

④ df.loc[:, [False, False, True]].sum()

 

나머지 항은 col1 열의 값을 각 값을 합하여 반환하지만, ④은 col3의 값을 계산한다.

+ Recent posts