Notice
Recent Posts
Recent Comments
Link
«   2025/09   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
Archives
Today
Total
관리 메뉴

yooonicode

python 데이터 분석 - ch 3 본문

데이터분석

python 데이터 분석 - ch 3

yooonii 2023. 9. 15. 16:02

✔️ CH3-1

지난 글에서도 사용한 남산도서관 책 목록을 가져옴

import gdown

df.loc로 슬라이싱하여 'Unnamed: 13' 열을 제외하여 ns_book에 저장함

ns_df.columns 중에서 Unnamed: 13과 다른 column들을 배열로 출력

selected_columns에 Unnamed: 13이 아닌 배열을 저장한 후,

ns_book에 loc 메서드 사용하여 리스트 슬라이싱 작업 진행

부가기호 컬럼을 제외하고 ns_book에 옮겨 담기

➡️ drop() 메서드 사용하여 Unnamed: 13 제거하기

axis 매개변수가 1이면 열을 삭제함, inplace=True는 열을 제거한 후 ns_book에 저장하게 함

➡️ dropna() 메서드의 사용, NaN이 하나 이상 포함된 행이나 열을 삭제하는데

how 매개변수를 이용하여 전체 행이 NaN인 열을 삭제하였음

 

인덱스 0부터 1까지를 삭제하였음 (첫 2개 행의 삭제)

[]연산자를 사용하여 슬라이싱이나 불리언 배열을 전달하면 행을 선택할 수 있음

loc 메서드에 불리언 배열을 이용하여 행을 선택할 수 있음

조건식으로 만든 불리언 배열으로 리스트 슬라이싱

➡️ duplicated() 메서드를 사용하여 중복된 행 찾기

중복된 행 중의 첫 번째 한 개만 False, 나머지는 True로 반환하는 배열을 만든다

+ 중복되지 않은 행은 False

 

sum(ns_book.duplicated())만 할 경우에는 yes24에서 자체 분류해둔 번호때문에 중복된 행이 0이라고 출력

이를 해결하기 위해서는 subset을 지정해서 기준 열을 나열해야 함

 

dup_rows 변수에 중복된 행을 표시한 불리언 배열을 저장한 후, ns_book에 전달하여 행을 선택함


➡️ groupby() 메서드

by 매개변수에는 합칠때 기준이 되는 열을 지정함

dropna = False > NaN이 있는 행도 연산에 포함하겠다는 의미

 

count_df로 보기 편하게 만들어준 이후, 대출 건수를 합쳐줌

같은코드, 한줄로


✔️ 대출건수를 더해주었으니, 원본 데이터를 업데이트하기

ns_book3를 만들어 준 후 다시 중복된 행이 있는지 확인해줌

loan_count에다 업데이트 할 것이기 때문에 도서명, 저자, ISBN, 권 행을 인덱스로 만들어줌

➡️ update() 메서드를 이용하여 다른 데이터 프레임을 이용하여 원본 데이터프레임의 값을 업데이트함

➡️ 업데이트 되었다면 인덱스열 해제, reset_index()

대출 건수가 100권 초과인 책의 개수가 증가하였음, 업데이트 된 것을 확인할 수 있음

하지만 초기 데이터프레임의 열과 ns_book4의 열 순서가 달라졌으므로 초기와 같이 변경해줌

저장하고 일괄처리함수 만들기

실행 결과 같은지 확인해줌


✔️ Ch 3-2

판다스의 누락된 값은 NaN으로 처리됨

데이터 불러와서

memory_usage = 'deep'이면 정확한 메모리 사용량 알 수 있음
도서권수에 누락된 값 없으므로 임의로 누락된 값, None으로 바꾼 후에 다시 합계 sum() 출력해봄

✔️ isna()를 이용해서 각 행이 비어있는지 나타내는 불리언 배열을 반환하고, sum을 이어서 호출하여 비어있는 행 개수를 얻을 수 있음

+ 누락되지 않은 값의 개수 파악에는 notna() 메서드를 사용함

첫번째열 도서권수 원상복구 해주고, 도서권수와 대출건수의 자료형을 정수형으로 변경해줌

NaN이 실수값으로 저장되어있어서 float64로 되어있었음

 ✔️ astype() 메서드를 이용하여 딕셔너리로 전달해줌

 

만약 부가기호를 None으로 바꾼다면 NaN이 아니라 None으로 표시됨

그래서, 넘파이에 있는 nan 값으로 변경해주어야함

+ 판다스에는 nan이라는 값이 따로 없음

 


⭐ 누락된 값 바꾸기 : loc, fillna()

sol1 ) loc를 이용해서 isbn이 누락된 열들을 전부 빈 문자열로 바꿔주고, 누락된 값이 몇 개인지 세어주었음

sol2 ) fillna를 이용해서 각 열의 nan을 '없음'으로 바꾸어줌, fillna는 새로운 데이터프레임을 반환하므로 isna()를 연결하여 개수까지 세어줌

 

특정 열만 지정해줄 수 있음

여기서의 차이점은 특정열 선택 이후 fillna하면 열 이름 제외한 개수만 주어지는 판다스 시리즈의 객체

두번째 줄은 딕셔너리 형태로 전달하여 전체 데이터프레임을 반환해줌

 

⭐ replace() 메서드 : NaN 이외에도 모두 바꿀 수 있음

replace(원래 값, 새로운 값)

- 바꾸려는 값이 여러개면 리스트 형식으로 지정, 혹은 딕셔너리 형식으로도 지정 가능

⬆️ 부가기호 열의 NaN을 없음으로 바꾸어줌 (열 자체를 바꿔줄 수도 있음)

열 2개짜리를 지정해서 바꿔주는 경우도 가능


⭐ 정규 표현식(정규식) ; 문자열 패턴을 찾아 대체하는 규칙 모음

2018년은 18로 바뀌지 않았음

✔️ 정규식에서 숫자를 나타내는 기호는 \d, \d\d\d\d는 네 자리에 해당

replace와 regex는 유사함, 대신에 정규식 쓸때만 regex

괄호로 묶어서 패턴 안의 첫 번째 그룹으로 연도를 바꿔줌

+ r은 정규 표현식을 구별하기 위한 접두사

중괄호를 사용하여 개수를 지정할 수도 있음

 

✔️ 문자 찾기

저자 열에, 지은이와 옮긴이를 삭제하려 한다면 이름의 글자수는 정확하게 알 수 없기 때문에

반복 개수를 지정할 수 없고, 마침표로 어떤 문자든지 대응하게끔 하고, *문자를 사용하여 0개 이상의 반복이라고 표시함

저자 이름을 괄호로 묶어두고, \s는 공백문자를 나타냄

괄호를 일반문자라고 인식하기 위해서 역슬래시를 앞에, 

r' ➡️ (.*) ➡️ \s ➡️ \(지은이\) ➡️  (.*) ➡️ \s ➡️ \(옮긴이\) : r 그룹1(.*: 저자 이름 따로 괄호로 묶어둠), 그룹2

 


✔️ 발행 연도 잘못된 것이 있는지 확인하려면

오류가 발생함

왜? '1988.'이라고 되어있는 연도때문에 오류가 발생함

'1988'을 포함하고 있는 열의 개수를 세어보면 contains()

만약 ns_book4[''] == '1988'이라 쓰면 같은 것만 찾아낼 수 있음

 

정규 표현식에서 숫자 한자리를 나타내는 \d,

문자 한자리를 나타내는 표현은 \D이다

 

문자열 포함하는 열의 개수와 열 5개정도 프린트해줌

정규 표현식을 이용해 연도 앞뒤의 글자를 제거하였음

제거 해줬는데도 아직 문자열인 열들을 추려내고

개수 센 이후 5개 출력하기

발행년도를 -1로 변경해준 이후에, 자료형을 정수로 변환해주었음

연도가 4000년 넘는 경우를 세주었더니

131개나 됨 (부등호로도 똑같이 출력할 수 있음)

(ns_book5['']>4000).sum()

 

단군기원을 사용했기 때문에 서기로 바꾼 다음, 오류값 여전히 남아있는지 확인해봄

여전히 이상한 친구들은 -1로 표기해줌

연도가 너무 작은 값들도 확인해주어야함 0이상, 1900 이전의 도서를 찾기 gt & lt

작은 친구들도 -1로 변경한다음에

-1의 값을 가지고 있는 행의 개수는 86개임을 알 수 있음

➡️ 이후, 발행 년도가 -1이거나 출판사, 도서명, 저자가 누락되었을 경우를 na_rows에 저장하고, 

na_rows의 개수를 출력함

\는 코드가 이어짐을 표시하기 위함

빈 값을 뷰티풀수프로 채워쥑로 

테스트

도서명을 추출했으니 저자, 출판사, 발행 연도를 추출하여 반환한다

단, 저자는 두 명 이상이므로 find_all을 이용해 저자를 담은 태그를 모두 추출하고, for 문으로 모든 텍스트를 파이썬 리스트에 저장한 이후, 추출한 결과를 join()을 이용하여 하나의 문자열로 합쳐줌

 

발행 연도는 n년 n월의 형식이기 때문에 연도만 추출해주며, 정규 표현식을 지원하는 re 모듈의 findall() 함수를 사용하여 원하는 모든 문자열을 찾아 리스트로 반환해준다. 첫 매개변수 = 원하는 정규식, 둘째 매개변수 = 검색 대상 문자열이므로, r\d{4} 패턴의 연도만 추출해줌

import re

def get_book_info(row):
    title = row['도서명']
    author = row['저자']
    pub = row['출판사']
    year = row['발행년도']
    # Yes24 도서 검색 페이지
    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

누락된 값에만 추출 값을 저장하고 추출할 수 없는 경우에는 오류가 발생하므로

try~except로 예외처리

 

샘플 만들어보면

처음 두 행에 get_book_info 실행시킨 이후

여러개의 값을 반환하게 되는데

apply 메서드는 반환 값을 하나의 튜플로 만들기 때문에 expand로 지정하여

각기 다른 열로 만들어준 이후 출력해주었음

 

업데이트 된 파일을 다운로드 하여 실행해줌

ns_book5를 ns_book5_update 데이터로 업데이트 한 후 누락된 행의 확인, 4615개

처음보다 650개 가량 줄었음을 확인 가능, 누락된 값은 삭제하여 분석 제외할 것

자료형 정수형으로 변경한 이후, 

dropna()를 이용하여 도서명, 저자, 출판사를 리스트로 지정한 후 누락된 값이 있는 행을 삭제하고,

발행년도 값이 -1이 아닌 행만 선택하여 새로운 데이터프레임을 생성해줌

 

⭐ 일괄 처리 함수

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

 

'데이터분석' 카테고리의 다른 글

python 데이터 분석 - ch 6  (0) 2023.09.22
python 데이터 분석 - ch 5  (0) 2023.09.22
python 데이터 분석 - ch 4  (1) 2023.09.15
python 데이터 분석 - ch1, 2  (1) 2023.09.09
빅데이터 응용 - 1  (0) 2023.09.07