혼자 공부하는 데이터 분석 with 파이썬 03-1 불필요한 데이터 삭제하기

손지호·2024년 1월 21일
0

데이터 정제(data cleaning) : 데이터에서 손상되거나 부정확한 부분 수정, 불필요한 데이터 삭제하거나 불완전한 값을 교체하는 등의 작업


열 삭제하기

gdown 패키지 사용해 남산도서관 데이터 다운로드.

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()
>>> 번호	도서명	저자	출판사	발행년도	ISBN	세트 ISBN	부가기호	권	주제분류번호	도서권수	대출건수	등록일자	Unnamed: 13
0	1	인공지능과 흙	김동훈 지음	민음사	2021	9788937444319	NaN	NaN	NaN	NaN	1	0	2021-03-19	NaN
1	2	가짜 행복 권하는 사회	김태형 지음	갈매나무	2021	9791190123969	NaN	NaN	NaN	NaN	1	0	2021-03-19	NaN
2	3	나도 한 문장 잘 쓰면 바랄 게 없겠네	김선영 지음	블랙피쉬	2021	9788968332982	NaN	NaN	NaN	NaN	1	0	2021-03-19	NaN
3	4	예루살렘 해변	이도 게펜 지음, 임재희 옮김	문학세계사	2021	9788970759906	NaN	NaN	NaN	NaN	1	0	2021-03-19	NaN
4	5	김성곤의 중국한시기행 : 장강·황하 편	김성곤 지음	김영사	2021	9788934990833	NaN	NaN	NaN	NaN	1	0	2021-03-19	NaN

여기서 마지막 'Unnamed: 13'열은 CSV 파일 각 라인의 끝에 콤마(,)가 있어서 판다스가 자동으로 추가한 것. 불필요한 열이므로 삭제!

ns_book = ns_df.loc[:, '번호':'등록일자'] 
# '번호' 열부터 '등록일자' 열까지 전체 행 선택.
ns_book.head()
>>> 번호	도서명	저자	출판사	발행년도	ISBN	세트 ISBN	부가기호	권	주제분류번호	도서권수	대출건수	등록일자
0	1	인공지능과 흙	김동훈 지음	민음사	2021	9788937444319	NaN	NaN	NaN	NaN	1	0	2021-03-19
1	2	가짜 행복 권하는 사회	김태형 지음	갈매나무	2021	9791190123969	NaN	NaN	NaN	NaN	1	0	2021-03-19
2	3	나도 한 문장 잘 쓰면 바랄 게 없겠네	김선영 지음	블랙피쉬	2021	9788968332982	NaN	NaN	NaN	NaN	1	0	2021-03-19
3	4	예루살렘 해변	이도 게펜 지음, 임재희 옮김	문학세계사	2021	9788970759906	NaN	NaN	NaN	NaN	1	0	2021-03-19
4	5	김성곤의 중국한시기행 : 장강·황하 편	김성곤 지음	김영사	2021	9788934990833	NaN	NaN	NaN	NaN	1	0	2021-03-19

하지만 여기에서 '부가기호' 열 제외하고 선택하기 위해 불리언 배열(boolean array)을 사용하면 쉽게 원하는 열만 선택할 수 있다!


loc 메서드와 불리언 배열

ns_df 데이터프레임은 13개의 열을 가지고 있기에 사용할 열을 불리언 배열로 나타내려면 일일이 13개의 불리언 값을 나열해 작성해야 한다.

# 데이터프레임 열 이름이 저장된 columns 속성 확인.
print(ns_df.columns)
>>> Index(['번호', '도서명', '저자', '출판사', '발행년도', 'ISBN', '세트 ISBN', '부가기호', '권',
       '주제분류번호', '도서권수', '대출건수', '등록일자', 'Unnamed: 13'],
      dtype='object')

columns 속성은 판다스의 Index 클래스 객체이다. 이 객체의 원소는 파이썬의 리스트처럼 숫자 인덱스로 참조할 수 있다.

print(ns_df.columns[0])
>>> 번호

Index 클래스를 비롯하여 판다스 배열 성격의 객체는 어떤 값과 비교할 때 자동으로 배열에 있는 모든 원소와 하나씩 비교해주는데, 이를 원소별 비교(element-wise comparison) 라고 한다.
[p157 그림]

# 원소별 비교 활용하여 ns_df.columns에서 Unnamed: 13 열 아닌 것을 표기하는 배열 만든다.
ns_df.columns != 'Unnamed: 13'
>>> array([ True,  True,  True,  True,  True,  True,  True,  True,  True,
        True,  True,  True,  True, False])

!=비교 연산자. columns 속성과 비교하여 'Unnamed: 13' 열이 아닌 것은 True가 되고 'Unnamed: 13' 열은 False가 된다. 이렇게 반환된 결과는 넘파이 배열(Numpy arrary).

# 위 배열을 selected_columns 변수에 저장하고 판다스 데이터프레임의 loc 메서드에 전달하여 True 열의 행만 선택.
selected_columns = ns_df.columns != 'Unnamed: 13'
ns_book = ns_df.loc[:, selected_columns] # True인 열의 모든 행을 선택.
ns_book.head()
>>> 번호	도서명	저자	출판사	발행년도	ISBN	세트 ISBN	부가기호	권	주제분류번호	도서권수	대출건수	등록일자
0	1	인공지능과 흙	김동훈 지음	민음사	2021	9788937444319	NaN	NaN	NaN	NaN	1	0	2021-03-19
1	2	가짜 행복 권하는 사회	김태형 지음	갈매나무	2021	9791190123969	NaN	NaN	NaN	NaN	1	0	2021-03-19
2	3	나도 한 문장 잘 쓰면 바랄 게 없겠네	김선영 지음	블랙피쉬	2021	9788968332982	NaN	NaN	NaN	NaN	1	0	2021-03-19
3	4	예루살렘 해변	이도 게펜 지음, 임재희 옮김	문학세계사	2021	9788970759906	NaN	NaN	NaN	NaN	1	0	2021-03-19
4	5	김성곤의 중국한시기행 : 장강·황하 편	김성곤 지음	김영사	2021	9788934990833	NaN	NaN	NaN	NaN	1	0	2021-03-19

# '부가기호' 열 제외.
selected_columns = ns_df.columns != '부가기호'
ns_book = ns_df.loc[:, selected_columns]
ns_book.head()
>>> 번호	도서명	저자	출판사	발행년도	ISBN	세트 ISBN	권	주제분류번호	도서권수	대출건수	등록일자	Unnamed: 13
0	1	인공지능과 흙	김동훈 지음	민음사	2021	9788937444319	NaN	NaN	NaN	1	0	2021-03-19	NaN
1	2	가짜 행복 권하는 사회	김태형 지음	갈매나무	2021	9791190123969	NaN	NaN	NaN	1	0	2021-03-19	NaN
2	3	나도 한 문장 잘 쓰면 바랄 게 없겠네	김선영 지음	블랙피쉬	2021	9788968332982	NaN	NaN	NaN	1	0	2021-03-19	NaN
3	4	예루살렘 해변	이도 게펜 지음, 임재희 옮김	문학세계사	2021	9788970759906	NaN	NaN	NaN	1	0	2021-03-19	NaN
4	5	김성곤의 중국한시기행 : 장강·황하 편	김성곤 지음	김영사	2021	9788934990833	NaN	NaN	NaN	1	0	2021-03-19	NaN

drop() 메서드

drop() 메서드 : 데이터프레임의 행이나 열을 삭제하는 메서드. 이 메서드로 열 삭제하려면 첫 번째 매개변수에 삭제하려는 열 이름 전달하고 axis 매개변수를 1로 지정.

ns_book = ns_df.drop('Unnamed: 13', axis=1)
# 'Unnamed: 13' : 삭제하려는 열 이름 / axis=1 : axis 매개변수에 1을 지정하면 열을 삭제.
ns_book.head()
>>> 번호	도서명	저자	출판사	발행년도	ISBN	세트 ISBN	부가기호	권	주제분류번호	도서권수	대출건수	등록일자
0	1	인공지능과 흙	김동훈 지음	민음사	2021	9788937444319	NaN	NaN	NaN	NaN	1	0	2021-03-19
1	2	가짜 행복 권하는 사회	김태형 지음	갈매나무	2021	9791190123969	NaN	NaN	NaN	NaN	1	0	2021-03-19
2	3	나도 한 문장 잘 쓰면 바랄 게 없겠네	김선영 지음	블랙피쉬	2021	9788968332982	NaN	NaN	NaN	NaN	1	0	2021-03-19
3	4	예루살렘 해변	이도 게펜 지음, 임재희 옮김	문학세계사	2021	9788970759906	NaN	NaN	NaN	NaN	1	0	2021-03-19
4	5	김성곤의 중국한시기행 : 장강·황하 편	김성곤 지음	김영사	2021	9788934990833	NaN	NaN	NaN	NaN	1	0	2021-03-19
# drop() 메서드로 삭제
ns_book = ns_df.drop(['부가기호','Unnamed: 13'], axis=1)
# '부가기호','Unnamed: 13' : 여러 개 열을 삭제하려면 리스트 형식을 사용.
ns_book.head()
>>> 번호	도서명	저자	출판사	발행년도	ISBN	세트 ISBN	권	주제분류번호	도서권수	대출건수	등록일자
0	1	인공지능과 흙	김동훈 지음	민음사	2021	9788937444319	NaN	NaN	NaN	1	0	2021-03-19
1	2	가짜 행복 권하는 사회	김태형 지음	갈매나무	2021	9791190123969	NaN	NaN	NaN	1	0	2021-03-19
2	3	나도 한 문장 잘 쓰면 바랄 게 없겠네	김선영 지음	블랙피쉬	2021	9788968332982	NaN	NaN	NaN	1	0	2021-03-19
3	4	예루살렘 해변	이도 게펜 지음, 임재희 옮김	문학세계사	2021	9788970759906	NaN	NaN	NaN	1	0	2021-03-19
4	5	김성곤의 중국한시기행 : 장강·황하 편	김성곤 지음	김영사	2021	9788934990833	NaN	NaN	NaN	1	0	2021-03-19

drop() 메서드에 inplace 매개변수를 True로 지정하면 현재 선택한 데이터프레임을 바로 수정할 수 있다.

ns_book.drop('주제분류번호', axis=1, inplace=True) # 선택한 데이터프레임에 덮어쓴다
ns_book.head()
>>> 번호	도서명	저자	출판사	발행년도	ISBN	세트 ISBN	권	도서권수	대출건수	등록일자
0	1	인공지능과 흙	김동훈 지음	민음사	2021	9788937444319	NaN	NaN	1	0	2021-03-19
1	2	가짜 행복 권하는 사회	김태형 지음	갈매나무	2021	9791190123969	NaN	NaN	1	0	2021-03-19
2	3	나도 한 문장 잘 쓰면 바랄 게 없겠네	김선영 지음	블랙피쉬	2021	9788968332982	NaN	NaN	1	0	2021-03-19
3	4	예루살렘 해변	이도 게펜 지음, 임재희 옮김	문학세계사	2021	9788970759906	NaN	NaN	1	0	2021-03-19
4	5	김성곤의 중국한시기행 : 장강·황하 편	김성곤 지음	김영사	2021	9788934990833	NaN	NaN	1	0	2021-03-19

dropna() 메서드

drop() 메서드와 비슷한 dropna() 메서드는 기본적으로 NaN이 하나 이상 포함된 행이나 열을 삭제한다.

ns_book = ns_df.dropna(axis=1)
ns_book.head()
>>> 번호	ISBN	도서권수	대출건수	등록일자
0	1	9788937444319	1	0	2021-03-19
1	2	9791190123969	1	0	2021-03-19
2	3	9788968332982	1	0	2021-03-19
3	4	9788970759906	1	0	2021-03-19
4	5	9788934990833	1	0	2021-03-19

모든 값이 NaN인 열을 삭제하려면 dropna() 메서드에 how 매개변수를 'all'로 지정하면 된다.

ns_book = ns_df.dropna(axis=1, how='all')
ns_book.head()
>>> 번호	도서명	저자	출판사	발행년도	ISBN	세트 ISBN	부가기호	권	주제분류번호	도서권수	대출건수	등록일자
0	1	인공지능과 흙	김동훈 지음	민음사	2021	9788937444319	NaN	NaN	NaN	NaN	1	0	2021-03-19
1	2	가짜 행복 권하는 사회	김태형 지음	갈매나무	2021	9791190123969	NaN	NaN	NaN	NaN	1	0	2021-03-19
2	3	나도 한 문장 잘 쓰면 바랄 게 없겠네	김선영 지음	블랙피쉬	2021	9788968332982	NaN	NaN	NaN	NaN	1	0	2021-03-19
3	4	예루살렘 해변	이도 게펜 지음, 임재희 옮김	문학세계사	2021	9788970759906	NaN	NaN	NaN	NaN	1	0	2021-03-19
4	5	김성곤의 중국한시기행 : 장강·황하 편	김성곤 지음	김영사	2021	9788934990833	NaN	NaN	NaN	NaN	1	0	2021-03-19
# dropna() 메서드도 inplace=True 지정하여 데이터프레임을 새로 생성하여 반환하지 않고 현재 데이터프레임을 수정할 수 있음.

loc 메서드로 필요한 열 선택하려면 슬라이스 연선자나 불리언 배열을 사용할 수 있다. drop() 메서드로 열 삭제하려면 axis=1과 삭제하려는 열 지정. 삭제할 열이 여러 개일 때는 열 이름을 리스트로 전달할 수 있다. 또는 dropna() 메서드에 axis=1을 지정하면 NaN이 들어있는 열을 삭제할 수 있다.


행 삭제하기

drop() 메서드 사용 가능. axis 매개변수를 0으로 지정하면 행을 삭제할 수 있지만, 기본값이 0이기 때문에 생략해도 됨!

ns_book2 = ns_book.drop([0,1]) # 인덱스 0부터 1까지 2개 행 선택
ns_book2.head()
>>> 번호	도서명	저자	출판사	발행년도	ISBN	세트 ISBN	부가기호	권	주제분류번호	도서권수	대출건수	등록일자
2	3	나도 한 문장 잘 쓰면 바랄 게 없겠네	김선영 지음	블랙피쉬	2021	9788968332982	NaN	NaN	NaN	NaN	1	0	2021-03-19
3	4	예루살렘 해변	이도 게펜 지음, 임재희 옮김	문학세계사	2021	9788970759906	NaN	NaN	NaN	NaN	1	0	2021-03-19
4	5	김성곤의 중국한시기행 : 장강·황하 편	김성곤 지음	김영사	2021	9788934990833	NaN	NaN	NaN	NaN	1	0	2021-03-19
5	6	처음 읽는 음식의 세계사	미야자키 마사카츠 지음, 한세희 옮김	탐나는책	2021	9791189550370	NaN	NaN	NaN	NaN	1	0	2021-03-19
6	7	아르센 벵거 자서전 My Life in Red and White	아르센 벵거 지음, 이성모 옮김	한즈미디어(한스미디어)	2021	9791160075793	NaN	NaN	NaN	NaN	1	0	2021-03-19

[] 연산자와 슬라이싱

[] 연산자 : 행 선택할 때 자주 사용.

# 슬라이싱 사용
ns_book2 = ns_book[2:]
ns_book2.head()
>>> 번호	도서명	저자	출판사	발행년도	ISBN	세트 ISBN	부가기호	권	주제분류번호	도서권수	대출건수	등록일자
2	3	나도 한 문장 잘 쓰면 바랄 게 없겠네	김선영 지음	블랙피쉬	2021	9788968332982	NaN	NaN	NaN	NaN	1	0	2021-03-19
3	4	예루살렘 해변	이도 게펜 지음, 임재희 옮김	문학세계사	2021	9788970759906	NaN	NaN	NaN	NaN	1	0	2021-03-19
4	5	김성곤의 중국한시기행 : 장강·황하 편	김성곤 지음	김영사	2021	9788934990833	NaN	NaN	NaN	NaN	1	0	2021-03-19
5	6	처음 읽는 음식의 세계사	미야자키 마사카츠 지음, 한세희 옮김	탐나는책	2021	9791189550370	NaN	NaN	NaN	NaN	1	0	2021-03-19
6	7	아르센 벵거 자서전 My Life in Red and White	아르센 벵거 지음, 이성모 옮김	한즈미디어(한스미디어)	2021	9791160075793	NaN	NaN	NaN	NaN	1	0	2021-03-19

[] 연산자에 슬라이싱을 사용하면 loc 메서드에 슬라이싱을 사용하는 것과 다르게 파이썬의 슬라이싱처럼 마지막 인덱스를 포함하지 않는다.

ns_book2 = ns_book[0:2] # 인덱스 0과 1 선택. 2 포함X.
ns_book2.head()
>>> 번호	도서명	저자	출판사	발행년도	ISBN	세트 ISBN	부가기호	권	주제분류번호	도서권수	대출건수	등록일자
0	1	인공지능과 흙	김동훈 지음	민음사	2021	9788937444319	NaN	NaN	NaN	NaN	1	0	2021-03-19
1	2	가짜 행복 권하는 사회	김태형 지음	갈매나무	2021	9791190123969	NaN	NaN	NaN	NaN	1	0	2021-03-19

[] 연산자와 불리언 배열

불리언 배열 사용해서 행 선택도 가능. 비교 연산자 활용해 원하는 행은 Ture로 표시하고 제외할 행은 False로 표시한 불리언 배열을 만들어 사용함.

selected_rows = ns_df['출판사'] == '한빛미디어'
ns_book2 = ns_book[selected_rows]
ns_book2.head()
>>> 번호	도서명	저자	출판사	발행년도	ISBN	세트 ISBN	부가기호	권	주제분류번호	도서권수	대출건수	등록일자
60	61	(맛있는 디자인)프리미어 프로 CC: 쉽게 배워 제대로 써먹는 유튜브 영상 편집	정지원,심수진,윤성우,김덕영 지음	한빛미디어	2021	9791162244029	NaN	1	2021	005.567	1	1	2021-03-15
70	71	처음 배우는 애저 (Azure Portal로 배우는 애저 도입부터 활용까지)	김도균	한빛미디어	2020	9791162243695	NaN	NaN	NaN	005.74	1	1	2021-03-15
88	89	맛있는 디자인 프리미어 프로 CC 2021 - 쉽게 배워 제대로 써먹는 유튜브 영상 편집	정지원, 심수진, 윤성우, 김덕영 (지은이)	한빛미디어	2021	9791162244029	NaN	NaN	NaN	NaN	0	0	2021-03-15
156	157	실전 보고서 작성 기술 with 파워포인트, 워드, 한글	홍장표 지음	한빛미디어	2020	9791162243763	NaN	NaN	NaN	NaN	1	0	2021-03-12
198	199	처음 배우는 리액트 네이티브	김범준 지음	한빛미디어	2021	9791162243879	NaN	NaN	NaN	NaN	1	0	2021-03-12

+ loc 메서드에 불리언 배열을 사용하여 행 선택할 수 있나요?
사용할 수 있다! 다음과 같이 loc 메서드에 불리언 배열 넣으면 출판사가 '한빛미디어'인 모든 행 선택한다.

추가로 대출건수가 1,000 이하인 행 모두 삭제하고 싶다면 반대로 ns_book['대출건수']>1000와 같이 조건을 넣어 대출건수가 1,000이 넘는 행을 선택하면 된다. 앞에서는 불리언 배열을 변수에 저장하여 사용했지만, 실제로 많은 판다스 사용자들은 불리언 배열을 만드는 조건을 []연산자에 바로 넣는다. 따라서 다음 코드처럼 selected_rows 변수를 만들지 않고 조선 직접 넣어 사용해보자.

ns_book2 = ns_book[ns_book['대출건수'] > 1000]
ns_book2.head()
>>> 번호	도서명	저자	출판사	발행년도	ISBN	세트 ISBN	부가기호	권	주제분류번호	도서권수	대출건수	등록일자
94781	94782	사피엔스 :유인원에서 사이보그까지, 인간 역사의 대담하고 위대한 질문	유발 하라리 지음 ;조현욱 옮김	김영사	2016	9788934972464	NaN	NaN	NaN	909	30	1468	2016-04-22
346944	346945	해커스 토익:Listening	David Cho 지음	해커스어학연구소	2005	9788990700148	NaN	1	NaN	740.77	29	1065	2005-02-01

1,000회 이상 대출된 도서는 두 권뿐.
열 삭제할 때와 마찬가지로 행 삭제할 때도 loc 메서드, drop() 메서드, dropna() 메서드 모두 사용할 수 있지만, 불리언 배열을 [] 연산자에 전달하는 방법 자주 사용한다.


중복된 행 찾기

duplicate() 메서드 : 중복된 행 검사할 수 있음. 중복된 행 중에서 처음 행 제외한 나머지 행은 True로, 그 외 중복되지 않은 나머지 모든 행은 False로 표시한 불리언 배열을 반환한다. 기본적으로 데이터프레임에 있는 모든 열 기준으로 중복된 행 찾는다.

# ns_book 데이터프레임에 중복된 행이 있는지 확인
sum(ns_book.duplicated())
>>> 0

# True로 표시된 행이 하나도 없음! 중복된 행 없다는 뜻.

ns_book 데이터프레임의 모든 행은 '번호' 열에 고유한 값 가지고 있기 떄문에 중복된 행이 나올 수가 없다. 만약 '도서명', '저자', 'ISBN'을 기준으로 중복된 행이 있는지 찾으려면 duplicated() 메서드의 subset 매개변수에 기준열을 나열한다.

sum(ns_book.duplicated(subset=['도서명','저자','ISBN']))
>>> 22096

이제 '도서명', '저자', 'ISBN' 기준으로 어떤 데이터가 중복되어 있는지 확인해보자. duplicated() 메서드에 keep 매개변수를 False로 지정하여 중복된 모든 행을 True로 표시한다.

dup_rows = ns_book.duplicated(subset=['도서명','저자','ISBN'], keep=False)
# keep=False : 중복된 행을 모두 True로 표시한 불리언 배열을 반환한다.
ns_book3 = ns_book[dup_rows]
ns_book3.head()
>>> 번호	도서명	저자	출판사	발행년도	ISBN	세트 ISBN	부가기호	권	주제분류번호	도서권수	대출건수	등록일자
109	110	파친코	이민진 지음 ;이미정 옮김	문학사상	2018	9788970129815	9788970129808	0	1	843.6	1	0	2021-03-12
110	111	파친코	이민진 지음 ;이미정 옮김	문학사상	2018	9788970129822	9788970129808	0	2	843.6	1	0	2021-03-12
111	112	보건교사 안은영 :정세랑 장편소설	지은이: 정세랑	민음사	2021	9788937479953	NaN	0	NaN	813.7	1	0	2021-03-12
112	113	보건교사 안은영 :정세랑 장편소설	지은이: 정세랑	민음사	2021	9788937479953	NaN	0	NaN	813.7	1	1	2021-03-12
113	114	스토너	존 윌리엄스 지음 ;김승욱 옮김	RHK(알에이치코리아)	2021	9788925538297	NaN	0	NaN	843.5	1	0	2021-03-12

그룹별로 모으기

앞으로 어떤 도서가 인기 있을지 예상하려고 하기 때문에 이 데이터프레임에서 '대출건수'열이 중요하다. 따라서 같은 도서의 대출건수는 하나로 합치는게 좋다.
groupby() 메서드 사용. 여기서 by 매개변수에는 행 합칠 때 기준이 되는 열 지정함. '도서명', '발행연도', '대출건수' 열을 기준으로 행 합치면 다음과 같다.
[p171 그림]
+ 중복된 행을 합치지 않고 그냥 삭제하려면 어떻게 해야하나요?
drop_duplicated() 메서드를 사용해 중복된 행 삭제할 수 있다. 이 메서드는 duplicated() 메서드와 동일하게 subset, keep 매개변수를 제공하며 현재 데이터프레임을 수정하기 위해 inplace 매개변수가 있다.

# 그룹으로 묶을 기준 열과 '대출건수' 열만 선택하여 사용
count_df = ns_book[['도서명','저자','ISBN','권','대출건수']]

그 다음 결과를 변환한 count_df 데이터프레임에 groupby() 메서드를 적용한다. 같은 책의 대출건수는 하나로 합쳐야 하므로 sum() 메서드를 사용한다.
groupby() 메서드는 기본적으로 by 매개변수에 지정된 열에 NaN이 포함되어 있으면 해당 행을 삭제한다. count_df 데이터프레임의 '도서명'이나 '저자', '권' 열에는 값이 누락되어 이따금 NaN이 포함되어 있다. NaN이 포함되어 있는 행을 삭제하고 계산하면 대출건수 합계에서 빠지기 때문에 이를 막기 위해 dropna 매개변수를 False로 지정한다. 이는 연산할 때 NaN이 있는 행도 포함하겠다는 의미.

group_df = count_df.groupby(by=['도서명','저자','ISBN','권'], dropna=False)
# 'dropna=False' : NaN이 있는 행을 삭제하지 않는다.
loan_count = group_df.sum()

# 위처럼 따로 해도 좋지만, 두 메서드를 연이어 호출하는 것을 선호한다.
loan_count = count_df.groupby(by=['도서명','저자','ISBN','권'], dropna=False).sum()
loan_count.head()
>>> 				대출건수
도서명	저자	ISBN	권	
(꼭 필요한 것부터 쉽게 배우는) 자신만만 블로그 차근차근 배우기	김상현 지음	9788955025637	NaN	38
(맨처음 배우는) 세상의 직업	엘레오노라 바르소티 글 ·그림 ;김태은 옮김	9788992924146	NaN	10
(영잘원 리스닝과 패턴 영어의 절묘한 만남으로 태어난 ) 리스닝 ABC : 입문편	JD Kim 지음	9788993466089	NaN	4
(즉석에서 바로바로 활용하는) 일상생활 베트남어 첫걸음	FL4U컨텐츠 지음	9788971728000	NaN	3
,에게	이기린(이진희)	9791196137014	NaN	0

loan_count 데이터프레임은 '도서명', '저자', 'ISBN', '권'까지 네 개의 열이 인덱스이고 각 책의 대출건수를 더한 결과가 '대출건수' 열에 저장된 것을 알 수 있다.


원본 데이터 업데이트하기

대출건수를 원본 데이터프레임에 업데이트를 할건데, 원본 데이터프레임에는 중복된 데이터가 있다. 따라서 더해진 대출건수를 업데이트하기 전에 다음과 같은 과정을 거처야 한다.

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

이 과정을 코드로 살펴보자!
중복된 행을 True로 표시한 불리언 배열을 반전시킬 때는 판다스의 ~ 연산자를 사용한다. 그 다음 원본 배열에서 고유한 배열 선택하여 copy() 메서드로 ns_book3 데이터프레임을 만든다.

dup_rows = ns_book.duplicated(subset=['도서명','저자','ISBN','권']) → ① 중복된 행을 True로 표시
unique_rows = ~dup_rows → ② 불리언 배열을 반전시켜 고유한 행을 True로 표시함.
ns_book3 = ns_book[unique_rows].copy() → ③ 고유한 행만 선택.

+ copy() 메서드는 왜 사용하나요??
copy() 메서드는 데이터프레임의 복사본을 만든다. unique_rows 배열로 일부 행을 선택하여 만든 ns_book3의 '대출건수' 열을 업데이트해야 하기 때문이다. 판다스는 copy() 메서드를 사용하지 않으면 ns_book3 데이터프레임이 별도의 메모리 공간에 저장되는지 보장하지 않는다. 따라서 명시적으로 복사하지 않고 '대출건수' 열을 업데이트하면 ns_book의 데이터가 바뀔 수도 있다. 이런 이유로 판다스에서는 일부 행이나 열을 선택하여 데이터를 업데이트 할 때는 항상 복사하는 것이 좋다.

# ns_book3에 중복된 행 없는지 duplicated() 메서드로 확인.
sum(ns_book3.duplicated(subset=['도서명','저자','ISBN','권']))
>>> 0

# 중복된 행  X!

원본 데이터프레임 인덱스 설정하기

지정한 열을 인덱스로 설정할 때는 set_index() 메서드를 사용한다. 이때 inplace 매개변수를 True로 지정해 새로운 데이터프레임을 반환하지 않고 ns_book3 데이터프레임을 수정하자. 인덱스가 변경된 ns_book3 데이터프레임의 처음 다섯 개 행을 출력해보자.

ns_book3.set_index(['도서명','저자','ISBN','권'], inplace=True)
ns_book3.head()
>>> 번호	출판사	발행년도	세트 ISBN	부가기호	주제분류번호	도서권수	대출건수	등록일자
도서명	저자	ISBN	권									
인공지능과 흙	김동훈 지음	9788937444319	NaN	1	민음사	2021	NaN	NaN	NaN	1	0	2021-03-19
가짜 행복 권하는 사회	김태형 지음	9791190123969	NaN	2	갈매나무	2021	NaN	NaN	NaN	1	0	2021-03-19
나도 한 문장 잘 쓰면 바랄 게 없겠네	김선영 지음	9788968332982	NaN	3	블랙피쉬	2021	NaN	NaN	NaN	1	0	2021-03-19
예루살렘 해변	이도 게펜 지음, 임재희 옮김	9788970759906	NaN	4	문학세계사	2021	NaN	NaN	NaN	1	0	2021-03-19
김성곤의 중국한시기행 : 장강·황하 편	김성곤 지음	9788934990833	NaN	5	김영사	2021	NaN	NaN	NaN	1	0	2021-03-19

'도서명', '저자', 'ISBN', '권'열이 인덱스가 되었다. 인덱스 맞추었기 때문에 이제 ns_book3 데이터프레임의 '대출건수' 열을 loan_count 데이터프레임의 '대출건수' 열로 업데이트하는 것은 아주 간단하다.


업데이트하기: update() 메서드

# 다른 데이터프레임을 사용해 원본 데이터프레임의 값 업데이트할 때는 update() 메서드를 사용한다.
ns_book3.update(loan_count)
ns_book3.head()
>>> 				번호	출판사	발행년도	세트 ISBN	부가기호	주제분류번호	도서권수	대출건수	등록일자
도서명	저자	ISBN	권									
인공지능과 흙	김동훈 지음	9788937444319	NaN	1	민음사	2021	NaN	NaN	NaN	1	0.0	2021-03-19
가짜 행복 권하는 사회	김태형 지음	9791190123969	NaN	2	갈매나무	2021	NaN	NaN	NaN	1	0.0	2021-03-19
나도 한 문장 잘 쓰면 바랄 게 없겠네	김선영 지음	9788968332982	NaN	3	블랙피쉬	2021	NaN	NaN	NaN	1	0.0	2021-03-19
예루살렘 해변	이도 게펜 지음, 임재희 옮김	9788970759906	NaN	4	문학세계사	2021	NaN	NaN	NaN	1	0.0	2021-03-19
김성곤의 중국한시기행 : 장강·황하 편	김성곤 지음	9788934990833	NaN	5	김영사	2021	NaN	NaN	NaN	1	0.0	2021-03-19

'도서명', '저자', 'ISBN', '권'이 모두 인덱스 열.
이걸 해제한다.

ns_book4 = ns_book3.reset_index()
ns_book4.head()
>>> 도서명	저자	ISBN	권	번호	출판사	발행년도	세트 ISBN	부가기호	주제분류번호	도서권수	대출건수	등록일자
0	인공지능과 흙	김동훈 지음	9788937444319	NaN	1	민음사	2021	NaN	NaN	NaN	1	0.0	2021-03-19
1	가짜 행복 권하는 사회	김태형 지음	9791190123969	NaN	2	갈매나무	2021	NaN	NaN	NaN	1	0.0	2021-03-19
2	나도 한 문장 잘 쓰면 바랄 게 없겠네	김선영 지음	9788968332982	NaN	3	블랙피쉬	2021	NaN	NaN	NaN	1	0.0	2021-03-19
3	예루살렘 해변	이도 게펜 지음, 임재희 옮김	9788970759906	NaN	4	문학세계사	2021	NaN	NaN	NaN	1	0.0	2021-03-19
4	김성곤의 중국한시기행 : 장강·황하 편	김성곤 지음	9788934990833	NaN	5	김영사	2021	NaN	NaN	NaN	1	0.0	2021-03-19
# 대출건수가 잘 합쳐졌는지 간단히 확인.
# 먼저 원본 데이터프레임 ns_book에서 대출건수가 100회 이상인 책의 개수를 세어보고, 앞에서 배웠던 것처럼 비교 연산자를 사용하고 sum() 함수로 True 원소의 개수를 센다.
sum(ns_book['대출건수']>100)
>>> 2311

# 새로 만든 ns_book4 데이터프레임에서 대출건수가 100회 이상인 책의 개수도 세어본다.
sum(ns_book4['대출건수']>100)
>>> 2550

대출건수가 100회 이상인 책이 훨씬 늘어났다. 이는 중복된 도서의 대출건수를 합쳤기 때문!

그러나 여러 개의 열을 사용해 데이터프레임의 인덱스를 만들었다가 다시 해제했기 때문에 초기 데이터프레임의 열과 ns_book4 데이터프레임의 열 순서가 달라졌다. 데이터 분석을 수행할 때 열의 순서가 문제가 되지는 않지만, 앞으로 작업을 진행할 때 혼동하지 않도록 원래 열의 순서대로 맞추자.
열의 순서를 바꾸는 가장 간단한 방법은 [] 연산자에 원하는 열 이름을 순서대로 전달하는 것.

ns_book4 = ns_book4[ns_book.columns] → ns_book 데이터프레임의 열 이름을 전단한다.
ns_book4.head()
>>> 번호	도서명	저자	출판사	발행년도	ISBN	세트 ISBN	부가기호	권	주제분류번호	도서권수	대출건수	등록일자
0	1	인공지능과 흙	김동훈 지음	민음사	2021	9788937444319	NaN	NaN	NaN	NaN	1	0.0	2021-03-19
1	2	가짜 행복 권하는 사회	김태형 지음	갈매나무	2021	9791190123969	NaN	NaN	NaN	NaN	1	0.0	2021-03-19
2	3	나도 한 문장 잘 쓰면 바랄 게 없겠네	김선영 지음	블랙피쉬	2021	9788968332982	NaN	NaN	NaN	NaN	1	0.0	2021-03-19
3	4	예루살렘 해변	이도 게펜 지음, 임재희 옮김	문학세계사	2021	9788970759906	NaN	NaN	NaN	NaN	1	0.0	2021-03-19
4	5	김성곤의 중국한시기행 : 장강·황하 편	김성곤 지음	김영사	2021	9788934990833	NaN	NaN	NaN	NaN	1	0.0	2021-03-19
# 지금까지 불필요한 데이터 제거하여 만든 ns_book4 데이터프레임을 다음 작업을 위해 저장한다.
ns_book4.to_csv('ns_book4.csv', index=False)

일괄 처리 함수 만들기

열 삭제 방법으로 loc 메서드슬라이싱이나 불리언 배열 적용했고 drop() 메서드를 사용.
행 삭제할 때는 주로 [] 연산자슬라이싱 또는 불리언 배열 사용.
마지막으로 중복된 행 찾는 방법으론 groupby() 메서드sum() 메서드를 사용 후, 원본 데이터프레임에 업데이트하기 위해 update() 메서드 사용. 이떄 인덱스 기준으로 업데이트하기 위해 set_index() 메서드reset_index() 메서드 사용.


일괄 처리 함수

불필요한 행과 열을 제거하기 위해 작성했던 코드를 새로운 데이터에 적용하기 쉽도록 일괄처리하는 data_cleaning() 이라는 함수를 만들어 본다.

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')
    
    # 대출건수를 합치기 위해 필요한 행만 추출하여 count_df 데이터프레임 만든다.
    count_df = ns_book[['도서명', '저자', 'ISBN', '권', '대출건수']]
    
    # 도서명, 저자, ISBN, 권을 기준으로 대출건수를 groupby 함.
    loan_count = count_df.groupby(by=['도서명', '저자', 'ISBN', '권'], 도서며dropna=False).sum()
    
    # 원본 데이터프레임에서 중복된 행을 제외하고 고유한 행만 추출하여 복사.
    dup_rows = ns_book.duplicated(subset=['도서명', '저자', 'ISBN', '권'])
    unique_rows = ~dup_rows
    ns_book3 = ns_book[unique_rows].copy()
    
    # 도서명, 저자, ISBN, 권을 인덱스로 설정
    ns_book3.set_index(['도서명', '저자', 'ISBN', '권'], inplace=True)
    
    # loan_count에 저장된 누적 대출건수를 업데이트.
    ns_book3.update(loan_count)
    
    # 인덱스 재설정
    ns_book4 = ns_boo3.reset_index()
    
    # 원본 데이터프레임의 열 순서로 변경
    ns_book4 = ns_book4[ns_book.columns]
    return ns_book4

# 이 함수 제대로 작동하는지 확인.
# 다른 데이터프레임을 비교할 때는 **equals() 메서드** 사용
new_ns_book4 = data.cleaning('ns_202104.csv')
ns_book4.equals(new_ns_book4)
>>> True

두 데이터프레임 완전히 동일! data_cleanin() 함수가 앞서 수행했던 모든 작업 동일하게 수행한다는 것을 보장함. 언제라도 새로운 장서 데이터가 도착하면 이 함수를 수행해서 정제 작업을 간단히 수행할 수 있음!


정리

  • 데이터 정제는 수집된 데이터에서 잘못된 부분을 고치거나 제거하여 필요한 데이터를 준비하는 과정. 데이터가 올바르게 정제되지 못하면 분석된 결과를 왜곡시킬 수 있으며 잘못된 의사 결정을 초래하기도 한다.
  • 데이터를 정제하는 과정과 데이터 분석 및 머신러닝에 적합한 형태로 데이터를 변환하는 과정을 합쳐서 데이터 랭글링(혹은 데이너 먼징)이라고 한다.
  • 판다스의 데이터프레임과 인덱스를 하나의 값과 비교하면 데이터프레임과 인덱스에 있는 모든 원소와 비교한다. 이를 원소별 비교라고 부른다. 비교한 결과는 True 또는 False로 이루어진 불리언 배열로 반환된다.
  • 넘파이는 파이썬의 대표적인 다차원 배열. 판다스의 데이터프레임과 달리 한 종류의 데이터만 담을 수 있지만 매우 효율적이고 성능이 높다. 파이썬의 다른 과학 패키지와 호환성이 높으며 기본 데이터 구조로 널리 사용된다.

핵심 함수와 메서드 정리

DataFrame.drop() : 데이터프레임의 행이나 열을 삭제한다.
DataFrame.dropna() : 누락된 값이 포함된 행이나 열을 삭제한다.
DataFrame.duplicated() : 중복된 행을 찾아 불리언 값으로 표시한 배열을 반환한다.
DataFrame.groupby() : 데이터프레임의 행을 그룹으로 모은다.
DataFrame.sum() : 행 또는 열을 기준으로 합계를 계산함.
DataFrame.set_index() : 지정한 열을 인덱스로 설정.
DataFrame.reset_index() : 데이터프레임의 인덱스를 재설정.
DataFrame.update() : 다른 데이터프레임을 사용해 원본 데이터프레임의 값을 업데이트한다. 다른 데이터프레임에 있는 NaN은 업데이트에서 제외.
DataFrame.equals() : 다른 데이터프레임과 동일한 원소를 가졌는지 비교. 두 데이터프레임이 동일하면 True, 그렇지 않으면 False를 반환.

profile
초보 중의 초보. 열심히 하고자 하는 햄스터!

0개의 댓글