실습 코드 : https://colab.research.google.com/drive/1w0EtiFSRByNqe3urTkmTVoC2dx7Q9bi2?usp=sharing
데이터에서 불필요한 부분을 삭제하거나 손상/부정확한 부분을 수정하는 등의 작업
print(dataframe.columns)
dataframe.drop(['주제분류번호','Unnamed: 13'], axis=1, inplace=True)
# inplace : 변경 결과를 기존 변수(dataframe)에 바로 적용하기.
dataframeWithoutNaN = dataframe.dropna(axis=1, how='all')
# how : 'all'을 지정하면 모든 값이 NaN일 때만 삭제. how 변수를 지정하지 않으면 NaN이 포함된 행/열은 모두 삭제된다.
selected_rows = dataframe['출판사'] == '한빛미디어'
# 행 별로 열의 값을 비교(출판사 값 == 한빛미디어)한 boolean 배열을 반환한다.
hanbit = dataframe[selected_rows]
highscore = dataframe[dataframe['대출건수'] > 1000]
sum(dataframe.duplicated())
dup_rows = dataframe.duplicated(subset=['도서명','저자','ISBN'], keep=False)
# keep = 중복된 열 중 첫 번째 열도 True로 표시
groupStandardDf = dataframe[['도서명','저자','ISBN','권','대출건수']]
loan_count = groupStandardDf.groupby(by=['도서명','저자','ISBN','권'], dropna=False).sum()
dup_rows = dataframe.duplicated(subset=['도서명','저자','ISBN','권'])
unique_rows = ~dup_rows
nonDupDf = dataframe[unique_rows].copy()
# copy() : 명시적으로 복사하여 다른 메모리에 저장되도록 보장해줌.
nonDupDf.set_index(['도서명','저자','ISBN','권'], inplace=True)
nonDupDf.update(loan_count)
nonDupDf.reset_index(inplace=True)
nonDupDf = nonDupDf[dataframe.columns]
def data_cleaning(filename):
origin = pd.read_csv(filename, low_memory=False)
nonNull = origin.dropna(axis=1, how='all')
count_df = nonNull[['도서명','저자','ISBN','권','대출건수']]
loan_count = count_df.groupby(by=['도서명','저자','ISBN','권'], dropna=False).sum()
dup_rows = nonNull.duplicated(subset=['도서명','저자','ISBN','권'])
unique_rows = ~dup_rows
duplicates = nonNull[unique_rows].copy()
duplicates.set_index(['도서명','저자','ISBN','권'], inplace=True)
duplicates.update(loan_count)
nonDup = duplicates.reset_index()
nonDup = nonDup[nonNull.columns]
return nonDup
ns_book4 = pd.read_csv('ns_book4.csv', low_memory=False)
ns_book4.info()
ns_book4.isna().sum()
ns_book4 = ns_book4.astype({'도서권수': 'int32', '대출건수': 'int32'})
열의 데이터 형이 정수형인 경우 : None 값 지정
이외 : np.nan
ns_book4.loc[0, '도서권수'] = None
ns_book4.loc[0, '부가기호'] = np.nan
ns_book4.loc[set_isbn_na_rows, '세트 ISBN'] = ''
ns_book4.fillna('없음')
ns_book4['부가기호'].fillna('없음')
# ns_book4.fillna({'부가기호': '없음'})
ns_book4.replace(np.nan, '없음')
ns_book4.replace({np.nan: '없음', '2021': '21'}).head(2)
ns_book4.replace({'부가기호': {np.nan: '없음'}, '발행년도': {'2021': '21'}})
문자열 패턴을 찾아서 대체하기 위한 규칙의 모음
()로 그룹을 지정할 수 있다.
파이썬에서는 r 문자를 붙여 정규표현식임을 나타낼 수 있다.
표현식이 반복될 때는 중괄호와 숫자로 표현할 수 있다. ex) \d\d(\d\d) == \d{2}(\d{2})
ns_book4.replace({'발행년도': {r'\d\d(\d\d)': r'\1'}}, regex=True)
ns_book4.replace({'저자': {r'(.*)\s\(지은이\)(.*)\s\(옮긴이\)': r'\1\2'}, '발행년도': {r'\d\d(\d\d)': r'\1'}}, regex=True)
데이터프레임에서 1988년도에 발간된 책들으 찾을 수 없는 오류 발생. 원인 파악 후 조치하기
ns_book4['발행년도'].str.contains('1988').sum()
invalid_number = ns_book4['발행년도'].str.contains('\D', na=True) # na를 true로 하여 NaN 행이 NaN이 아닌 True로 저장되도록.
print(invalid_number.sum())
ns_book4[invalid_number].head()
ns_book5 = ns_book4.replace({'발행년도':'.*(\d{4}).*'}, r'\1', regex=True)
unknown_year = ns_book5['발행년도'].str.contains('\D', na=True)
print(unknown_year.sum())
ns_book5[unknown_year].head()
ns_book5.loc[unknown_year, '발행년도'] = '-1'
ns_book5 = ns_book5.astype({'발행년도': 'int32'})
ns_book5['발행년도'].gt(4000).sum()
dangun_yy_rows = ns_book5['발행년도'].gt(4000)
ns_book5.loc[dangun_yy_rows, '발행년도'] = ns_book5.loc[dangun_yy_rows, '발행년도'] -2333
ns_book5.loc[ns_book5['발행년도'].gt(4000), '발행년도'] = -1
old_books = ns_book5['발행년도'].gt(0) & ns_book5['발행년도'].lt(1900)
ns_book5[old_books]
ns_book5.loc[old_books, '발행년도'] = -1
도서명, 저자, 출판사, 발행년도에 값이 누락된 경우를 해결해보기
na_rows = ns_book5['도서명'].isna() | ns_book5['저자'].isna() \
| ns_book5['출판사'].isna() | ns_book5['발행년도'].eq(-1)
print(na_rows.sum())
ns_book5[na_rows].head(2)
누락된 데이터를 웹 스크래핑으로 획득하는 함수
- 저자는 두 명 이상일 수 있기 때문에 find_all()메서드로 모두 추출후 for문으로 리스트에 저장. 그 후 join()메서드로 하나의 문자열로 변경.
- 발행 연도는 re 모듈의 findall()함수로 원하는 정규식에 매칭되는 모든 문자열을 리스트로 받기. 그 후 정규식으로 추출
import re
def get_book_info(row):
title = row['도서명']
author = row['저자']
pub = row['출판사']
year = row['발행년도']
url = 'http://www.yes24.com/Product/Search?domain=BOOK&query={}'
r = requests.get(url.format(row['ISBN']))
soup = BeautifulSoup(r.text, 'html.parser')
try:
if pd.isna(title):
title = soup.find('a', attrs={'class':'gd_name'}) \
.get_text()
except AttributeError:
pass
try:
if pd.isna(author):
authors = soup.find('span', attrs={'class':'info_auth'}) \
.find_all('a')
author_list = [auth.get_text() for auth in authors]
author = ','.join(author_list)
except AttributeError:
pass
try:
if pd.isna(pub):
pub = soup.find('span', attrs={'class':'info_pub'}) \
.find('a') \
.get_text()
except AttributeError:
pass
try:
if year == -1:
year_str = soup.find('span', attrs={'class':'info_date'}) \
.get_text()
year = re.findall(r'\d{4}', year_str)[0]
except AttributeError:
pass
return title, author, pub, year
updated_sample = ns_book5[na_rows].head(2).apply(get_book_info,
axis=1, result_type ='expand')
updated_sample
beautifulsoup를 활용하여도 채우지 못한 데이터는 비정상 데이터로 간주하여 삭제해보자.
ns_book5 = ns_book5.astype({'발행년도': 'int32'})
ns_book6 = ns_book5.dropna(subset=['도서명','저자','출판사'])
ns_book6 = ns_book6[ns_book6['발행년도'] != -1]
ns_book6.head()
def data_fixing(ns_book4):
ns_book4 = ns_book4.astype({'도서권수':'int32', '대출건수': 'int32'})
set_isbn_na_rows = ns_book4['세트 ISBN'].isna()
ns_book4.loc[set_isbn_na_rows, '세트 ISBN'] = ''
ns_book5 = ns_book4.replace({'발행년도':'.*(\d{4}).*'}, r'\1', regex=True)
unkown_year = ns_book5['발행년도'].str.contains('\D', na=True)
ns_book5.loc[unkown_year, '발행년도'] = '-1'
ns_book5 = ns_book5.astype({'발행년도': 'int32'})
dangun_yy_rows = ns_book5['발행년도'].gt(4000)
ns_book5.loc[dangun_yy_rows, '발행년도'] = ns_book5.loc[dangun_yy_rows, '발행년도'] - 2333
dangun_year = ns_book5['발행년도'].gt(4000)
ns_book5.loc[dangun_year, '발행년도'] = -1
old_books = ns_book5['발행년도'].gt(0) & ns_book5['발행년도'].lt(1900)
ns_book5.loc[old_books, '발행년도'] = -1
na_rows = ns_book5['도서명'].isna() | ns_book5['저자'].isna() \
| ns_book5['출판사'].isna() | ns_book5['발행년도'].eq(-1)
updated_sample = ns_book5[na_rows].apply(get_book_info,
axis=1, result_type ='expand')
ns_book6 = ns_book5.dropna(subset=['도서명','저자','출판사'])
ns_book6 = ns_book6[ns_book6['발행년도'] != -1]
return ns_book6
데이터 분석 전 결과에 영향을 줄 수 있는 잘못된 데이터들을 처리하는 방법에 대해 알아보았다.
어느정도 정제되어 있는 데이터였기에 간단하게 처리 할 수 있었지만 러프하게 수집된 데이터들을 분석하기 위해서는 꽤 고단한 데이터 처리 작업이 필요할 것 같다.
데이터 분석 작업에서 가장 중요한 부분중 하나로 데이터 정제를 꼽는 이유를 체감할 수 있었다.