코랩에서 먼저 데이터를 불러 온다.
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()
책들의 정보가 들어있는 데이터 프레임이 불러와졌다.
#불필요한 데이터 제거
이중 필요 없는 열을 제거 해보자.
#앞서 불리언 배열을 사용하여 불필요한 열을 제외했던 것처럼, drop 메서드로 중간에 있는 열을 제외 할 수 있다.
ns_book = ns_df.drop(['부가기호','Unnamed: 13'], axis=1)
ns_book.head()
이렇게 직접 열을 제거 해도 되지만,
자동으로 nan 결측치가 모두 삽입된 열을 drop 할 수 있다.
ns_book = ns_df.dropna(axis=1, how='all')
#모든 값이 nan 인 열을 삭제하려면 how 매개변수를 all 으로 지정하면 된다.
ns_book.head()
#중복된 데이터 찾기
#중복된 행 찾기
sum(ns_book.duplicated())
# 값이 0이라면 True로 표시된 행이 없다는 것. 중복된 행이 없음. 기본적으로 duplicated() 메서드는 모든 열을 기준으로 찾음
위의 데이터 프레임에서 중복된 행을 찾아보자 값이 0으로 중복된 행이 없다고 한다.
몇가지 열을 기준으로 중복된 행을 찾아보자.
sum(ns_book.duplicated(subset=['도서명','저자','ISBN']))
#결과 값이 20000개가 넘는다는 뜻
결과가 22096 으로 상당히 많은 책들이 중복으로 존재하고 있다.
중복된 행들을 출력해보자.
dup_rows = ns_book.duplicated(subset=['도서명','저자','ISBN'],keep=False)
#keep 매개변수를 false로 지정하여 중복된 모든 행을 true로 표시 이를 아래 변수로 저장하여 확인하면,
ns_book3=ns_book[dup_rows]
ns_book3.head()
중복된 행들이 출력이 되었다.
#group_by
이제 group_by로 대출건수를 합산해보자.
count_df = ns_book[['도서명','저자','ISBN','권','대출건수']]
group_df=count_df.groupby(['도서명','저자','ISBN','권'],dropna=False)
#groupby() 메서드는 데이터를 합칠때 기본적으로 지정된 열에 nan이 있는 행은 삭제한다. dropna 를 false 로 지정한다면 nan이 있는 행을 삭제하지 않는다.
loan_count = group_df.sum()
#이렇게 groupby와 sum 을 나눠서 호출해도 되지만 판다스는 한번에 연이어 호출하는 것을 선호한다.
loan_count.head()
이렇게 ['도서명','저자','ISBN','권','대출건수']을 가져온 데이터 프레임에서 대출건수를 제외한 열을 기준으로 합산하였다.
결과는 잘 출력이 되었으나, 판다스는 한번에 호출하는 것을 선호한다고 한다.
따라서 아래의 코드로 다시 실행 해보자.
# 한번에 연이어 호출하는 법
loan_count = ns_book[['도서명','저자','ISBN','권','대출건수']].groupby(['도서명','저자','ISBN','권'],dropna=False).sum()
loan_count.head()
#이제 loan_count 데이터프레임은 도서명 저자 이스빈 권 의 열을 가지고 각 책의 대출건수를 더한 결과가 저장됨.
설정한 열 4가지를 기준으로 대출건수가 잘 합산되었다.
하지만 이는 단순 합산된 데이터베이스이다.
원본 열은 5가지 열을 제외하고 더 많은 열이 존재한다.
그렇기에 원본 데이터 베이스에 위의 loan_count 데이터 베이스를 업데이트 시켜 대출건수를 올려보자.
#원본데이터 업데이트
'''원본 데이터 업데이트 하기
위에서 저장된 loan_count 데이터프레임을 원본 데이터에 업데이트 하겠다.
하지만, 원본데이터에는 중복된 값들이 존재한다.
1. 중복 제거
2. 고유행 true 로 표시
3. 고유행 선택
중복된 행을 true 로 표시한 불리언 배열을 반전 시킬때엔 ~ 연산자를 사용하면 된다.
그다음 원본 배열에서 고유한 배열을 선택하여 copy () 메서드로 ns_book3 데이터 프레임을 만든다.
'''
dup_rows =ns_book.duplicated(subset=['도서명','저자','ISBN','권'])
unique_rows = ~dup_rows
#고유한 배열 데이터 프레임
ns_book3= ns_book[unique_rows].copy()
sum(ns_book3.duplicated(subset=['도서명','저자','ISBN','권']))
# 0 이라면, 중복된 행이 없다
결과 : 0
주석으로 달아놓은 것처럼 먼저 원본데이터프레임에서 중복된 행을 제거 하고
고유한 데이터를 True 로 표시한 다음, 이를 선택한다.
#duplicated()함수는 기본적으로 첫번째 등장한 중복행은 false로 표시하고 그 이후에 나오는 중복된 행들에 대해 true 로 반환한다.
즉 첫번째값만 남기고 나머지는 중복으로 처리하는 것.
따라서 ~dup_rows 가 고유행이 될 수 있는 것
ns_book3는 대출건수는 합산되지 않은 단순 고유한 행들인 것이다.
그래서 결과를 확인할겸 duplicated() 를 한번더 해봄으로써 0이 나옴으로써 중복값이 없음을 검증했다.
이제 위의 loan_count 데이터프레임에서 대출건수를 가져와 연동 해보자.
#원본 데이터 프레임 인덱스 설정하기
''' ns_book3의 인덱스를 loan_count 데이터 프레임의 인덱스와 동일하게 만든다. 지정한 열을 인덱스로 설정할 때는 set_index() 메서드를 사용한다. 이때 inplace 를 true
로 지정하여 바로 ns_book3읠 데이터 프레임을 수정한다. '''
ns_book3.set_index(['도서명','저자','ISBN','권'], inplace= True )
ns_book3.head()
ns_book3와 원본데이터를 연동하기 위해선 인덱스가 동일해야한다. !!!!!!!!!!!!!!!
set_index로 동일하게 만든다.
인덱스가 동일하게 되었다.
이제 업데이트를 해보자.
#loan_count와 동일하게 열이 인덱스가 되었다.
#ns_book3 데이터프레밍의 대출건수 열을 loan_count 데이터프레밍의 대출건수 열로 업데이트 하는 것
ns_book3.update(loan_count)
ns_book3.head()
인덱스의 위치가 달라졌다. 업데이트가 제대로 되었나보다.
이제 대출건수가 잘합쳐졌는지 확인해보자.
대출건수가 100이상인 것을 True로 반환하여 원소의 개수를 세자.
#대출건수가 100이상인 것을 true로 반환하여 원소의 개수를 셈
sum(ns_book['대출건수']>100) #2311
sum(ns_book4['대출건수']>100) #2550
#100회 이상인 책이 훨씬 늘었다.-> 중복된 도서의 대출건수가 합쳐졌기 때문
1. 결과1 : 2311
2. 결과2 : 2550
원본인 ns_book 과 ns_book4와의 결과 차이가 약 200개 정도 차이가 났다.
#정리
결과적으로 정리해보자면
열을 삭제하는 방법으로 loc메서드에 슬라이싱을 사용하거나, 불리언 배열을 적용해보고, drop( ) 메서드를 사용했다.
행을 삭제하는 방법으론 []연산자와 슬라이싱, 불리언 배열을 사용했다.
판다스 사용자들은 [ ]연산자와 불리언배열을 사용해서 특정 열의 값이 어떤 조건을 만족하는 행을 찾는 방식을 즐겨 사용한다는점을 기억하자.
마지막으로 일괄 처리 함수를 정리해보자.
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 권 대출건수를 그룹바이 한다
loan_count = count_df.groupby(['도서명','저자','ISBN','권'],dropna=False).sum()
#원본 데이터 프레임에서 중복된 행을 제외하고 고유한 행만 하여 복사한다.
dup_rows = ns_book.duplicated(subset=['도서명','저자','ISBN','권'])
unique_rows = ~dup_rows
ns_book3 = ns_book[unique_rows].copy()
#도서명 저자 이스빈 권을 인덱스로 설정한다
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
위 함수를 실행한것과 위에서 내려온 코드 결과를 비교해보면 결과의 두데이터베이스가 같다는 것을 알 수 있다.
#궁금점
그렇다면, 궁금한 점이 생겼다. 엑셀의 필터 기능 혹은 피벗테이블 기능을 사용할때 처럼 , group_by메서드를 사용할때,
원본데이터에서 필요한 4가지 열들만 선택하고 다른 열들을 손실없이 보존하면서 대출건수에 대해서 합산 할수 있는 방법은 없나?
gpt에 물어보았다.
먼저, 가능하다는 답변을 받았다.
pandas 에서 group_by를 사용하여 원본 데이터의 필요한 열들을 선택한 후, 대출건수에 대해 그룹화 및 합산을 할 수 있다.
위에서 선택했던 4가지 열의 그룹화 기준을 유지하면서 다른 정보를 손실되지 않도록 처리하려면,
agg나 group_by 후 sum 메서드를 사용할 수 있다. 필요에 다라 다른 열을 포함할 수도 있다.
방법1 : group_by 와 sum을 사용한 그룹화 및 합산
# 필요한 열들만 선택하고 대출건수를 합산
grouped_df = ns_book.groupby(['도서명', '저자', 'ISBN', '권'], as_index=False).sum()
먼저 4가지 열을 기준으로 그룹화 한후 그룹별로 대출건수를 합산한다.
as_index= False 이 옵션은 그룹화한 열들을 인덱스가 아닌 일반 열로 유지한다.
방법2: agg()를 사용하여 여러열을 그룹화하고 다른 열은 유지
그룹화 외에 다른 부가정보들의 열을 함께 처리하고싶다면, agg( ) 를 사용하여 각 열에 대해 다른 함수를 적용할 수 있다.
grouped_df = ns_book.groupby(['도서명', '저자', 'ISBN', '권'], as_index=False).agg({
'대출건수': 'sum', # 대출건수는 합산
'출판사': 'first', # 출판사 정보는 첫 번째 값을 유지
'발행년도': 'first', # 발행년도도 첫 번째 값 유지
'주제분류번호': 'first', # 주제분류번호도 첫 번째 값 유지
'등록일자': 'first', # 등록일자도 첫 번째 값 유지
'부가기호': 'first',
})
grouped_df.head()
- **agg()**는 각 열에 대해 적용할 함수를 지정할 수 있습니다.
- 대출건수: 'sum': 대출건수를 합산합니다.
- 발행년도: 'first': 발행년도는 그룹화된 행 중에서 첫 번째 값을 유지합니다. 이외에도 'max', 'min' 등을 사용할 수 있습니다.
- 출판사: 'first': 출판사도 마찬가지로 그룹화된 행 중 첫 번째 값을 유지합니다.
이런식으로 agg( ) 를 사용해서 ns_book3 를 만들지 않고 원본 데이터에서 직접 대출건수를 합산할 수 있다.