혼공분석 with 파이썬 공부

[혼공분석] with 파이썬 3주차 공부

hihi2468 2024. 7. 21. 10:44

3-1 판다스 이용하여 데이터 수정하기(=데이터 정제하기, 전처리)

import gdown
gdown.download('https://bit.ly/3RhoNho', 'ns_202104.csv',quiet=False)

import pandas as pd

ns_df= pd.read_csv('ns_202104.csv',low_memory = False)
ns_df.head()

1. 열 삭제하기


(1) 범위를 지정하는 **슬라이싱**으로 열삭제

(2) 범위 중간에 열만 제외하려면?  -> 불리언 배열

(3) **drop(삭제할열, axis = 1, inplace = True)** 메서드 이용하기

(4)판다스의 빈값인 NaN을 하나라도 포함하는 열 삭제 

.**dropna(axis = 1)** 이용 ( how = 'all' 추가하면 모든 값이 NaN인 열의 삭제)
(마찬가지로 inplace = True 하면 기존 데이터에 반영됨.)


ns_book = ns_df.loc[ :, '번호':'등록일자']
ns_book.head()

ns_df.columns !='Unnamed: 13'

ns_book = ns_df.loc[:,ns_df.columns!='Unnamed: 13']
ns_book.head()

#주제 분류번호를 삭제해보자.
ns_book.drop('주제분류번호',axis=1,inplace = True ) # inplace = True는 원본데이터에 바로 덮어쓰기
ns_book.head()

ns_book = ns_df.dropna(axis =1)
ns_book.head()

# 모든 값이 NaN인 열을 삭제하려면 -> dropna 옵션에 how = 'all' 추가하기
ns_book = ns_df.dropna(axis =1,how = 'all')
ns_book.head()

2. 행 삭제하기

(1) drop([삭제할 행 인덱스] -> axis =0 이 디폴트

(2) 데프 행을 슬라이싱할때는 loc 말고 df[:2],df[조건문]와 같이 바로 인덱스를 잡아도 됨.

(3) 조건에 맞는 행 선택(예.출판사가 한빛미디어인 행만 선택하기) -> 불리언배열

ns_book2 = ns_book.drop([0,1])
ns_book2.head()

ns_book2 = ns_book[2:]
ns_book2.head()

ns_book2 = ns_book[:2]
ns_book2.head()

# 출판사가 한빛미디어인 행만 선택하기 -> 불리언배열

selected_rows = ns_book.loc[:,'출판사'] == '한빛미디어'
print(selected_rows)
ns_book2 = ns_book[selected_rows]
ns_book2.head()

# 대출건수가 1,000건 이하인 행을 모두 삭제하기
selected_row = ns_book.loc[:,'대출건수'] >1000
ns_book2 = ns_book[selected_row]
ns_book2.head()

 3. 중복된 행 찾기

판다스 데이터프레임의 중복된 행은 **duplicated() 메서드**를 이용하여 찾을 수 있습니다.

중복된 행 중에서 처음 행을 제외한 나머지 행은 True로, 그외에 중복되지 않은 나머지 모든 행은 False로 표시합니다.

그래서 duplicated()를 적용하고 sum을 하면 중복된 행이 총 몇개 있는지 알수 있습니다.

print(ns_df.duplicated())
sum(ns_df.duplicated())

하지만 적용한 데이터 프레임 ns_book에는 고유번호가 있기 때문에 완전히 중복될 수가 없습니다.

만약 도서명, 저자, ISBN을 기준으로 중복된 행이 있는지 찾아보면 어떨까요?

-> dupicated의 subset 매개변수로 일부 열을 기준으로 중복이 되는 열을 찾을 수 있습니다.

sum(ns_book.duplicated(subset=['도서명','저자','ISBN']))
#중복이 2만건이 넘는다는 것을 알수 있다

어떤행이 중복되었는지 확인하려면> -> duplicated()에 keep = False로 !

dup_rows = ns_book.duplicated(subset = ['도서명','저자','ISBN'], keep = False)
print(dup_rows)

ns_book3 = ns_book[dup_rows]
ns_book3.head()

 


4. 그룹별로 모으기


-> gropby(by = [기준되는 열 즉 구분의 기준이되고 유지되는열], dropna = False)

-> 디폴트가 by에 지정된 열에 NaN이 포함되면 해당 열을 삭제함

-> dropna = False로 옵션 지정해야 삭제되지 않음!

count_df = ns_book[['도서명','저자','ISBN','권','대출건수']]
count_df.head()

대출건수_합한_df = count_df.groupby(by = ['도서명','저자','ISBN','권'], dropna = False).sum()
#by에 포함되지 않은 대출건수는 합쳐짐.
대출건수_합한_df.head()

 5. 원본 데이터 업데이트하기

1) duplicated()메소드로 중복된 행을 True로 표시한 불리언 배열을 만든다.
2) 1번에서 구한 불리언 배열을 반전시켜 중복되지 않은 고유한 열을 True로 만든다.
3) 2번에서 구한 불리언 배열을 사용해 원본 배열에서 고유한 행만 택한다.

4) set_index를 이용해 고유한 열을 찾을때 기준이 된 열을 맨 앞에 인덱스로 삼는다. -> 대출건수합과 맞추기 위해!
5) 대출건수를 합한 값을 고유행만 있는 df에 업데이트 한다.
6) set_index를 통한 인덱스 열을 해제하기 위해 reset_index()를 이용한다.

(주의! reset_index는 일시적이라, 새로 저장해야만 기록이 남는다.)

ns_book3 = ns_book[ns_df.duplicated(subset=['도서명','저자','ISBN','권']) == False].copy()
ns_book3.head()

print(ns_book3.duplicated(subset=['도서명','저자','ISBN','권']).sum())
ns_book3

ns_book3.set_index('저자')

ns_book3.set_index(['도서명','저자','ISBN','권'],inplace = True) # 원본 데이터에 반영
ns_book3.head()

ns_book3.update(대출건수_합한_df)
ns_book3.head()

ns_book4 = ns_book3.reset_index() # 리셋 인덱스는 임시적인것. 꼭 저장해주어야 함!!!!
ns_book4.head()

대출건수가 잘 합쳐졌는지 확인하기!

즉 ns_book과 ns_book4의 대출건수 비교하기

print(f'원본데이터에서 대출건수가 100건 이상 : {sum(ns_book.loc[:,"대출건수"]>100)}')
print(f'수정데이터에서 대출건수가 100건 이상 : {sum(ns_book4.loc[:,"대출건수"]>100)}')


원본데이터의 열 순서대로 맞추려면?

-> 수정데이터[원본데이터의열이름]

-> df_2 = df_2[df.columns]


ns_book4[['번호', '도서명', '저자', '출판사', '발행년도', 'ISBN', '세트 ISBN', '부가기호', '권',
       '주제분류번호', '도서권수', '대출건수', '등록일자']]
#즉
ns_book4 = ns_book4[ns_book.columns]


#파일로 저장하기
ns_book4.to_csv('ns_book4.csv',index = False)

6. 일괄처리함수

def data_cleaning(filename):
  """
  남산 도서관 장서 CSV 데이터 전처리 함수
  :param filename : CSV 파일이름
  """
  #파일을 데이터프레임으로 읽습니다.
  ns_df= pd.read_csv(filename, low_memory = False)
  # 항목이 모두 NaN인 열을 삭제합니다.
  ns_book = ns_df.dropna(axis = 1, how='all')
  #대출건수를 합치기 위해 필요한 데프 형태 유지하며 행만 추출합니다.
  ns_book2 = ns_book[['도서명','저자','ISBN','권','대출건수']]
  #여기서 기준 열의 값이 같은 행끼리 묶습니다. 그리고 제외된 열에 대해 더합니다.
  대출건수합 = ns_book2.groupby(by = ['도서명','저자','ISBN','권'], dropna = False).sum()
  #원본 데이터프레임에서 중복된 행을 제외하고 고유한 행만 추출하여 복사합니다.
  ns_book3 = ns_book[ns_book.duplicated(subset=['도서명','저자','ISBN','권']) == False].copy()
  #도서명, 저자, ISBN, 권을 인덱스로 설정합니다.
  ns_book3.set_index(['도서명','저자','ISBN','권'],inplace = True)
  #대출건수합에 저장된 누적 대출건수를 업데이트합니다.
  ns_book3.update(대출건수합)
  #인덱스를 원본과 같이 재설정합니다.
  ns_book4 = ns_book3.reset_index()
  ns_book4 = ns_book4[ns_book.columns]
  return ns_book4

new_ns_book4 = data_cleaning('ns_202104.csv')
#ns_book4.equals(new_ns_book4)

#저장하기
new_ns_book4.to_csv('ns_book4.csv')

 


3-2 잘못된 데이터 수정하기

 

 

#NaN값 확인하려면 -> info()메소드
import gdown
gdown.download('http://bit.ly/3GisL6J','ns_book4.csv', quiet = False)

import pandas as pd
ns_book4 = pd.read_csv('ns_book4.csv', low_memory = False)
ns_book4.head()

ns_book4.info()

1. 누락된 값 찾는 방법

(1) info() 메소드로 NaN이 아닌 행을 찾아 전체 행에서 뺄셈

RangeIndex: 384591

-> 전체 행의 수 나타냄

 도서명  384188 non-null  object

 -> NaN이 아닌 행은 384,188 개라는 의미

 -> 누락된 값은 384591 - 384188 = 403개

 이방법은 약간 번거로움



 (2) isna() 메소드 -> 행이 비어있다면 True를 반환 -> sum() 메소드로 합산

ns_book4.isna().sum()

## 2. 누락된 값으로 표시하기 : **None 으로 값 변경** 혹은 **np.nan**

예시) 누락값이 하나도 없는 도서권수 열에 첫번째 행 값을 None으로 변경하여 누락값 찾아보자

ns_book4.head(2)

#누락값으로 변경하기

ns_book4.loc[0,'도서권수'] = None
ns_book4['도서권수'].isna().sum()

ns_book4.head(2)

위에서 2번째 행의 도서 권수가 정수 1에서 실수 1.0으로 변경되었다. 이는 NaN을 특별한 실수값으로 인식하기 때문이다.

다시 정수형으로 변경해보자

즉 float64 -> int64로 데이터 타입을 변경한다.

-> 이를 위해 astype()메소드를 사용한다.

->astype()은 dict 형태로 대입

->astype({'변경할열':'데이터타입' , '변경할 열':'데이터타입'})

#NaN에서 1로 원상복구
ns_book4.loc[0,'도서권수'] = 1
#특정 열의 데이터 타입의 변경 -> astype{}
ns_book4 = ns_book4.astype({'도서권수':'int32','대출건수':'int32'})
ns_book4.head()

#문자열의 한 행에 None 대입
# 특정 행, 열의 값 하나 택할때도 loc 사용하기!!

ns_book4.loc[0,'부가기호'] = None
ns_book4.head(2)

NaN이 아닌 **문자열 그대로 None**이 대입되었다.

이것은 판다스가 NaN이라는 값을 따로 가지고 있지 않기 때문이다.

대신 넘파이 패키지의 **np.nan**을 대입해야 한다.

import numpy as np
ns_book4.loc[0,'부가기호'] = np.nan
ns_book4.head(2)
#해당값에 NaN이 삽입되었다.

 

 3. 누락된 값 바꾸기

(1) loc , fillna() 메소드

 1)누락된 값을 ' ' 이라는 빈 문자열로 변경하기 위해

-> isna()로 누락된 값을 찾아서 불리언배열로 만들고

-> loc으로 해당 값들에 ' '을 대입한다.

 2) fillna('누락값 대신 채울값')


*   


 (2) replace 메소드 이용하기

 -> replace('원래 값', '바꿀 값')

1) 여러개 일괄적으로 변경하려면
 * 리스트 형태 : replace( [ '원래 값1' ,'원래 값2' ] ,  [ '새로운 값1' , '새로운 값2' ])
 * 딕셔너리 형태 : replace({ 원래 값1 : 새로운값1 , 원래값2 : 새로운 값2 })


 2) 열마다 다른 값으로 바꿀때

->  replace({'열1:{'원래 값1' : '새로운 값1'}, '열2':{'원래 값2':'새로운 값2'}})



#loc을 이용한 누락값 채우기 예시. 세트 ISBN 열에 누락값을 빈 문자열로 채운다.
set_isbn_NaN_rows = ns_book4['세트 ISBN'].isna() # 누락값에 True 반환
ns_book4.loc[set_isbn_NaN_rows,'세트 ISBN'] = ''

#누락값 개수 확인
ns_book4['세트 ISBN'].isna().sum()

#fillna를 이용해서 데이터 프레임 전범위 누락값 채우기
#임시적 변환
ns_book4.fillna('없음').isna().sum()

#fillna 이용해서 특정 열의 누락값만 채우기
ns_book4['부가기호'].fillna('없음').isna().sum()
#혹은 딕셔너리 형태로 전달하여 바꾸기
ns_book4.fillna({'부가기호':'없음'}).isna().sum()

#(2) replace 메소드 이용해서 누락값 변경하기

ns_book4.replace(np.nan,'없음').isna().sum()

# 여러 항목을 동시에 변경할때
#np.nan을 '없음'으로, '2021'을 21로 변경하기
ns_book4.replace([np.nan,'2021'], ['없음', '21']).head(2)

# dictionary 형태로 여러 항목 동시 변경하기
ns_book4.replace({np.nan:'없음','2021':'21'}).head(2)

# 열마다 다른 값으로 바꿀때
#부가기호 열에서 누락값만 없음으로 바꿔보자
ns_book4.replace({'부가기호 ': np.nan},'없음').head(2)

#부가기호 열의 누락값을 없음으로, 발행년도 열의 2021을 21로 변경하기
ns_book4.replace({'부가기호':{np.nan : '없음'}, '발행년도':{'2021':'21'}}).head(2)

4. 정규표현식( = 정규식)

> 문자열 패턴을 찾아 대체하기 위한 규칙의 모음


예시. 위에서 발행년도 열의 일부를 2021에서 21로 변경했다. 그런데 2018인 항목과 같은 형식은 아니다. replace를 사용하여 2018를 변경하여도 되지만, 다른 항목들도 모두 개별적으로 변경하여야 해 번거롭다.

-> 이런 경우 정규 표현식을 이용한다.

(1) 숫자 찾기 : \d

-> 정규식에서 숫자를 나타내는 기호는 \d이다.

-> 네 자리 연도를 표현한다면 \d \d \d \d 이다.

-> 그리고 표현식을 그룹으로 묶으려면 소괄호를 사용한다.

-> 2021의 21 부분만 묶는다면 \d \d ( \d \d )

-> 만약 2021, A21, 21.9 의 문자열이 있다면 이중에 위 형식에 해당하는건 2021 하나뿐이다.

-> 정규표현식에서 변경시에는 replace()에서 regex() 매개변수를 True로 하여 이용한다.


* 위에서 정규식의 패턴 속 그룹 (\d\d) 같은 경우는 \1로, 다른 그룹이 이어진다면 \2 , 또 다른 그룹이 이어진다면 \3 ... 이렇게 순차적으로 표현한다.

* 정규식의 앞에 붙인 문자 r은 정규식을 다른 문자열과 구별하기 위한 것.
(접두사처럼 붙인다)

*  \d \d \d \d 처럼 같은 패턴이 반복된다면  \d{4}로 줄일수 있다.

(2) 문자 찾기 : 마침표 (  .  )

문자에 대응하는 표현은 마침표 (  .  )이고, 몇개의 문자가 올지 알수 없다면 * 사용하기

* 문자열 -> 정규 표현식에선 .* 으로 표현

* 공백은 \s

* ( 지은이 ) -> 정규식의 그룹과 구분하여 문자로서의 소괄호를 사용하려면 앞에 \ 붙이기 -> \( 지은이 \) ??????

예시. 로런스 인그래시아(지은이), 안기순 (옮긴이)를 정규식으로

->(.*)\s\(지은이\)(.*)\s\(옮긴이\)


# 발행년도의 열의 값을 정규 표현식으로 두 자리 연도로 변경하기
# 중괄호 주의!
ns_book4.replace({'발행년도': {r'\d\d(\d\d)':r'\1'}},regex = True)[100:102]
# 여기서 \1은 (\d\d)을 의미

ns_book4.replace({'발행년도':{r'\d{2}(\d{2})':r'\1'}}, regex = True)[100:102]

#(2) 문자 찾기 : 마침표 (  .  ) 적용하기 -> 저자 열에서 (지은이)와 (옮긴이) 문자열만 일괄적으로 삭제하기
ns_book4.replace({'저자':{r'(.*)\s\(지은이\)(.*)\s\(옮긴이\)':r'\1\2'},'발행년도':{r'\d{2}(\d{2})':r'\1'}},regex = True)[100:102]

## 5. 잘못된 값 바꾸기

#발행년도 열에서 문자열이 모두 정수로 구성된 것인지 확인하기
ns_book4.astype({'발행년도':'int64'})

발행년도 열에서 '1988.' 처럼 온전히 정수로만 구성되지 않은 항목도 있다.

그렇다면 1998을 포함하는 항목은 몇개인지 세보자.


-> contains( ) 메소드는 기본적으로 정규 표현식을 인식한다.

-> 정규 표현식 - 숫자 대응 : \d // 문자 대응 : \D

# 리스트 컴프리헨션으로 세기
sum(['1988' in str(i) for i in ns_book4['발행년도']])

# contain() 메소드로 세기
ns_book4['발행년도'].str.contains('1988').sum()

발행년도 열에서 **숫자가 아닌 문자**를 포함하는 행만 찾아보자.(누락값도 같이)

invalid_number = ns_book4['발행년도'].str.contains('\D', na = True)
# na = True는 누락값의 항목을 변경하는게 아닌 True로 표시하여 invalid_number에 반환함.

print(invalid_number)
print(f"문자가 들어간 열의 행의 수 : {invalid_number.sum()}")
ns_book4[invalid_number].head(8)

정규 표현식을 사용하여 연도 앞과 뒤에 있는 문자를 제외하기

ns_book5 = ns_book4.replace({'발행년도': {r'.*(\d{4}).*' : r'\1'}},regex = True)
unknown_year = ns_book5['발행년도'].str.contains('\D',na = True)
print(f"발행년도 알 수 없는 행 수 {unknown_year.sum()}")
ns_book5[unknown_year].head()

어떻게 변환할지 알수 없는 행들은 임의로 '-1' 값으로 변경한다.

-> 그다음 데이터 타입을 int64로 변경한다.

ns_book5.loc[unknown_year,'발행년도'] = -1
ns_book5 = ns_book5.astype({'발행년도':'int64'})

연도 중 이상하게 큰 값이나 너무 작은 값 있다.

-> 우선 연도가 4000을 넘는 값을 찾아보자.

ns_book5.loc[ns_book5['발행년도']> 4000].head(3)

sum(ns_book5['발행년도']> 4000)
# 혹은 gt() 이용하여 조건에 맞는 행 개수 세기

ns_book5['발행년도'].gt(4000).sum()

4,000년이 넘는 연도에서 2333을 빼서 서기로 바꾼 다음

-> 그래도 연도가 4,000년을 넘는 도서가 있는지 확인한다.

-> 해당 도서들의 발행년도를 모두 -1로 표기한다.

연도가 0보다 크고 1900년 이전의 도서도 찾는다.

#내가 생각한 방법
ns_book6 = ns_book5.loc[ns_book5['발행년도'].gt(4000)].apply({'발행년도':lambda x: x-2333})
ns_book6['발행년도'].gt(4000).sum()

#책에 나와 있는 방법
big_year_rows = ns_book5['발행년도'].gt(4000)
ns_book5.loc[big_year_rows, '발행년도'] = ns_book5.loc[big_year_rows, '발행년도'] -2333 # 서기로 바꾼다.
print(ns_book5['발행년도'].gt(4000).sum())
ns_book5[ns_book5['발행년도'].gt(4000)].head(2)

# 이런 도서들을 모두 -1로 표시하기
ns_book5.loc[ns_book5['발행년도'].gt(4000),'발행년도'] = -1

old_books = ns_book5['발행년도'].gt(0) & ns_book5['발행년도'].lt(1900)
ns_book5[old_books]

#모두 -1로 설정하기
ns_book5.loc[old_books,'발행년도'] = -1

#이제 발행년도 열에서 -1인 값의 수 세기
ns_book5['발행년도'].eq(-1).sum()

6. 누락된 정보 채우기

다음같은 경우의 수를 찾는다.

1) 도서명, 저자 출판사 열에 누락값 있음

2) 발행년도 열이 -1 임.

na_rows = ns_book5['도서명'].isna() | ns_book5['저자'].isna() |  ns_book5['출판사'].isna()  | ns_book5['발행년도'].eq(-1)
# or이 아닌 | 을 사용한다.
print(na_rows.sum())
ns_book5[na_rows].head(2)

이런 값들을 채우기 위해 뷰티플수프를 사용한다.

import requests
from bs4 import BeautifulSoup

#도서명 가져오기 위한 함수를 만든다
def get_book_title(isbn):
  url = 'https://www.yes24.com/Product/Search?domain=BOOK&query={}'
  r = requests.get(url.format(isbn))
  soup = BeautifulSoup(r.text, 'html.parser') # HTML 파싱
  # 클래스 이름이 gd_name 인 <a> 태그의 텍스트를 가져온다.
  title = soup.find('a', attrs = {'class' : 'gd_name'}).get_text()
  return title

get_book_title(9791191266054)

이제 다음과 같은 순서로 작업한다.

1) find_all() 메소드를 이용하여 저자를 담은 < a > 태그를 모두 추출한다.

2) 그리고 리스트 컴프리헨션 이용하여 < a >태그의 텍스트들을 합쳐준다.

3) 다음 추출한 결과를 join( ) 메소드를 이용하여 하나의 문자열로 합쳐준다.

4) 발행년도는 2020년 12월 처럼 쓰여져 있으므로 정규식을 이용하여 연도만 추출한다.

-> 이때 정규식을 지원하는 re 모듈의 findall() 함수를 사용하면 정규식에 매칭되는 모든 문자열을 리스트로 반환한다.

-> 연도는 4개의 숫자 -> r'\d{4}' 패턴으로 연도만 추출한다.



* Yes24 홈페이지에 도서정보가 없거나 HTML 요소가 누락된 경우 -> 오류발생

-> try ~ except 문으로 예외를 줌.


import re

def get_book_info(row):
    title = row['도서명']
    author = row['저자']
    pub = row['출판사']
    year = row['발행년도']
    # Yes24 도서 검색 페이지 URL
    url = 'http://www.yes24.com/Product/Search?domain=BOOK&query={}'
    # URL에 ISBN을 넣어 HTML 가져옵니다.
    r = requests.get(url.format(row['ISBN']))
    soup = BeautifulSoup(r.text, 'html.parser')   # HTML 파싱
    try:
        if pd.isna(title):
            # 클래스 이름이 'gd_name'인 a 태그의 텍스트를 가져옵니다.
            title = soup.find('a', attrs={'class':'gd_name'}) \
                    .get_text()
    except AttributeError:
        pass

    try:
        if pd.isna(author):
            # 클래스 이름이 'info_auth'인 span 태그 아래 a 태그의 텍스트를 가져옵니다.
            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):
            # 클래스 이름이 'info_auth'인 span 태그 아래 a 태그의 텍스트를 가져옵니다.
            pub = soup.find('span', attrs={'class':'info_pub'}) \
                      .find('a') \
                      .get_text()
    except AttributeError:
        pass

    try:
        if year == -1:
            # 클래스 이름이 'info_date'인 span 태그 아래 텍스트를 가져옵니다.
            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

해당 get_book_info 함수를 모든 행에 적용하면 너무 오랜 시간이 걸리므로
여기서는 미리 만들어둔 파일을 이용한다.

gdown.download('http://bit.ly/3UJZiHw', 'ns_book5_update.csv', quiet=False)

ns_book5_update = pd.read_csv('ns_book5_update.csv', index_col = 0)
ns_book5_update.head()

ns_book5.update(ns_book5_update)
na_rows = ns_book5['도서명'].isna() | ns_book5['저자'].isna() |  ns_book5['출판사'].isna()  | ns_book5['발행년도'].eq(-1)
# or이 아닌 | 을 사용한다.
print(na_rows.sum())
ns_book5[na_rows].head(2)

누락된 값이 있는 행은 4,615개로 뷰티플수프로 데이터를 채우기 전보다 653개 감소하였다.

-> 이제 마지막으로 누락된 값을 가진 행을 모두 삭제하여 분석에서 제외한다.


#발행년도의 데이터 타입을 정수형으로 변경하기
ns_book5 = ns_book5.astype({'발행년도': 'int32'})
# 도서명, 저자, 출판사 열에서 na가 1개라도 있는 행을 삭제
ns_book6 = ns_book5.dropna(subset = ['도서명','저자', '출판사'])
#발행년도에서 -1인 값을 모두 제외하기 -> 즉 -1인 값을 False로 하는 불리언 배열 만들어 적용하기
ns_book6 = ns_book6[ns_book6['발행년도'] != -1]
ns_book6.head()

# ns_book6 저장하기
ns_book6.to_csv('ns_book6.csv', index = False)

마지막으로 배운 내용으로 일괄처리 함수를 만든다.

def data_fixing(ns_book4):
    """
    잘못된 값을 수정하거나 NaN 값을 채우는 함수

    :param ns_book4: data_cleaning() 함수에서 전처리된 데이터프레임
    """
    # 도서권수와 대출건수를 int32로 바꿉니다.
    ns_book4 = ns_book4.astype({'도서권수':'int32', '대출건수': 'int32'})
    # NaN인 세트 ISBN을 빈문자열로 바꿉니다.
    set_isbn_na_rows = ns_book4['세트 ISBN'].isna()
    ns_book4.loc[set_isbn_na_rows, '세트 ISBN'] = ''

    # 발행년도 열에서 연도 네 자리를 추출하여 대체합니다. 나머지 발행년도는 -1로 바꿉니다.
    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'

    # 발행년도를 int32로 바꿉니다.
    ns_book5 = ns_book5.astype({'발행년도': 'int32'})
    # 4000년 이상인 경우 2333년을 뺍니다.
    dangun_yy_rows = ns_book5['발행년도'].gt(4000)
    ns_book5.loc[dangun_yy_rows, '발행년도'] = ns_book5.loc[dangun_yy_rows, '발행년도'] - 2333
    # 여전히 4000년 이상인 경우 -1로 바꿉니다.
    dangun_year = ns_book5['발행년도'].gt(4000)
    ns_book5.loc[dangun_year, '발행년도'] = -1
    # 0~1900년 사이의 발행년도는 -1로 바꿉니다.
    old_books = ns_book5['발행년도'].gt(0) & ns_book5['발행년도'].lt(1900)
    ns_book5.loc[old_books, '발행년도'] = -1

    # 도서명, 저자, 출판사가 NaN이거나 발행년도가 -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')
    updated_sample.columns = ['도서명','저자','출판사','발행년도']
    ns_book5.update(updated_sample)

    # 도서명, 저자, 출판사가 NaN이거나 발행년도가 -1인 행을 삭제합니다.
    ns_book6 = ns_book5.dropna(subset=['도서명','저자','출판사'])
    ns_book6 = ns_book6[ns_book6['발행년도'] != -1]

    return ns_book6

 

 

이렇게 해서 누락된 값에 값을 채우고 그렇지 못한 것은 정리하였다.

3_판다스_이용_데이터_수정(=_정제하기,_전처리),_불필요한_데이터_삭제.ipynb
0.67MB