Post

[Pandas] DataFrame 연산


본 포스팅은 Pandas의 DataFrame 연산에 관하여 정리하였습니다.



with 숫자

데이터프레임과 숫자의 연산도 시리즈의 연산과 동일하게 데이터프레임의 모든 원소에 숫자를 연산시킨다. 더불어, 연산 클래스의 결과로 데이터프레임을 반환시킨다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
df = \
pd.DataFrame({'c0':[10, 20, 30], 
              'c1':[40, 50, 60],
              'c2':[70, 80, 90]}, index = ['r0','r1','r2'])

df_add = df+100
df_sub = df-100
df_div = df/100
df_mul = df*100

print('[ 덧셈 ]\n', df_add, '\n', type(df_add), '\n')
print('[ 뺄셈 ]\n', df_sub, '\n', type(df_sub), '\n')
print('[ 나눗셈 ]\n', df_div, '\n', type(df_div), '\n')
print('[ 곱셈 ]\n', df_mul, '\n', type(df_mul), '\n')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
[ 덧셈 ]
     c0   c1   c2
r0  110  140  170
r1  120  150  180
r2  130  160  190 
<class 'pandas.core.frame.DataFrame'> 

[ 뺄셈 ]
    c0  c1  c2
r0 -90 -60 -30
r1 -80 -50 -20
r2 -70 -40 -10 
<class 'pandas.core.frame.DataFrame'> 

[ 나눗셈 ]
     c0   c1   c2
r0  0.1  0.4  0.7
r1  0.2  0.5  0.8
r2  0.3  0.6  0.9 
<class 'pandas.core.frame.DataFrame'> 

[ 곱셈 ]
      c0    c1    c2
r0  1000  4000  7000
r1  2000  5000  8000
r2  3000  6000  9000 
<class 'pandas.core.frame.DataFrame'>


with 데이터프레임

데이터프레임끼리의 연산의 경우 동일한 행과 열에 위치한 원소끼리 계산되어진다. 시리즈 끼리의 계산과 마찬가지로 어느 한쪽에 동일 위치의 원소가 존재하지 않을 경우 NaN으로 처리된다.

다음의 예제에서는 각 데이터프레임의 동일 행과 열( [c1행, r1열], [c2행, r2열], [c3행, r1열], [c3행, r2열] )에 위치된 원소의 경우 연산 값을 반환하며, 그외의 동일한 행과 열이 아닐 경우에는 NaN으로 처리되어지며, 두 데이터프레임의 합집합인 4행 4열의 데이터프레임을 결과로 반환한 것을 확인할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
df1 = \
pd.DataFrame({'c0':[10, 20, 30], 
              'c1':[40, 50, 60],
              'c2':[70, 80, 90]}, index = ['r0','r1','r2'])

df2 = \
pd.DataFrame({'c1':[100, 200, 300], 
              'c2':[400, 500, 600],
              'c3':[700, 800, 900]}, index = ['r1','r2','r3'])

df_add = df1+df2
df_sub = df1-df2
df_div = df1/df2
df_mul = df1*df2

print('[ 덧셈 ]\n', df_add, '\n')
print('[ 뺄셈 ]\n', df_sub, '\n')
print('[ 나눗셈 ]\n', df_div, '\n')
print('[ 곱셈 ]\n', df_mul, '\n')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
[ 덧셈 ]
    c0     c1     c2  c3
r0 NaN    NaN    NaN NaN
r1 NaN  150.0  480.0 NaN
r2 NaN  260.0  590.0 NaN
r3 NaN    NaN    NaN NaN 

[ 뺄셈 ]
    c0     c1     c2  c3
r0 NaN    NaN    NaN NaN
r1 NaN  -50.0 -320.0 NaN
r2 NaN -140.0 -410.0 NaN
r3 NaN    NaN    NaN NaN 

[ 나눗셈 ]
    c0   c1    c2  c3
r0 NaN  NaN   NaN NaN
r1 NaN  0.5  0.20 NaN
r2 NaN  0.3  0.18 NaN
r3 NaN  NaN   NaN NaN 

[ 곱셈 ]
    c0       c1       c2  c3
r0 NaN      NaN      NaN NaN
r1 NaN   5000.0  32000.0 NaN
r2 NaN  12000.0  45000.0 NaN
r3 NaN      NaN      NaN NaN


with 시리즈

데이터프레임은 시리즈와도 연산을 수행할 수 있다. 데이터프레임의 행(axis=1)축을 기준으로 모든 행의 열 이름과 시리즈의 인덱스 이름이 동일한 위치인 원소끼리 계산되어진다. 더불어, 다른 계산들과 마찬가지로 짝이 없을 경우 NaN으로 처리되어지며, 반환된 결과로 데이터프레임에 없던 열이 추가되어 데이터프레임 객체로 반환되어진다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
df = \
pd.DataFrame({'c0':[10, 20, 30], 
              'c1':[40, 50, 60],
              'c2':[70, 80, 90]}, index = ['r0','r1','r2'])

sr = \
pd.Series({'c1':100, 'c2':200, 'c3':300})

df_add = df+sr
df_sub = df-sr
df_div = df/sr
df_mul = df*sr

print('[ 덧셈 ]\n', df_add, '\n')
print('[ 뺄셈 ]\n', df_sub, '\n')
print('[ 나눗셈 ]\n', df_div, '\n')
print('[ 곱셈 ]\n', df_mul, '\n')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[ 덧셈 ]
    c0     c1     c2  c3
r0 NaN  140.0  270.0 NaN  -> c1의 모든 행에 100이 더해진 것을 확인할 수 있다. 
r1 NaN  150.0  280.0 NaN  -> c2의 모든 행에 200이 더해진 것을 확인할 수 있다.
r2 NaN  160.0  290.0 NaN 

[ 뺄셈 ]
    c0    c1     c2  c3
r0 NaN -60.0 -130.0 NaN
r1 NaN -50.0 -120.0 NaN
r2 NaN -40.0 -110.0 NaN 

[ 나눗셈 ]
    c0   c1    c2  c3
r0 NaN  0.4  0.35 NaN
r1 NaN  0.5  0.40 NaN
r2 NaN  0.6  0.45 NaN 

[ 곱셈 ]
    c0      c1       c2  c3
r0 NaN  4000.0  14000.0 NaN
r1 NaN  5000.0  16000.0 NaN
r2 NaN  6000.0  18000.0 NaN


메소드를 이용한 연산

시리즈와 동일한 메소드를 이용하여 데이터프레임의 연산을 수행할 수 있다. 물론, 시리즈와 데이터프레임의 연산에도 동일한 메소드를 이용할 수 있다.

  • 객체 순서의 상관없이 항상 데이터프레임 객체를 반환한다.
  • fill_value 옵션을 이용해서 연산의 결과가 NaN으로 처리되는 것을 방지할 수 있다.
    • 데이터프레임과 시리즈의 연산에서는 fill_value 옵션을 사용하면 에러가 반환된다.
    • 따라서, 데이터프레임과 시리즈의 연산에서 fill_value 옵션을 이용하여 NaN 을 방지하길 원할 경우, 시리즈 객체를 to_frame메소드를 이용해서 데이터프레임으로 변환시켜 연산해야한다(이때, Transpose가 필요할 수도 있다.).
    • 하지만, fill_value는 완전히 새로 생겨난 데이터프레임 셀에 위치한 NaN의 관해서는 연산으로 생겨난 셀이 아니기 때문에 처리하지 못한다.
  • 데이터프레임과 시리즈의 연산에서는 axis 옵션을 이용해서 연산의 축 방향을 자유롭게 지정할 수 있다.
1
2
3
4
5
# 메소드를 이용한 데이터프레임끼리의 연산
[데이터프레임 객체1].[add|sub|mul|div]([데이터프레임 객체2], fill_value=..., axis=...)

# 메소드를 이용한 데이터프레임과 시리즈의 연산
[데이터프레임 객체1|시리즈 객체1].[add|sub|mul|div]([데이터프레임 객체2|시리즈 객체2], axis=...)


먼저 데이터프레임끼리의 연산 예제를 확인해보자.

1
2
3
4
5
6
7
8
9
10
11
12
df1 = \
pd.DataFrame({'c0':[10, 20, 30], 
              'c1':[40, 50, 60],
              'c2':[70, 80, 90]}, index = ['r0','r1','r2'])

df2 = \
pd.DataFrame({'c0':[100, 200, 300], 
              'c1':[400, 500, 600],
              'c2':[700, 800, 900]}, index = ['r1','r2','r3'])

print('[ fill_value옵션 X ]\n', df1.add(df2), '\n')
print('[ fill_value옵션 O ]\n', df1.add(df2, fill_value = 10))
1
2
3
4
5
6
7
8
9
10
11
12
13
[ fill_value옵션 X ]
       c0     c1     c2
r0    NaN    NaN    NaN
r1  120.0  450.0  780.0
r2  230.0  560.0  890.0
r3    NaN    NaN    NaN 

[ fill_value옵션 O ]
       c0     c1     c2
r0   20.0   50.0   80.0
r1  120.0  450.0  780.0
r2  230.0  560.0  890.0
r3  310.0  610.0  910.0


다음은 데이터프레임과 시리즈끼리의 연산이다. 동일 행과 열에 위치한 원소끼리 연산되는 것을 확인할 수 있으며, 동일한 행과 열이 없을 경우에는 NaN으로 처리되는 것을 확인할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
df = \
pd.DataFrame({'c0':[10, 20, 30], 
              'c1':[40, 50, 60],
              'c2':[70, 80, 90]}, index = ['r0','r1','r2'])

sr = \
pd.Series({'c1':100, 'c2':200, 'c3':300,
           'r1':400, 'r2':500, 'r3':600})

df_axis1 = df.add(sr) # axis = 1은 default다
df_axis0 = df.add(sr, axis = 0)

print('[ axis = 1 (default) ]\n', df_axis1, '\n')
print('[ axis = 0 ]\n', df_axis0)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[ axis = 1 (default) ]
    c0     c1     c2  c3  r1  r2  r3
r0 NaN  140.0  270.0 NaN NaN NaN NaN
r1 NaN  150.0  280.0 NaN NaN NaN NaN
r2 NaN  160.0  290.0 NaN NaN NaN NaN 

[ axis = 0 ]
       c0     c1     c2
c1    NaN    NaN    NaN
c2    NaN    NaN    NaN
c3    NaN    NaN    NaN
r0    NaN    NaN    NaN
r1  420.0  450.0  480.0
r2  530.0  560.0  590.0
r3    NaN    NaN    NaN


하지만, 메소드를 이용한 시리즈와 데이터프레임의 연산에서 fill_value 옵션을 이용할 경우 아직 구현되지 않았다는 의미의 에러(NotImplementedError)를 반환하는 것을 확인할 수 있다.

1
2
3
4
5
try:
    df_axisError = df.add(sr, fill_value = 0)
    print('[ Error ]\n', df_axisError)
except NotImplementedError:
    print('NotImplementedError 에러 반환')
1
NotImplementedError 에러 반환


하지만, 적절히 변환을 시도해서 데이터프레임과 시리즈의 연산에서 발생된 NaNfill_value옵션으로 방지할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
df = \
pd.DataFrame({'c0':[10, 20, 30], 
              'c1':[40, 50, 60],
              'c2':[70, 80, 90]}, index = ['r0','r1','r2'])

sr = \
pd.Series({'c1':100, 'c2':200, 'c3':300})

# 시리즈의 이름을 지정해준다.
sr.rename('r0', inplace = True)

# 시리즈를 데이터프레임객체로 변환시키고 치환을 진행한다.
# 치환의 경우 시리즈의 인덱스 값에 따라서 이용하지 않아도 된다.
sr_to_df = sr.to_frame().T

# 모든 행의 특정 열 이름에 위치한 원소의 값들과 연산시키기 위해
# 행을 증가시키고, 비어있는 값을 이전 행 값으로 모두 채운다.
sr_to_df = sr_to_df.reindex(index=['r0', 'r1', 'r2']).fillna(method = 'ffill')

print('[ 시리즈의 데이터프레임 변환 결과 ]\n', sr_to_df)

# 데이터프레임객체와 시리즈객체 끼리의 연산이므로 fill_value를 사용할 수 없다.
normal_calc_result = df.add(sr)
# 데이터프레임객체 끼리의 연산이므로 fil_value옵션을 이용할 수 있다.
proc_calc_reuslt = df.add(sr_to_df, fill_value=0)

print('\n=======================비교=======================\n')
print('[ sr을 df와 그냥 연산했을 경우 ]\n',normal_calc_result, '\n')
print('[ sr을 df로 전처리시켜서 연산을 진행했을 경우]\n',  proc_calc_reuslt)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[ 시리즈의 데이터프레임 변환 결과 ]
       c1     c2     c3
r0  100.0  200.0  300.0
r1  100.0  200.0  300.0
r2  100.0  200.0  300.0

=======================비교=======================

[ sr을 df와 그냥 연산했을 경우 ]
    c0     c1     c2  c3
r0 NaN  140.0  270.0 NaN       -> NaN으로 처리되어 출력됨
r1 NaN  150.0  280.0 NaN
r2 NaN  160.0  290.0 NaN 

[ sr을 df로 전처리시켜서 연산을 진행했을 경우]
      c0     c1     c2     c3
r0  10.0  140.0  270.0  300.0  -> 공통된 위치의 원소가 없을 경우, 0을 더해서 처리됨
r1  20.0  150.0  280.0  300.0
r2  30.0  160.0  290.0  300.0
This post is licensed under CC BY 4.0 by the author.