Post

[Pandas] 함수매핑2 : applymap, pipe


이전 포스팅에 이어서 dataframe의 applymappipe메소드에 대해서 정리하였습니다.



applymap 메소드

applymapapply와 비슷한 기능을 수행하는 함수이다.
하지만, applymapapply와 달리 dataframe의 요소 별로 매핑 함수를 적용할 수 있다는 장점이 있다.
(Apply a function to a Dataframe elementwise)


applymap의 기본 format은 다음과 같다.

1
[DataFrame 객체].applymap(mapping_func)
  • mapping_func
    • 데이터 셀에 1:1 대응하는 결과를 리턴하는 함수
    • 따라서, applymap메소드는 apply메소드와 달리 집계 함수를 사용할 수 없다.
      (물론, 기입은 할 수 있고 에러도 발생하지 않지만, 집계값이 반환되지 않는다.)


다음은 DataFrame의 일부 요소들을 양수에서 음수로 변환하는 예제이다.

1
2
3
4
5
6
7
8
9
df = pd.DataFrame([[1, 2, 3], [5, 3, 2], [3, 4, 1]],
                  index = ['row1', 'row2', 'row3'],
                  columns = ['col1', 'col2', 'col3'])

copy_df0 = df.copy()
copy_df0.loc['row2':'row3', 'col2':'col3'] = \
copy_df0.loc['row2':'row3', 'col2':'col3'].applymap(lambda x: -x)

print(copy_df0)
1
2
3
4
      col1  col2  col3
row1     1     2     3
row2     5    -3    -2
row3     3    -4    -1


그리고, 다음은 정수형 DataFrame 요소들을 실수로 변환하는 예제이다.

1
2
3
4
5
6
7
8
df = pd.DataFrame([[1, 2, 3], [5, 3, 2], [3, 4, 1]],
                  index = ['row1', 'row2', 'row3'],
                  columns = ['col1', 'col2', 'col3'])

copy_df1 = df.copy()
copy_df1 = copy_df1.applymap(float)

print(copy_df1)
1
2
3
4
      col1  col2  col3
row1   1.0   2.0   3.0
row2   5.0   3.0   2.0
row3   3.0   4.0   1.0


applymapapply와 달리 DataFrame의 집계에 사용하기 적합하지 않으며
주로 위의 예제에서 확인할 수 있듯이 DataFrame의 요소를 변환(Transformation)하는데 적합하다.

그리고, 아래의 예제에서 볼 수 있듯이 두 메소드 모두 하나의 값을 반환하는 함수를 입력받을 경우에는 동일한 결과를 출력하는 것을 확인할 수 있다. 이때, 주로 performance가 더 좋은 것은 applymap이지만, 데이터의 상황에 따라 다르므로 두 메소드를 모두 적용해보고 더 나은 메소드를 고르는 것을 추천한다.

1
2
3
4
5
6
7
8
9
10
11
12
df = pd.DataFrame([[1, 2, 3], [5, 3, 2], [3, 4, 1]],
                  index = ['row1', 'row2', 'row3'],
                  columns = ['col1', 'col2', 'col3'])

func = lambda x : x+10

result_df0 = df.apply(func, axis=0)
result_df1 = df.applymap(func)

print(result_df0)
print()
print(result_df1)
1
2
3
4
5
6
7
8
9
      col1  col2  col3
row1    11    12    13
row2    15    13    12
row3    13    14    11

      col1  col2  col3
row1    11    12    13
row2    15    13    12
row3    13    14    11



pipe 메소드

pipe메소드는 사용하는 함수가 반환하는 리턴 값에 따라서 반환하는 객체의 종류가 결정된다.
DataFrame을 반환할 수 있고, Series를 반환할 수 있으며, 그리고 하나의 값을 반환할 수도 있다는 의미이다.


이렇게 범용적인 pipe메소드는 pandas의 chainable function이다.
즉, 해당 메소드를 이용하여 객체에 연속적으로 함수를 적용할 수 있다는 의미이다.


다음은 pipe메소드의 기본 format이다.

1
[ DataFrame 객체 | Series 객체 ].pipe(func, *args, **kwargs)
  • func : Series혹은 DataFrame에 적용가능한 함수를 받는다.
  • *args : func에 전달될 arguments를 받는다.
  • **kwargs : func에 전달될 keyword arguments를 전달 받는다.


아래는 pipe메소드를 이용하여 아이템 판매 로그에서 특정 판매 개수를 만족하는 아이템의 총 판매 금액을 반환하는 코드이다.
아래와 같이 사용하는 경우는 없겠지만 pipe를 이해하기 위한 예제이기에 어떻게 pipe를 사용하는지만 보도록 하자.

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
# 다음의 DataFrame은 판매 아이템에 대한 로그이며, 
# 각 column은 판매된 아이템의 이름, 개수, 그리고 아이템의 판매 총 가격을 의미한다.
df = \
pd.DataFrame([['item A', 1, 20900],
              ['item B', 1, 3200],
              ['item C', 5, 100],
              ['item D', 1, 4000],
              ['item E', 3, 5000]], columns = ['name', 'cnt', 'price'])

# 특정 판매 개수를 만족하는 아이템 판매 기록을 필터링한 DataFrame을 반환합니다.
def cnt_filter(df, a):
    cond = df.cnt == a
    filter_df = df.loc[cond]
    return filter_df

# 아이템 가격을 Series로 반환합니다.
def get_sr(df, col_name):
    return df[col_name]

# Series의 Value를 모두 더합니다.
def sum_val(sr):
    return sr.sum()

x = sum_val(get_sr(cnt_filter(df, 1), 'price')) # 일반
y = df.pipe(cnt_filter, 1).pipe(get_sr, 'price').pipe(sum_val) # pipe 이용

print(x, y)
1
28100 28100

위에서 볼 수 있듯이 함수안에 객체를 넣어서 처리하는 방식보다 pipe 메소드를 이용하여 순차적으로 처리하는 방식이 더 직관적이고 알기 쉬움을 알 수 있다.



References

This post is licensed under CC BY 4.0 by the author.