파이썬-pandas-데이터-프레임-레드-판다
판다스에서 레드 판다스로

지난 시간은 Python pandas 사용법의 두 번째 세션으로 Python pandas 데이터 확인, 정렬, 선택하는 법을 알아보았습니다. 이번 시간에는 Python pandas에서 데이터 프레임(DataFrame)을 합치는 법과 데이터를 정제하는 법, 그리고 DataFrame을 변형하는 법을 알아보겠습니다. 먼저 DataFrame을 합치는 법부터 함께 살펴보겠습니다.

DataFrame을 통합하는 부분은 기본 부분과 실전 부분으로 나뉩니다. 기본 부분에서는 merge, join, concat 함수를 사용해 데이터를 병합하는 기본적인 방법을 알아볼 예정입니다. 실전 부분에서는 DataFrame을 가지고 실제로 작업할 때 DataFrame을 합치는 방향에 따라 각 방향에 사용하면 좋은 함수를 알아보겠습니다. 그럼 함께 시작해볼까요?

병합하기(Merging): 기본

먼저 실습을 위해 CSV 파일을 로딩하고, 불러온 데이터를 DataFrame 객체로 만들어보겠습니다. 여기서는 실습 예제 파일을 사용하겠습니다. 가지고 계신 CSV 파일을 사용하셔도 됩니다. 실습 파일은 페이지 상단에서 다운로드할 수 있습니다. 그럼 read_csv 메서드를 사용해 CSV 파일로 DataFrame을 만들어보겠습니다.

python
df1 = pd.read_csv('data1.csv')

위 코드를 실행하니 'df1' 이라는 이름의 DataFrame이 만들어진 것을 알 수 있습니다.

df1
df1  - Snug Archive

같은 방법으로 두 번째 DataFrame도 만들어보겠습니다.

python
df2 = pd.read_csv('data2.csv')

두 번째로 만들어진 DataFrame은 다음과 같습니다.

df2
df2  - Snug Archive

CSV 파일 또는 엑셀 파일을 불러올 때 사용하는 다양한 파라미터를 알고 싶은 분들은 Python pandas 데이터 생성, 로딩과 저장, 색인 관리하는 법을 참조해주세요. 그럼 지금부터 본격적으로 DataFrame을 병합할 때 사용하는 merge 함수의 사용법을 알아보겠습니다.

1) 옆으로 합치기1: merge

merge 함수를 사용할 때는 두 DataFrame의 교집합인 결과와 합집합인 결과를 가져오는 방법으로 나뉩니다. 먼저 두 DataFrame의 교집합인 결과를 가져와 보겠습니다.

교집합 결과 가져오기: 옵션 없음 or how='inner'

두 DataFrame에서 교집합인 결과를 가져오려면 merge 함수에 아무 옵션을 주지 않거나 조인 방법을 정하는 how 옵션에 'inner' 값을 전달하면 됩니다. merge 함수는 기본적으로 내부 조인(inner join)을 수행합니다. 내부 조인이란 '두 DataFrame에서 교집합인 결과만 가져오겠다'는 의미입니다. 함수를 사용할 때 따로 옵션을 명시하지 않으면 두 DataFrame에서 교집합인 부분, 즉 두 DataFrame 사이에서 중복된 열을 자동으로 찾아 그 열의 이름을 키로 사용합니다.

사용할 코드는 다음과 같습니다.

python
df = pd.merge(df1, df2)
# 또는
df = pd.merge(df1, df2, how='inner')
# 또는
df = df1.merge(df2, left_index=True, right_index=True)

코드를 실행하면 다음과 같은 DataFrame을 얻게 됩니다.

교집합으로 합쳐진 DataFrame
교집합으로 합쳐진 DataFrame  - Snug Archive

두 DataFrame 사이에서 공통된 부분만 가져온 것을 알 수 있습니다. 이 DataFrame을 만들 때는 두 DataFrame을 합칠 때 기준으로 사용할 열을 정하지 않았습니다. pandas에서 자동으로 결정했죠.

하지만 DataFrame을 합칠 때는 어떤 열을 기준으로 DataFrame을 병합할 것인지 명시적으로 지정하는 것이 좋습니다. on 옵션을 사용하면 기준 열을 정할 수 있습니다. 단, 이 옵션을 사용할 때는 반드시 두 DataFrame 객체 모두에 존재하는 열 이름을 사용해야 합니다. 그럼 특정 열을 기준으로 DataFrame을 합쳐볼까요?

예를 들어 '도감번호'를 기준으로 DataFrame을 합쳐보겠습니다. 그럼 다음과 같이 merge 함수에 on='도감번호'를 옵션으로 전달하면 됩니다.

python
df = pd.merge(df1, df2, on='도감번호') # how='left'는 생략 가능

코드를 실행하면 아래와 같이 새로운 DataFrame이 만들어집니다.

on 옵션으로 합쳐진 DataFrame
on 옵션으로 합쳐진 DataFrame  - Snug Archive

사진에서 알 수 있듯이 '도감번호' 열은 정수 색인 바로 다음 열에 나타났습니다. 기준으로 삼은 열은 색인 다음에 가장 왼편에 위치합니다. 여기서 주황색으로 표시된 두 부분은 두 DataFrame에서 중복된 열을 나타냅니다. pandas에서는 두 DataFrame에서 공통으로 있던 열을 식별하기 위해 한쪽 DataFrame의 열 이름의 끝에 _x 표시를, 다른 DataFrame에 있었던 열 이름 끝에는 _y로 표시합니다. 초록색 부분은 오른쪽 DataFrame에서 가져온 새 열입니다.

만약 두 객체 사이에 중복된 열 이름이 없다면 left_onright_on 옵션을 사용하면 됩니다. 우리가 사용한 예제에서는 중복되는 열이 있었지만, 여기서는 없다고 가정하고 이 두 개의 옵션을 사용해보겠습니다.

left_on 옵션에는 왼쪽 DataFrame에서 기준으로 삼을 열 이름을 전달하면 됩니다. right_on 옵션에는 오른쪽 DataFrame에서 기준으로 삼을 열 이름을 전달하면 되죠. 아래 코드는 열 이름 '포켓몬명'을 기준으로 삼아 DataFrame을 합치는 예제입니다.

python
df = pd.merge(df1, df2, left_on='포켓몬명', right_on='포켓몬명') # on='포켓몬명' 사용 가능

위 코드를 실행하면 다음과 같은 결과가 나타납니다.

left_on, right_on 옵션으로 합쳐진 DataFrame
left_on, right_on 옵션으로 합쳐진 DataFrame  - Snug Archive

만약 위 DataFrame에서 _x_y로 표시된 부분을 다른 이름으로 변경하려면 다음과 같이 suffixes 옵션을 사용하면 됩니다.

python
df = pd.merge(df1, df2, on='포켓몬명', suffixes=('_left', '_right'))

이 코드를 실행하면 주황색 부분으로 표시된 열의 이름이 각각 도감번호_left, '도감번호_right'으로 변경됩니다. 지금까지는 두 DataFrame의 교집합인 부분을 DataFrame으로 만들어보았습니다. 이번에는 두 DataFrame에서 합집합인 결과를 DataFrame으로 만드는 실습을 해보겠습니다.

합집합 결과 가져오기: how='outer'

두 DataFrame의 합집합인 결과를 가져오고 싶으면 how 옵션에 outer 값을 전달하면 됩니다. outer 옵션값은 양쪽 테이블에 존재하는 모든 키 조합을 이용해 DataFrame을 합칩니다. 실행할 코드는 다음과 같습니다.

python
df = pd.merge(df1, df2, how='outer')

이 코드를 실행하면 다음과 같은 DataFrame을 만들 수 있죠.

left 옵션으로 합쳐진 DataFrame
left 옵션으로 합쳐진 DataFrame  - Snug Archive

위 사진을 보면 초록색 부분이 새로 나타난 것을 알 수 있습니다. 오른쪽 DataFrame에 있던 부분이 왼쪽 DataFrame에 추가로 합쳐졌네요. outer 값은 양쪽 DataFrame에서 모든 열과 행을 가져오기 때문에 데이터를 빠짐없이 합칠 수 있다는 장점이 있습니다.

지금까지 DataFrame을 병합하는 merge 함수의 사용법을 알아보았습니다. merge와 비슷한 함수로는 join이 있습니다.

2) 옆으로 합치기2: join

join 함수는 행을 기준으로 DataFrame을 옆으로 합칠 때 사용합니다. join 함수는 두 DataFrame 사이에서 중복된 열 이름이 없을 때와 열 이름이 있을 때에 따라 사용법이 달라집니다.

중복되는 열 이름이 없을 때

두 DataFrame 사이에서 중복되는 열 이름이 없을 때는 how 옵션을 사용하면 됩니다.

python
left = pd.DataFrame(np.arange(9).reshape(3, 3),
index=['A1', 'A2', 'A3'],
columns=['버스', '택시', '지하철'])
right = pd.DataFrame(np.arange(9).reshape(3, 3),
index=['A1', 'A4', 'A6'],
columns=['자전거', '킥보드', '도보'])
df = left.join(right, how='outer') # how='inner' 도 사용 가능

이 코드를 실행하면 다음과 같은 DataFrame이 만들어집니다.

join 함수로 합쳐진 DataFrame
join 함수로 합쳐진 DataFrame  - Snug Archive

join 함수는 기본적으로 왼쪽 조인을 수행합니다. 왼쪽 DataFrame을 기준으로 데이터의 누락 값을 정합니다. 그래서 위 사진의 초록색 부분은 NaN 처리되었습니다. 만약 두 DataFrame 사이 중복된 열이 있는데 join 함수를 실행하면 columns overlap but no suffix specified 오류가 발생합니다. 접두사(suffix)를 명시하라는 메시지입니다. 그럼 중복되는 열이 있을 때는 아래와 같이 접두사를 명시하면 됩니다.

중복되는 열 이름이 있을 때

합치려는 두 DataFrame에서 중복되는 열 이름이 있을 때는 join 함수와 함께 lsuffixrsuffix 옵션을 지정해주어야 합니다. 이 옵션은 DataFrame을 합칠 때 중복되는 열의 이름을 구분 지어줍니다.

python
df = df1.join(df2, lsuffix='_left', rsuffix='_right')

위 코드를 실행하면 다음과 같은 DataFrame이 만들어집니다.

'lsuffix'와 'rsuffix' 옵션을 더해 join 함수로 합쳐진 DataFrame
'lsuffix'와 'rsuffix' 옵션을 더해 join 함수로 합쳐진 DataFrame  - Snug Archive

왼쪽 DataFrame에서 가져온 열과 오른쪽 DataFrame에서 가져온 열이 우리가 지정한 접두사에 따라 구분되어 표기된 것을 알 수 있습니다.

지금까지 'join' 함수를 알아보았습니다. 마지막으로 concat 함수를 알아보겠습니다.

3) 위아래로 합치기: concat

concat 함수는 DataFrame을 물리적으로 이어 붙여주는 함수입니다. concat 함수를 사용할 때는 위에서 살펴본 merge 함수와 join 함수에서와 달리 중복 열의 유무와 관계없이 두 DataFrame을 단순하게 이어 붙입니다. concat 함수를 사용할 때도 옵션을 다양하게 사용할 수 있습니다. 하나씩 차례로 알아보겠습니다.

수직으로 붙이기: 옵션 없음

만약 아무 옵션을 주지 않으면 concat 함수는 두 DataFrame을 수직으로 붙입니다.

python
df = pd.concat([left, right]) # 수직으로 붙이기

위 코드를 실행하면 다음과 같은 결과가 나타나죠.

concat 함수로 합쳐진 DataFrame
concat 함수로 합쳐진 DataFrame  - Snug Archive

초록색 부분이 왼쪽 DataFrame이고 보라색 부분이 오른쪽 DataFrame입니다.

수평으로 붙이기: axis=1

axis=1 옵션을 주면 concat 함수는 두 DataFrame을 수평으로 붙입니다.

python
df = pd.concat([left, right], axis=1)

이 코드를 실행하면 다음과 같은 DataFrame을 얻을 수 있습니다.

'axis=1' 옵션을 더해 concat 함수로 합쳐진 DataFrame
'axis=1' 옵션을 더해 concat 함수로 합쳐진 DataFrame  - Snug Archive

이 DataFrame에서 자주색으로 표시된 부분이 두 DataFrame에서 공통된 부분입니다. 이 부분이 수평으로 붙여진 것을 알 수 있습니다.

교집합 결과 가져오기: join='inner'

concat 함수에 join='inner' 옵션을 주면 두 DataFrame에서 교집합인 결과를 반환합니다.

python
df = pd.concat([left, right], join='inner')
# 결과
A1
A2
A3
A1
A4
A6

두 DataFrame에서 교집합인 부분은 없기 때문에 색인만 결과로 출력된 것을 알 수 있습니다.

합집합 결과 가져오기: join='outer'

두 DataFrame에서 합집합인 부분을 가져올 수도 있습니다. 다음 코드를 한 번 실행해보세요.

python
df = pd.concat([left, right], join='outer')

계층적 색인 주어 이어 붙이기

계층적 색인을 주어 두 DataFrame을 이어 붙일 수도 있습니다. 계층적 색인을 사용하려면 아래 코드에서와 같이 keys 옵션을 사용하면 됩니다.

python
df = pd.concat([left, right], axis=1, keys=['level1', 'level2'])

위 코드를 실행하면 다음과 같은 결과가 나타납니다.

'axis=1' 옵션을 더해 concat 함수로 합쳐진 DataFrame
'axis=1' 옵션을 더해 concat 함수로 합쳐진 DataFrame  - Snug Archive

계층적 색인 level1level2가 나타난 것을 알 수 있습니다. 지금까지 merge, join, concat 함수의 기본 사용법을 알아보았습니다. 이제부터는 실제로 데이터 전처리 작업을 할 때 자주 사용하는 DataFrame 통합 방법을 살펴보겠습니다.

병합하기(Merging): 실전

실전에서는 먼저 DataFrame을 합칠 방향을 고려해야 합니다. 합치는 방향은 옆과 아래로 나뉘죠.

1) 옆으로 통합: merge

옆으로 통합할 때는 merge 함수를 사용합니다. 만약 왼쪽 DataFrame을 기준으로 통합하고 싶다면 on='left' 옵션을 오른쪽 DataFrame을 기준으로 통합하고 싶다면 on='right' 옵션을 주면 됩니다. 각 경우를 차례로 살펴보죠.

왼쪽 DataFrame 기준: how='left'

왼쪽 DataFrame을 기준으로 DataFrame을 합치려면 다음과 같이 how 옵션에 left 값을 전달하면 됩니다.

python
df = pd.merge(df1, df2, how='left')

이 코드를 실행하면 아래와 같은 DataFrame을 얻을 수 있습니다.

기본 코드에 'left' 옵션을 더해 만든 DataFrame
기본 코드에 'left' 옵션을 더해 만든 DataFrame  - Snug Archive

초록색 부분이 오른쪽 DataFrame에서 가져온 열입니다. 코드를 아래와 같이 더 세부적으로 명시할 수도 있습니다. 저는 이 방법을 선호합니다.

python
df = pd.merge(left=df1, # 왼쪽 DataFrame 지정
right=df2, # 오른쪽 DataFrame 지정
how='left', # 조인 방법 지정
left_on='도감번호', # 왼쪽 DataFrame에서 기준으로 삼을 열 지정
right_on='도감번호') # 오른쪽 DataFrame에서 기준으로 삼을 열 지정

이 코드를 실행하면 다음과 같은 결과가 나타납니다.

세부 코드에 'left' 옵션을 더해 만든 DataFrame
세부 코드에 'left' 옵션을 더해 만든 DataFrame  - Snug Archive

두 코드 중 선호하시는 방법을 사용하시면 됩니다.

오른쪽 DataFrame 기준: how='right'

오른쪽 DataFrame을 기준으로 DataFrame을 옆으로 통합하려면 다음 코드를 사용하면 됩니다.

python
df = pd.merge(df1, df2, how='right')

코드를 실행하면 다음과 같은 DataFrame을 만들 수 있습니다.

'right' 옵션으로 합쳐진 DataFrame
'right' 옵션으로 합쳐진 DataFrame  - Snug Archive

이 코드도 위에서 했던 것처럼 세부적으로 명시할 수 있습니다. 지금까지 DataFrame을 옆으로 합치는 방법을 알아보았습니다. 이번에는 DataFrame을 아래로 합치는 법을 살펴보겠습니다.

2) 아래로 통합: concat, append

DataFrame을 아래로 합치려면 concat 함수나 append 함수를 사용하면 됩니다. concat 함수를 사용하는 코드는 다음과 같습니다.

python
df = pd.concat([df1, df2], ignore_index=True) # ignore_index=True 는 새 정수 색인을 만드는 옵션
또는
df = df1.append(df2, ignore_index=True)

코드를 합칠 때는 보통 ignore_index=True 옵션을 함께 사용합니다. 이 옵션은 새 DataFrame에서 새 정수 색인을 사용해서 더 깔끔하게 데이터를 확인할 수 있는 장점이 있습니다. concat 함수와 append 함수를 사용한 두 코드 모두 다음과 같은 동일한 결과를 출력합니다.

concat, append 함수를 통해 아래로 합쳐진 DataFrame
concat, append 함수를 통해 아래로 합쳐진 DataFrame  - Snug Archive

지금까지 pandas에서 merge, join, concat 함수를 이용해 DataFrame을 합치는 법을 배워보았습니다. 데이터를 합치고 나서는 무엇을 하면 좋을까요? 데이터를 정제하는 작업입니다. 보통 우리가 접하는 데이터는 정리가 필요한 경우가 많습니다. 정확한 결과를 얻기 위해서는 데이터에서 필요 없는 부분은 삭제하고 누락된 값을 처리해서 데이터를 깔끔하게 만들어야 합니다. 지금부터는 데이터를 정제하는 법을 알아보겠습니다.

정제하기(Refining)

데이터를 정제한다는 것은 결측치, 중복값, 이상치를 처리하는 것입니다. 먼저 결측치를 처리하는 법부터 살펴보겠습니다.

1) 결측치

누락 데이터를 처리하는 방법도 중복 데이터를 처리하는 방법과 유사합니다. 먼저 누락 데이터가 있는지를 확인합니다. 그리고 해당 누락 데이터를 제외하거나, 누락 값을 다른 값으로 대체할 수 있겠죠.

확인하기: pd.isna / isnull, notnull

결측치 유무를 파악하는 방법에는 패키지 함수의 isna와 메스드 isnull, notnull을 이용하는 방법이 있습니다.

pd.isna

pd.isna 함수는 데이터 내에서 누락값을 불리언값으로 출력해줍니다.

python
pd.isna(df) # 결측치 파악하기

결측치의 총 개수를 알고 싶으면 pd.isna 함수에 sum 메서드를 체이닝하면 됩니다.

python
pd.isna(df).sum() # 결측치 빈도 파악하기
isnull, notnull

isnullnotnull 메서드 역시 데이터 내에서 누락 값이 있는지를 알려줍니다. 이 함수의 사용법은 Python pandas 데이터 확인, 정렬, 선택하는 법의 '데이터 확인' 세션에서 더 자세히 살펴보실수 있습니다.

결측치를 확인한 후에는 누락 데이터를 제거하거나 누락값을 다른 값으로 채울 수 있습니다. 먼저 누락 데이터를 제외하는 dropna 함수부터 알아보겠습니다.

제외하기: dropna

dropna 메서드는 NaN 인 값을 하나라도 포함하고 있는 행을 제외하고 값을 반환합니다.

python
df.dropna() # 데이터 전체에서 결측치 제거하기, df[df.notnull()]과 동일

특정 열의 결측치만 제거하고 싶다면 dropna 함수의 파라미터 subset에 결측치를 제거할 열 이름을 리스트로 입력하면 됩니다.

python
df.dropna(subset=['열이름1', '열이름2'])

dropna 함수로 제거한 결측치는 원본 데이터에 반영되지 않습니다. 원본 데이터에 결측치 처리 결과를 반영하려면, DataFrame을 덮어쓰거나 기존 DataFrame에 inplace=True 옵션을 주면 됩니다.

python
df_refined = df.dropna(subset=['열이름'])
# 또는
df.dropna(inplace=True)

다음과 같이 NaN을 어떻게 처리할 것인지 옵션을 줄 수도 있습니다.

행 전체가 NaN인 경우에만 행 삭제: how='all'

행 전체가 모두 NaN인 경우만 DataFrame에서 제외하려면 how=all 옵션을 주면 됩니다.

python
df.dropna(how='all')
열 전체가 모두 NaN인 경우에만 열 삭제: axis=1 & how='all'

열 전체가 모두 NaN인 경우만 DataFrame에서 제거하려면 axis=1 옵션과 how=all 옵션을 사용하면 됩니다.

python
df.dropna(axis=1, how='all') # axis='columns' 사용 가능

이번에는 누락 데이터값을 채우는 fillna 함수의 사용법을 알아보겠습니다.

대체하기: fillna

fillna 함수는 결측치를 원하는 값으로 대체할 수 있습니다. 이 함수에 인자로 NaN 값 대신 사용할 값을 전달하면 되죠. 대체하는 방법은 다양합니다.

직접 정하기

대체할 값을 직접 정할 수 있습니다. 'NaN'를 '데이터 없음'으로 바꾸고 싶다면 다음과 같은 코드를 사용하면 됩니다.

python
df.fillna('데이터 없음')

fillna로 처리한 값은 원본 DataFrame을 변경하지는 않습니다. 만약 처리한 값을 DataFrame에 직접 반영하고 싶다면 dropna함수에서와 같이 inplace=True 옵션을 주거나 기존 DataFrame에 덮어 써야합니다.

python
df.fillna('데이터 없음', inplace=True)
# 또는
df = df.fillna('데이터 없음')

대체할 값으로 평균값이나 중간값을 사용할 수도 있습니다.

python
df.fillna(df.mean()) # 모든 데이터의 결측치를 평균으로 대체
python
df['열1'].fillna(0) # 열1의 결측치를 0으로 대체
각 열마다 다른 값 채우기

만약 열마다 누락 값을 다르게 채우고 싶다면 사전값을 인자로 넘겨주면 됩니다.

python
df.fillna({'과': '없음', '국어': 0}) # '과'열의 결측치는 '없음'으로 보간, '국어'열의 결측치는 0으로 보간
결측값을 위 값으로 채우기: method='ffill'

method='ffill' 옵션은 결측값을 바로 윗값과 동일하게 합하는 옵션입니다.

python
df.fillna(method='ffill') # method='pad' 사용 가능
결측값을 아래 값으로 채우기: method='bfill'

method='bfill' 옵션은 결측값을 바로 아래값과 동일하게 합하는 옵션입니다.

python
df.fillna(method='bfill') # method='backfill' 사용 가능
NaN으로 처리하기: np.nan

만약 특정 값을 NaN으로 처리하고 싶다면 넘파이의 nan 함수를 사용하면 됩니다.

python
df.loc[[0, 1, 3], ['열1']] = np.nan # 열1의 0, 1, 3번 째 행의 값을 NaN으로 처리

2) 중복값

중복 데이터를 처리하려면 먼저 중복 데이터가 있는지를 확인하고, 중복된 데이터를 제거하면 됩니다.

확인하기: duplicated

중복 데이터를 확인하고 싶다면 duplicated 메서드를 사용하면 됩니다. duplicated 메서드는 각 행이 중복되는지 아닌지를 불리언 Series로 알려줍니다. 행이 중복이 되지 않으면 False 값을 중복되면 True 값을 반환합니다.

python
df.duplicated()

제거하기: drop_duplicates

중복되는 데이터를 제거하고 싶다면 drop_duplicated 메서드를 사용하면 됩니다. drop_duplicatesduplicated 배열이 False인 DataFrame을 반환합니다. 배열이 False라는 의미는 곧 중복되지 않음을 뜻하죠. 이 함수를 사용하면 중복 데이터가 제거되고 남은 깔끔한 데이터를 얻을 수 있습니다.

python
df.drop_duplicates()

3) 이상치

이상치(abnomaly)는 정상 범위에서 크게 벗어난 값을 의미합니다. 논리적으로 존재하기 어려운 값이나 극단치(outlier)제거해야 분석이 왜곡될 가능성이 줄어듭니다.

존재할 수 없는 값

성별 데이터에 남자는 1, 여자는 2로 맵핑되었다고 가정합니다. 성별 데이터에 1과 2외의 값은 들어갈 수 없습니다. 이를 존재할 수 없는 값이라고 합니다. 존재할 수 없는 값은 따로 처리를 해주어야 합니다. 다음은 1 또는 2외의 값을 넘파이(Numpy)의 nan 함수를 이용해 NaN으로 처리하는 예제입니다.

python
import numpy as np
df['sex'] = np.where((df['sex'] == 1) | (df['sex'] == 2),
df['sex'], np.nan)

극단치

극단치의 경계가 되는 하한과 상한은 1사분위수와 3사분위수, 그리고 이 두 사분위수의 거리를 나타내는 IQR(inter quartile range, 사분위 범위)로 구할 수 있습니다.

python
pct25 = df['열이름'].quantile(.25) # 1사분위수: 하위 25%에 해당
pct75 = df['열이름'].quantile(.75) # 3사분위수: 하위 75%에 해당
iqr = pct75 - pct25 # 사분위 범위
lower_limit = pct25 - 1.5 * iqr # 하한
upper_limit = pct75 + 1.5 * iqr # 상한

상한값과 하한값을 구한 뒤에는 해당 기준값을 벗어나는 부분을 결측 처리해주면 됩니다.

python
df['열이름'] = np.where((df['열이름'] < lower_limit) | (df['열이름'] > upper_limit),
np.nan, df['열이름'])
df['열이름'].isna().sum() # 결측치 빈도 확인

극단치를 시각적으로 빠르게 파악하고 싶다면 상자 그림(box plot)을 그려보면 됩니다. 코드는 다음과 같습니다.

python
import seaborn as sns
df = pd.read_csv('data.csv')
sns.boxplot(data=df, y='열이름')

상자 그림을 그리는 자세한 방법은 파이썬 데이터 시각화 Seaborn 사용법 기초편에서 boxplot 부분을 참조해주세요.

지금까지 결측치와 중복값, 이상치를 처리해서 데이터를 정제하는 방법을 살펴보았습니다. 데이터를 정제하고 나면 데이터를 분석 목적에 적합하게 변형할 수 있습니다. 데이터를 더 깔끔하게 다듬을수록 정확도는 높아지겠죠. 마지막으로는 데이터를 변형하는 법을 살펴보겠습니다.

변형하기(Transforming)

데이터를 변형하는 법은 데이터를 추가하는 법, 데이터를 수정하는 법, 데이터를 삭제하는 법으로 나누어집니다. 먼저 데이터를 추가하는 법부터 알아보겠습니다. 예제 데이터로는 위에서 사용한 두 실습 파일을 합집합 방식으로 병합한 DataFrame을 사용하겠습니다. 실습으로 사용할 DataFrame은 다음과 같습니다.

merge함수에 how='outer' 옵션을 더해 만든 DataFrame
merge함수에 how='outer' 옵션을 더해 만든 DataFrame  - Snug Archive

1) 추가

DataFrame에는 열과 행을 추가할 수 있습니다. 먼저 행을 추가하는 법을 알아보겠습니다.

pandas DataFrame에 행을 추가하려면 loc를 사용하면 됩니다. 준비된 DataFrame에 행 색인을 지정한 뒤 loc를 사용해서 행을 추가해보겠습니다. 코드는 다음과 같습니다.

pythhon
df.set_index('도감번호', inplace=True)
df.loc['A14'] = ['근육몬', '예체능', 84, 15, 23, 42 , 28, '미학', '흙']

위 코드를 실행하면 다음과 같이 DataFrame에 행이 추가됩니다.

DataFrame에 행 추가
DataFrame에 행 추가  - Snug Archive

초록색 부분이 새로 추가된 행입니다. 행을 추가할 때는 행 색인이 있어야 합니다. 라벨 색인을 사용하는 loc 메서드를 사용해야 하기 때문입니다. iloc 함수로는 행을 추가할 수 없습니다. 이번에는 열을 추가해보겠습니다.

pandas에서 DataFrame에 열을 추가하려면 대괄호, loc, map 또는 lambda 함수, insert 함수, assign 함수를 사용하는 법이 있습니다.

대괄호

대괄호를 사용하면 DataFrame에 새 열을 추가할 수 있습니다. 여기서는 '평균'이라는 새로운 열을 추가해보겠습니다. 코드는 다음과 같습니다.

python
df['평균'] = (df['국어'] + df['영어'] + df['수학'] + df['과학'] + df['사회']) / 5

이 코드를 실행하면 아래와 같이 '평균' 열이 새로 추가된 것을 확인할 수 있습니다.

DataFrame에 추가된 '평균'열
DataFrame에 추가된 '평균'열  - Snug Archive

특정 조건에 따라 불리언 값을 담고 있는 새로운 열을 만들 수도 있습니다. 예제 코드를 참고해 주세요.

python
df['결과'] = df['평균'] >= 85 # 평균이 85이상인 대상에 True, 나머지에 False 부여

이 새로운 열은 df.결과 형태의 문법으로는 생성되지 않습니다.

loc

이번에는 loc를 사용해 열을 추가해보겠습니다. 위에서 만들어진 열에 '결과' 열을 만들 것입니다. 먼저 '결과'열의 모든 값을 'Fail'로 초기화해줍니다. 그리고 '평균'열에서 값이 80 이상인 값만 '결과' 열에 'Pass'로 표시해보겠습니다. 아래 코드를 함께 확인해보시죠.

python
df['결과'] = 'Fail' # '결과' 열을 추가하고 초깃값으로 Fail 지정
df.loc[df['평균'] >= 80, '결과'] = 'Pass' # 평균이 80 이상인 데이터에 대해 결과를 'Pass'로 업데이트

이 코드를 실행했더니 다음과 같이 '결과' 열이 새로 생겨난 것을 알 수 있습니다.

DataFrame에 추가된 '결과'열
DataFrame에 추가된 '결과'열  - Snug Archive

이처럼 loc 함수를 이용하면 특정 조건에 해당하는 데이터만 골라내서 새로운 값을 부여할 수 있습니다. 참고로 넘파이의 where()를 이용해도 위와 동일한 결과를 만들 수 있습니다.

python
import numpy as np
df['결과'] = np.where(df['평균'] >= 80, 'Pass', 'Fail')

만일 수치형 데이터를 여러 개의 범주로 분류하고 싶다면 np.where을 아래와 같이 사용할 수 있습니다.

python
df['결과'] = np.where(df['평균'] >= 90, 'A',
np.where(df['평균'] >= 80, 'B',
np.where(df['평균'] >= 70, 'C',
np.where(df['평균'] >= 60, 'D', 'F'))))

범주형 데이터의 각 범주를 특정값으로 분류하고 싶다면 다음과 같이 np.where()isin() 메서드를 사용하면 됩니다.

python
df['분류'] = np.where(df['성향'].isin(['공기', '흙']), '진로A' , '진로B')

'|' 기호를 이용해도 동일한 결과를 얻을 수 있습니다.

python
df['분류'] = np.where((df['성향'] == '공기') |
(df['성향'] == '흙'),
'진로A', '진로B')

단, np.where 함수는 문자와 np.nap을 함께 사용하면 NaN이 아니라 문자 'nan'이 반환되어 주의해야 합니다. np.where 함수를 사용할 때 한 쪽의 값이 문자면 다른 쪽의 값도 문자 또는 숫자로 변경해서 사용해야 합니다.

python
df['결과'] = np.where(df['평균'] >= 80, 'Pass', np.nan) # NaN이 아니라 문자 'nan'이 반환됨
assign

assign 메서드는 DataFrame의 맨 마지막 열 옆에 열을 추가합니다. 이 함수를 사용해 열을 추가하려면 추가하려는 열의 이름과 값을 전달하면 됩니다.

python
df.assign(어문총점 = df['국어'] + df['영어'], # 어문 계열 과목 총점 추가
어문평균 = (df['국어'] + df['영어']) / 2) # 어문 계열 과목 평균 추가

assign 메서드를 넘파이의 where 메서드와 조합하면 조건에 따라 다른 값을 부여할 수 있습니다.

python
df.assign(result = np.where(df['총점'] >= 80, '합격', '불합격'))

여기에 lambda 함수를 이용하면 코드를 더 간결하게 작성할 수 있습니다.

python
df.assign(어문총점 = lambda x: x['국어'] + x['영어'], # 어문 계열 과목 총점 추가
어문평균 = lambda x: x['어문총점'] / 2) # 어문 계열 과목 평균 추가
map

다음은 map 함수를 사용해서 데이터를 추가하는 코드입니다.

python
data = pd.DataFrame({
'grocery': ['potatoes', 'onions', 'Broccoli', 'tomatoes', 'Celery', 'carrots', 'Onions'],
'count': [5, 3, 2, 5, 1, 6, 7]})
field = {
'potatoes': '밭1',
'onions': '밭1',
'broccoli': '밭2',
'tomatoes': '밭3',
'celery': '밭1',
'carrots': '밭3'
}
lowercased = data['grocery'].str.lower() # str.lower 소문자로 변경
data['area'] = lowercased.map(field)

이 코드를 실행하면 아래와 같은 DataFrame이 생성됩니다.

map 함수로 열을 추가한 DataFrame
map 함수로 열을 추가한 DataFrame  - Snug Archive

area라는 새로운 열이 추가된 것을 확인할 수 있습니다. 아래와 같이 lambda 함수를 사용해도 새로 추가한 열을 얻을 수 있습니다.

python
data['grocery'].map(lambda x: field[x.lower()])
# 결과
01
11
22
33
41
53
61
Name: grocery, dtype: object
insert

insert 함수는 열을 추가할 위치를 지정할 수 있습니다. 이 함수로 열을 추가하려면 이 함수에 열을 추가할 위치와, 열 이름, 그리고 값을 전달하면 됩니다. insert 함수를 사용해 열을 추가할 때는 전달하는 값의 개수가 기존 열의 행 개수와 동일해야 합니다.

python
data.insert(1, "중요도", [1, 5, 8, 9, 2, 3, 4])

이 코드를 실행하면 grocerycount 열 사이에 중요도라는 새로운 열이 만들어집니다. 지금까지 pandas DataFrame에서 행과 열을 추가하는 법을 알아보았습니다. 이번에는 데이터를 수정하는 법을 알아보겠습니다.

2) 수정

특정 셀에 있는 값을 수정하려면 loc를 사용해 타겟 셀을 지정한 뒤 바꾸고 싶은 값을 할당하면 됩니다. 코드는 다음과 같습니다.

python
df.loc['A12', '희망전공'] = '컴퓨터공학' # '나옹'의 희망전공 변경

위 코드를 실행하면 '나옹'의 '희망전공'이 '컴퓨터공학'으로 수정됩니다. 여기서 'A12'는 행 색인의 라벨입니다. 여러 개의 값을 변경하려면 아래와 같이 대괄호를 사용하면 됩니다.

python
df.loc['A13', ['과', '희망전공']] = ['이과', '컴퓨터공학'] # '뚜벅쵸'의 과와 희망전공 변경

열별로 데이터를 수정할 때는 대괄호를 사용하는 방법과 'replace' 함수를 사용하는 방법이 있습니다.

대괄호

대괄호를 이용하면 특정 열을 선택해 열의 값을 수정할 수 있습니다. 다음 코드는 열의 모든 값을 소문자로 변경하는 예제입니다.

python
data['grocery'].str.lower() # upper()을 사용하면 열의 모든 값을 대문자로 변경
# 결과
0 potatoes
1 onions
2 broccoli
3 tomatoes
4 celery
5 carrots
6 onions
Name: grocery, dtype: object

만약 위의 결과를 DataFrame에 반영하려면 기존의 열을 새로 만든 열로 덮어쓰면 됩니다.

python
data['grocery'] = df['grocery'].str.lower() # upper()을 사용하면 열의 모든 값을 대문자로 변경

다음과 같이 문자열을 추가해서 기존의 열을 새로운 열로 덮어 쓸 수도 있습니다.

python
data['grocery'] += '상자' # 'grocery' 열의 모든 값 끝에 '상자'를 붙임
replace

대괄호를 사용하지 않고 replace 함수를 사용해도 됩니다. 다음 코드는 '과'열에 있는 모든 '문과'와 '이과'의 값을 각각 'Liberal Ars'와 'Science'로 변경하는 예제입니다.

python
df['과'].replace('이과', '예체능') # 과열에서 이과를 예체능으로 변경
# 또는
df['과'].replace({'문과': 'Liberal Arts', '이과': 'Science'}) # 문과는 Liberal Arts로 이과는 Science로 변경

만약 DataFrame에 결과를 바로 반영하고 싶다면 inplace=True 옵션을 주면 됩니다.

python
df['과'].replace({'문과': 'Liberal Arts', '이과': 'Science'}, inplace=True)

순서

DataFrame에서 순서를 변경하려면 대괄호와 리스트를 이용하는 방법이 있습니다.

대괄호

대괄호를 사용하면 원하는 열만 가져와서 순서대로 열을 배치할 수 있습니다.

python
df = df[['포켓몬명', '도감번호', '희망전공', '과']]
리스트

리스트를 이용하는 방법도 있습니다. 다음 코드는 맨 끝에 있는 열을 맨 처음으로 가져와서 나머지 열에 이어붙이는 코드입니다.

python
cols = list(df.columns)
df =[[cols[-1]] + cols[0:-1]]
열 순서를 변경한 DataFrame
열 순서를 변경한 DataFrame  - Snug Archive

지금까지 데이터를 수정하는 법을 살펴보았습니다. 마지막으로 데이터를 삭제하는 방법을 알아보겠습니다.

3) 삭제

데이터를 삭제할 때는 drop 함수를 사용하면 됩니다. drop 함수는 새로운 객체를 반환하는 대신 원본 객체를 변경합니다. inplace=True 옵션을 사용하면 결과가 바로 데이터 프레임에 반영됩니다. 단, 버려지는 값은 모두 삭제되니 주의해서 사용하는 것이 좋습니다.

데이터를 삭제하는 방법은 행을 삭제하는 법과 열을 삭제하는 법으로 나누어집니다. 하나씩 살펴보겠습니다.

1개 행을 삭제하는 법과 다수의 행을 한 번에 삭제하는 법을 알아보겠습니다.

1개 행

행 1개를 삭제하려면 아래와 같이 삭제하고 싶은 행 색인의 값을 drop 함수의 index 인자로 전달하면 됩니다.

python
df.drop(index='A10') # 행 색인이 'A10'인 행 삭제
df.drop(index='A10', inplace=True) # 행 색인이 'A10'인 행 삭제 & 데이터 프레임에 바로 반영
df['과'].drop('2번') # '과' 열에서 '2번' 행 삭제
df['포켓몬명'].drop(['1번', '3번']) # '포켓몬명' 열에서 '1번', '3번' 삭제
2개 행 이상

다수의 행을 삭제하려면 원하는 조건으로 데이터를 골라낸 뒤에 drop 함수의 index 인자에 데이터를 전달하면 됩니다.

python
filt = df['평균'] > 50
df.drop(index=df[filt].index)

이번에는 열을 삭제해보겠습니다.

열을 삭제하는 방법은 예제만 제시하겠습니다.

1개 열
python
df.drop(columns=['국어']) # '국어' 열 삭제
df.drop('수학', axis=1) # '수학' 열 삭제
df.drop(['국어', '영어'], axis='columns') # '국어', '영어' 열 삭제
2개 열 이상
python
df.drop(columns=['국어', '영어', '사회']) # '국어', '영어', '사회' 열 삭제

참고로 데이터 프레임에서 열을 삭제할 때 del 예약어를 사용하는 방법도 있습니다.

python
del df['국어'] # '국어' 열 삭제

지금까지 데이터 프레임을 병합하는 법, 정제하는 법, 그리고 데이터를 변형하는 법을 알아보았습니다. 다음 시간에는 Python pandas 데이터 재형성, 연산, 집계하는 법을 알아보겠습니다. 모두 수고하셨습니다.

참고 문헌

...

©2023 Snug Archive. All rights reserved.

Contact me at snugarchive@gmail.com.