19. 데이터프레임 중심 프로그래밍#

주요 내용

SeriesDataFrame 객체를 다루는 다양한 도구를 살펴본다.

  • 연산

  • 정렬

  • drop() 메서드: 행/열 삭제

기본 설정

pandas 라이브러리는 보통 pd 라는 별칭으로 사용된다.

import pandas as pd
import numpy as np

랜덤 시드, 어레이 내부에 사용되는 부동소수점 정확도, 도표 크기 지정 옵션 등은 이전과 동일하다.

np.random.seed(12345)
np.set_printoptions(precision=4, suppress=True)

import matplotlib.pyplot as plt
plt.rc('figure', figsize=(10, 6))

SeriesDataFrame을 표로 보여줄 때 사용되는 행의 수를 20으로 지정한다. 기본 값은 60이다.

pd.options.display.max_rows # 원래 60이 기본.
60

기본값을 20으로 변경한다.

pd.set_option("display.max_rows", 20)

19.1. 연산#

19.1.1. 산술 연산#

시리즈/데이터프레임의 사칙 연산은 아래 원칙을 따르면서 항목별로 이뤄진다.

  • 연산에 사용된 모든 행과 열의 라벨 모두 포함

  • 공통으로 사용되는 행과 열의 라벨에 대해서만 연산 적용. 그렇지 않으면 NaN으로 처리.

브로드캐스팅은 넘파이 어레이 연산처럼 필요한 경우 자동 적용된다.

시리즈 산술 연산

s1 = pd.Series([7.3, -2.5, 3.4, 1.5], index=['a', 'c', 'd', 'e'])
s1
a    7.3
c   -2.5
d    3.4
e    1.5
dtype: float64
s1*2
a    14.6
c    -5.0
d     6.8
e     3.0
dtype: float64
s2 = pd.Series([-2.1, 3.6, -1.5, 4, 3.1],
               index=['a', 'c', 'e', 'f', 'g'])
s2
a   -2.1
c    3.6
e   -1.5
f    4.0
g    3.1
dtype: float64
s1 + s2
a    5.2
c    1.1
d    NaN
e    0.0
f    NaN
g    NaN
dtype: float64

데이터프레임 산술 연산

df1 = pd.DataFrame(np.arange(9.).reshape((3, 3)), 
                   columns=list('bcd'),
                   index=['Ohio', 'Texas', 'Colorado'])
df1
b c d
Ohio 0.0 1.0 2.0
Texas 3.0 4.0 5.0
Colorado 6.0 7.0 8.0
df1 - 3
b c d
Ohio -3.0 -2.0 -1.0
Texas 0.0 1.0 2.0
Colorado 3.0 4.0 5.0
df2 = pd.DataFrame(np.arange(12.).reshape((4, 3)), columns=list('bde'),
                   index=['Utah', 'Ohio', 'Texas', 'Oregon'])
df2
b d e
Utah 0.0 1.0 2.0
Ohio 3.0 4.0 5.0
Texas 6.0 7.0 8.0
Oregon 9.0 10.0 11.0
df1 + df2
b c d e
Colorado NaN NaN NaN NaN
Ohio 3.0 NaN 6.0 NaN
Oregon NaN NaN NaN NaN
Texas 9.0 NaN 12.0 NaN
Utah NaN NaN NaN NaN

19.1.2. 연산과 결측치#

공통 인덱스가 아니거나 결측치가 이미 존재하는 경우 결측치로 처리된다. 하지만 fill_value 키워드 인자를 이용하여 지정된 값으로 처리하게 만들 수도 있다. 다만, 연산 기호 대신에 해당 연산의 메서드를 활용해야 한다.

df1.add(df2, fill_value=0)
b c d e
Colorado 6.0 7.0 8.0 NaN
Ohio 3.0 1.0 6.0 5.0
Oregon 9.0 NaN 10.0 11.0
Texas 9.0 4.0 12.0 8.0
Utah 0.0 NaN 1.0 2.0

많이 사용되는 산술 연산 기호에 해당하는 메서드가 존재한다. 아래는 가장 많이 사용되는 연산 메서드들이다.

메서드

설명

add()

덧셈(+) 계산 메서드

sub()

뺄셈(-) 계산 메서드

mul()

곱셈(*) 계산 메서드

div()

나눗셈(/) 계산 메서드

floordiv()

몫 (//) 계산 메서드

pow()

거듭제곱(**) 메서드

19.1.3. 브로드캐스팅#

넘파이에서 2차원 어레이와 1차원 어레이 사이에 브로드캐스팅이 가능한 경우, 즉, 차원을 맞출 수 있는 경우에 연산이 가능했다.

arr = np.arange(12.).reshape((3, 4))
arr
array([[ 0.,  1.,  2.,  3.],
       [ 4.,  5.,  6.,  7.],
       [ 8.,  9., 10., 11.]])
arr[0]
array([0., 1., 2., 3.])
arr - arr[0]
array([[0., 0., 0., 0.],
       [4., 4., 4., 4.],
       [8., 8., 8., 8.]])

브로드캐스팅이 불가능하면 오류가 발생한다.

arr[:,1]
array([1., 5., 9.])
try:
    arr + arr[:, 1]
except:
    print("브로드캐스팅 불가능!")
브로드캐스팅 불가능!

물론 아래와 같이 브로드캐스팅이 가능하도록 모양을 변환한 다음엔 연산이 가능하다.

arr_1 = arr[:,1][:, np.newaxis]
arr_1
array([[1.],
       [5.],
       [9.]])
arr + arr_1
array([[ 1.,  2.,  3.,  4.],
       [ 9., 10., 11., 12.],
       [17., 18., 19., 20.]])

데이터프레임과 시리즈 사이의 연산도 동일하게 작동한다. 다만, 행 또는 열에 대한 연산 여부를 확실하게 구분해주어야 한다.

frame = pd.DataFrame(np.arange(12.).reshape((4, 3)),
                     columns=list('bde'),
                     index=['Utah', 'Ohio', 'Texas', 'Oregon'])
frame
b d e
Utah 0.0 1.0 2.0
Ohio 3.0 4.0 5.0
Texas 6.0 7.0 8.0
Oregon 9.0 10.0 11.0
series = frame.iloc[0]
series
b    0.0
d    1.0
e    2.0
Name: Utah, dtype: float64

브로드캐스팅은 기본적으로 행 단위로 이루어진다. 따라서 아래처럼 데이터프레임과 시리즈의 연산을 그냥 적용할 수 있다.

frame - series
b d e
Utah 0.0 0.0 0.0
Ohio 3.0 3.0 3.0
Texas 6.0 6.0 6.0
Oregon 9.0 9.0 9.0

공통 인덱스가 존재하면 두 인자 모두에 대해 브로드캐스팅이 적용된다.

series2 = pd.Series(range(3), index=['b', 'e', 'f'])
series2
b    0
e    1
f    2
dtype: int64
frame + series2
b d e f
Utah 0.0 NaN 3.0 NaN
Ohio 3.0 NaN 6.0 NaN
Texas 6.0 NaN 9.0 NaN
Oregon 9.0 NaN 12.0 NaN

열 단위로 데이터프레임과 시리즈를 더하려면 해당 연산 메서드를 axis=0 키워드 인자와 함께 적용해야 한다.

series3 = frame['d']
series3
Utah       1.0
Ohio       4.0
Texas      7.0
Oregon    10.0
Name: d, dtype: float64
frame.sub(series3, axis=0)
b d e
Utah -1.0 0.0 1.0
Ohio -1.0 0.0 1.0
Texas -1.0 0.0 1.0
Oregon -1.0 0.0 1.0

axis='index'를 사용해도 된다.

frame.sub(series3, axis='index')
b d e
Utah -1.0 0.0 1.0
Ohio -1.0 0.0 1.0
Texas -1.0 0.0 1.0
Oregon -1.0 0.0 1.0

19.1.4. 유니버설 함수#

유니버설 함수는 넘파이의 경우와 동일하게 작동한다.

frame = pd.DataFrame(np.random.randn(4, 3), 
                     columns=list('bde'),
                     index=['Utah', 'Ohio', 'Texas', 'Oregon'])
frame
b d e
Utah -0.204708 0.478943 -0.519439
Ohio -0.555730 1.965781 1.393406
Texas 0.092908 0.281746 0.769023
Oregon 1.246435 1.007189 -1.296221

넘파이의 abs() 함수를 적용하면 항목별로 이루어진다.

np.abs(frame)
b d e
Utah 0.204708 0.478943 0.519439
Ohio 0.555730 1.965781 1.393406
Texas 0.092908 0.281746 0.769023
Oregon 1.246435 1.007189 1.296221

시리즈에 대해서도 동일하다.

np.abs(frame['b'])
Utah      0.204708
Ohio      0.555730
Texas     0.092908
Oregon    1.246435
Name: b, dtype: float64

map() 메서드

유니버설 함수가 아닌 함수를 시리즈의 항목별로 적용하려면 map() 메서드를 이용한다.

예를 들어 아래 람다(lambda) 함수는 부동소수점을 소수점 이하 셋째 자리에서 반올림한 값만 보여주도록 한다.

format = lambda x: float(f'{x:.2f}')

시리즈에 적용해보자.

frame['e'].map(format)
Utah     -0.52
Ohio      1.39
Texas     0.77
Oregon   -1.30
Name: e, dtype: float64

데이터프레임의 항목별로 적용이 가능하다.

주의사항

아래 코드를 실행할 때 오류가 발생하면 설치된 판다스가 2.1 이전 버전이라는 의미이며, applymap() 함수를 대신 사용해야 한다. 판다스 2.1 버전부터 applymap() 대신에 map()이 활용된다.

frame.map(format)
b d e
Utah -0.20 0.48 -0.52
Ohio -0.56 1.97 1.39
Texas 0.09 0.28 0.77
Oregon 1.25 1.01 -1.30

apply() 메서드

행 또는 열 단위로 함수를 적용하려면 apply() 메서드를 활용한다. 기본은 열 단위로 함수가 적용되며 반환값이 스칼라 값이면 시리즈가 반환된다.

예를 들어 아래 함수는 최댓값과 최소값의 차이를 반환한다.

f1 = lambda x: x.max() - x.min()

데이터프레임에 적용하면 열 별로 최댓값과 최솟값의 차이를 계산하여 시리즈로 반환한다.

frame.apply(f1)
b    1.802165
d    1.684034
e    2.689627
dtype: float64

행 별로 함수를 적용하려면 axis=1 또는 axis='columls'를 지정해야 한다.

frame.apply(f1, axis='columns')
Utah      0.998382
Ohio      2.521511
Texas     0.676115
Oregon    2.542656
dtype: float64
frame.apply(f1, axis=1)
Utah      0.998382
Ohio      2.521511
Texas     0.676115
Oregon    2.542656
dtype: float64

함수의 반환값이 시리즈이면 apply() 메서드는 데이터프레임을 반환된다. 예를 들어 아래 함수는 최솟값과 최댓값을 갖는 시리즈를 반환한다.

def f2(x):
    return pd.Series([x.min(), x.max()], index=['min', 'max'])

apply() 메서드와 함께 호출하면 열 별로 최댓값과 최솟값을 계산하여 데이터프레임으로 반환한다.

frame.apply(f2)
b d e
min -0.555730 0.281746 -1.296221
max 1.246435 1.965781 1.393406

참고: 시리즈 객체 또한 apply() 메서드를 갖는다. 하지만 기본적으로 map() 메서드처럼 작동한다. map() 메서드보다 좀 더 다야한 기능을 갖지만 여기서는 다루지 않는다.

19.2. 정렬#

행과 열의 인덱스 또는 항목을 대상으로 정렬할 수 있다.

sort_index() 메서드

시리즈의 경우 인덱스를 기준으로 정렬한다.

obj = pd.Series(range(4), index=['d', 'a', 'b', 'c'])
obj
d    0
a    1
b    2
c    3
dtype: int64
obj.sort_index()
a    1
b    2
c    3
d    0
dtype: int64

내림차순으로 정렬하려면 ascending=False 키워드 인자를 함께 사용한다.

obj.sort_index(ascending=False)
d    0
c    3
b    2
a    1
dtype: int64

데이터프레임의 경우 행 또는 열의 인덱스를 기준으로 정렬한다.

frame = pd.DataFrame(np.arange(8).reshape((2, 4)),
                     index=['three', 'one'],
                     columns=['d', 'a', 'b', 'c'])
frame
d a b c
three 0 1 2 3
one 4 5 6 7

기본은 행의 인데스를 기준으로 정렬한다.

frame.sort_index()
d a b c
one 4 5 6 7
three 0 1 2 3

열의 인덱스를 기준으로 정렬하려면 axis=1 또는 axis='columns' 키워드 인자를 사용한다.

frame.sort_index(axis=1)
a b c d
three 1 2 3 0
one 5 6 7 4
frame.sort_index(axis='columns')
a b c d
three 1 2 3 0
one 5 6 7 4

내림차순으로 정렬하려면 ascending=False 키워드 인자를 함께 사용한다.

frame.sort_index(axis=1, ascending=False)
d c b a
three 0 3 2 1
one 4 7 6 5

sort_values() 메서드

지정된 열 또는 행에 속한 값들을 기준으로 정렬할 때 사용한다.

obj = pd.Series([4, 7, -3, 2])
obj
0    4
1    7
2   -3
3    2
dtype: int64
obj.sort_values()
2   -3
3    2
0    4
1    7
dtype: int64

결측치는 맨 나중에 위치시킨다.

obj = pd.Series([4, np.nan, 7, np.nan, -3, 2])
obj
0    4.0
1    NaN
2    7.0
3    NaN
4   -3.0
5    2.0
dtype: float64
obj.sort_values()
4   -3.0
5    2.0
0    4.0
2    7.0
1    NaN
3    NaN
dtype: float64

데이터프레임의 경우 by 키워드 인자를 이용하여 열의 라벨을 지정해야 한다.

frame = pd.DataFrame({'b': [4, 7, -3, 2], 'a': [0, 1, 0, 1]})
frame
b a
0 4 0
1 7 1
2 -3 0
3 2 1

예를 들어 b 열의 값을 기준으로 정렬한다. 물론 동일한 행의 값은 함께 움직인다.

frame.sort_values(by='b')
b a
2 -3 0
3 2 1
0 4 0
1 7 1

여러 열의 값을 기준으로 정렬하려면 라벨의 리스트를 입력한다. 그러면 리스트 항목 순서대로 기준이 정해진다.

예를 들어 아래 코드는 먼저 a 열의 항목들을 순서대로 정렬한 다음에 동등한 값의 경우에는 b 열의 항목들 순서대로 정렬한다.

frame.sort_values(by=['a', 'b'])
b a
2 -3 0
0 4 0
3 2 1
1 7 1

19.3. 행/열 삭제: drop() 메서드#

특정 행 또는 열의 인덱스를 제외한 나머지로 이루어진 시리즈/데이터프레임을 생성할 때 사용한다.

시리즈의 행 삭제

시리즈의 경우 인덱스를 한 개 또는 여러 개 지정하면 나머지로 이루어진 시리즈를 얻는다.

obj = pd.Series(np.arange(5.), index=['a', 'b', 'c', 'd', 'e'])
obj
a    0.0
b    1.0
c    2.0
d    3.0
e    4.0
dtype: float64
new_obj = obj.drop('c')
new_obj
a    0.0
b    1.0
d    3.0
e    4.0
dtype: float64
obj.drop(['d', 'c'])
a    0.0
b    1.0
e    4.0
dtype: float64

원본 시리즈를 직접 건드리지는 않는다.

obj
a    0.0
b    1.0
c    2.0
d    3.0
e    4.0
dtype: float64

inplace=True 키워드 인자

inplace=True 키워드 인자를 이용하면 원본을 수정한다.

obj.drop('c', inplace=True)
obj
a    0.0
b    1.0
d    3.0
e    4.0
dtype: float64

데이터프레임의 행 삭제

데이터프레임의 경우도 기본적으로 행의 인덱스를 기준으로 작동한다.

data = pd.DataFrame(np.arange(16).reshape((4, 4)),
                    index=['Ohio', 'Colorado', 'Utah', 'New York'],
                    columns=['one', 'two', 'three', 'four'])
data
one two three four
Ohio 0 1 2 3
Colorado 4 5 6 7
Utah 8 9 10 11
New York 12 13 14 15
data.drop(['Colorado', 'Ohio'])
one two three four
Utah 8 9 10 11
New York 12 13 14 15

데이터프레임의 열 삭제

열을 기준으로 작동하게 하려면 axis=1로 지정한다.

data.drop('two', axis=1)
one three four
Ohio 0 2 3
Colorado 4 6 7
Utah 8 10 11
New York 12 14 15

axis='columns'로 지정해도 된다.

data.drop(['two', 'four'], axis='columns')
one three
Ohio 0 2
Colorado 4 6
Utah 8 10
New York 12 14

inplace=True 키워드 인자

inplace=True 키워드 인자를 사용하면 이번에도 원본을 수정함에 주의하라.

data.drop('two', axis=1, inplace=True)
data
one three four
Ohio 0 2 3
Colorado 4 6 7
Utah 8 10 11
New York 12 14 15

19.4. 연습문제#

참고: (실습) 데이터프레임 중심 프로그래밍