본문 바로가기
혼공학습단9기

혼자 공부하는 데이터 분석 with 파이썬: 3주차(Chapter 03-1)

by 이두스 2023. 1. 25.

Chapter03 "데이터 정제하기" 

 

데이터 정제 데이터에서 손상되거나 부정확한 부분을 수정하고, 불필요한 데이터를 삭제하거나 불완전한 값을 교체하는 등의 작업이다.

 

Chapter 03도 2개로 나누어 글을 작성하려 한다.

Chapter 03-1의 주제는 '불필요한 데이터 삭제하기' 이다.

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

열 삭제하기

먼저 남산도서관 데이터를 가져온다.

# 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()

실행결과

보면 맨 오른쪽 열이 'Unnamed: 13'이다. csv파일 각 라인 끝에 콤마(,)가 있어서 판다스가 자동으로 추가한 것이다.

불필요하기 때문에 삭제한다.

# 2주차에서 배운 loc메서드에 슬라이싱 사용
ns_book = ns_df.loc[:,'번호':'등록일자']
ns_book.head()

loc 메서드와 불리언 배열

# 데이터프레임의 열 이름이 저장된 columns 속성
print(ns_df.columns)
  • columns 속성은 판다스의 Index 클래스 객체
  • 객체의 원소는 숫자 인덱스로 참조 가능
# 숫자 인덱스
print(ns_df.columns[0])
결과는 '번호'이다.
Index 클래스를 비롯해 판다스 배열 성격의 객체는 어떤 값과 비교할 때 배열의 원소와 하나씩 비교해주는 원소별 비교를 한다.
 
원소별 비교를 하면 다음과 같다.
# 원소별 비교 'Unnamed: 13'열이 아닌 배열 생성
ns_df.columns != 'Unnamed: 13'

실행결과, 넘파이 배열임

이를 이용해 전의 슬라이싱을 이용하지 않고 열을 제거할 수 있다.

# 원소별 비교로 열 제거
selected_columns = ns_df.columns !='Unnamed: 13'
ns_book = ns_df.loc[:, selected_columns]
ns_book.head()

실행결과

drop() 메서드

  • 데이터프레임의 행이나 열을 삭제하는 메서드이다.
  • 열 삭제를 할 때는 axis 매개변수 = 1로 지정한다.
# ns_df 'Unnamed: 13' 열 삭제
ns_book = ns_df.drop('Unnamed: 13',axis = 1)
ns_book.head()

# 여러 열 삭제시에는 리스트 형식 사용
ns_book = ns_df.drop(['부가기호','Unnamed: 13'],axis = 1)
ns_book.head()

실행결과

  • inplace 매개변수 : 새 변수에 저장하지 않고 덮어쓰기
# implace 매개변수 
ns_book.drop('주제분류번호',axis = 1,inplace = True)
ns_book.head()

dropna() 메서드

  • 판다스는 빈 값을 NaN으로 표시한다
  • NaN이 포함된 열이나 행 삭제하는 메서드이다
# NaN이 포함된 열 삭제
ns_book = ns_df.dropna(axis = 1)
ns_book.head()

# 모든 값이 NaN인 열 삭제: how 매개변수
ns_book = ns_df.dropna(axis = 1, how = 'all')
ns_book.head()

원래 ns_book
실행결과, '주제분류번호'와 'Unnamed: 13'이 사라진 것이 보인다.

행 삭제하기

  • drop() 메서드를 사용해도 된다
  • 기본값이 axis = 0이다. 행 삭제를 의미한다
# 처음 2개 행 삭제
ns_book2 = ns_book.drop([0,1])
ns_book2.head()

0행과 1행이 사라짐

[ ]연산자와 슬라이싱

## 1
# 0, 1행 제외
ns_book2 = ns_book[2:]
ns_book2.head()

## 2
# 슬라이싱 주의: 마지막 값은 포함하지 않는다
ns_book2 = ns_book[0:2]
ns_book2.head()

## 2의 실행결과, 0과 1만 선택되었다

[ ]연산자와 불리언 배열

# 출판사가 '한빛미디어'인 행만 가져오기
selected_rows = ns_df['출판사'] == '한빛미디어'
ns_book2 = ns_book[selected_rows]
ns_book2.head()

# 불리언 배열을 []연산자 안에 바로 넣기 : 대출건수가 1000이하인 행 삭제
ns_book2 = ns_book[ns_book['대출건수'] > 1000 ]
ns_book2.head()

중복된 행 찾기

  • duplicated() 메서드사용
  • duplicated() 메서드 : 중복된 행 중에서 처음 행을 제외한 나머지를 True 중복 아니면 False
# 중복된 행 찾기
sum(ns_book.duplicated())

결과는 0이다. '번호' 열이 고유해서 중복이 나올 수 없기 때문이다.

그래서 subset 매개변수기준 열을 지정해준다. 

# subset 매개변수로 기준 열 지정
sum(ns_book.duplicated(subset = ['도서명','저자','ISBN']))

바뀐 기준 열로 중복된 행을 확인해본다

  • keep 메개변수 : False로 지정해주면 중복된 행을 모두 True로 지정
# keep 매개변수로 중복된 행을 모두 True로 지정
dup_rows = ns_book.duplicated(subset = ['도서명','저자','ISBN'], keep = False)
ns_book3 = ns_book[dup_rows]
ns_book3.head()

그룹별로 모으기

  • groupby() 메서드 사용
  • by 매개변수 : 행을 합칠 때 기준이 되는 열 지정
# 데이터프레임 합치기 : '도서명','저자','ISBN','권'
count_df = ns_book[['도서명','저자','ISBN','권','대출건수']]

group_df = count_df.groupby(by = ['도서명','저자','ISBN','권'],dropna = False)
loan_count = group_df.sum()

# 메서드 이어 쓰기 : groupby와 sum
loan_count = count_df.groupby(by = ['도서명','저자','ISBN','권'],dropna = False).sum()
loan_count.head()

기준 열이 묶여있다

원본 데이터 업데이트하기

업데이트하기 전 거쳐야할 과정

  1. duplicated() 메서드로 중복된 행을 True로 표시한 불리언 배열을 만듦
  2. 1번에서 구한 불리언 배열을 반전시켜서 중복되지 않은 고유한 행을 True로 표시
  3. 2번에서 구한 불리언 배열을 사용해 원본 배열에서 고유한 행만 선택
  • 중복된 행을 True로 표시한 불리언 배열을 반전 : ~ 연산자 사용
  • 그 후 고유한 배열로 copy() 메서드로 새로 df를 만듦
dup_rows = ns_book.duplicated(subset=['도서명','저자','ISBN','권']) # 중복된 행 True
unique_rows = ~dup_rows # 반전 -> 고유한 행 True
ns_book3 = ns_book[unique_rows].copy() # 고유한 행만 선택

# 중복이 없는지 확인
sum(ns_book3.duplicated(subset=['도서명','저자','ISBN','권']))

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

  • set_index()로 지정한 열을 인덱스로 설정
  • inplace 매개변수를 True : 새 데이터프레임 반환이 아닌 원래 것을 수정한다
ns_book3.set_index(['도서명','저자','ISBN','권'], inplace = True)
ns_book3.head()

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

# 다른 데이터프레임을 사용해 원본 데이터프레임의 값 업데이트
ns_book3.update(loan_count)
ns_book3.head()

# 인덱스 해제
ns_book4 = ns_book3.reset_index()
ns_book4.head()

인덱스 해제 결과

  • 중복된 도서의 대출건수 합침
# ns_book
sum(ns_book['대출건수']>100)

# ns_book4
sum(ns_book4['대출건수']>100)

차례대로 2311, 2550의 결과가 나온다. 중복된 도서의 대출건수를 합쳐서 늘어났다.

 

하지만 인덱스를 만들고 해제해서 열 순서가 다르다. 이를 []를 사용해 조정한다.

ns_book4 = ns_book4[ns_book.columns]
ns_book4.head()

일괄 처리 함수 만들기

  • 열 삭제
    • loc 메서드+슬라이싱+불리언 배열
    • drop 메서드
  • 행 삭제
    • [ ]연산자+슬라이싱+불리언 배열
  • 중복된 행 찾기
    • groupby 메서드+sum 메서드
    • update() 메서드
    • set_index()와 reset_index()
 
def data_cleaning(filename):
  """
  남산 도서관 장서 CSV 데이터 전처리 함수
  :param filename: CSV  파일 이름
  """
  # 파일을 DF로 읽기
  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_book3.reset_index()
  # 원본 데이터프레임의 열 순서로 변경
  ns_book4 = ns_book4[ns_book.columns]
  return ns_book4
  
new_ns_book4 = data_cleaning('ns_book4.csv')
ns_book4.equals(new_ns_book4)

https://colab.research.google.com/drive/1IMSJjbG8LIoNKMp93s6bpQ2oeKYe60wU?usp=sharing