달나라 노트

Python Pandas : DataFrame.assign 본문

Python/Python Pandas

Python Pandas : DataFrame.assign

CosmosProject 2020. 11. 2. 16:32
728x90
반응형

 

 

 

DataFrame.assign

assign은 apply와 비슷하게 내가 원하는 로직을 DataFrame의 어떤 컬럼에 적용시킨 후, 새로운 컬럼을 추가하여 변경된 DataFrame을 반환합니다.

 

먼저 test용 DataFrame을 생성합니다.

import pandas as pd

dict_item = {
    'item_id': [1, 2, 3, 4],
    'item_name': ['a', 'b', 'c', 'd'],
    'price': [1000, 2000, 3000, 4000]
}
df_item = pd.DataFrame(dict_item)
print(df_item)
print(type(df_item))
 
 - Output
   item_id item_name  price
0        1         a   1000
1        2         b   2000
2        3         c   3000
3        4         d   4000

<class 'pandas.core.frame.DataFrame'>



이제 assign을 이용해 기존 price컬럼의 값들을 모두 2배한 DataFrame을 만들어 보겠습니다.

import pandas as pd

dict_item = {
    'item_id': [1, 2, 3, 4],
    'item_name': ['a', 'b', 'c', 'd'],
    'price': [1000, 2000, 3000, 4000]
}
df_item = pd.DataFrame(dict_item)

df_item_new = df_item.assign(
    price=lambda x: x['price'] * 2
)
print(df_item)
print(df_item_new)
 


 - Output of df_item
   item_id item_name  price
0        1         a   1000
1        2         b   2000
2        3         c   3000
3        4         d   4000

 - Output of df_item_new
   item_id item_name  price
0        1         a   2000
1        2         b   4000
2        3         c   6000
3        4         d   8000

위 코드를 해석해봅시다.

 

1. df_item_new = df_item.assign -> 먼저 df_item에 assign을 이용해 어떤 작업을 한 후의 결과를 df_item_new라는 변수에 저장할 것입니다.

 

2. price=lambda x: -> price라는 컬럼에 lambda를 적용시켜 저장하며 여기서 말하는 x는 assign이 적용된 df_item을 의미합니다.

 

3. x['price'] * 2 -> x['price']는 x(df_item)의 price컬럼을 의미합니다. 즉, df_item의 price 컬럼에 있는 값들에 2를 곱한다는 뜻입니다.

Output of df_item_new 부분에서 보시는 것처럼 price 컬럼의 데이터가 모두 2배가 되었음을 알 수 있습니다.

근데 - Output of df_item 부분을 보시면 원본 DataFrame인 df_item은 그대로임을 알 수 있습니다.

이처럼 assign은 어떤 함수를 적용시켜 기존 DataFrame의 복사본에 변경을 가한 후 결과 데이터를 새로운 변수에 저장하기 때문에 원본 DataFrame에는 변화가 없습니다.



또한 lambda에 컬럼을 명시해줄 때 x['price']가 아닌 x.price처럼 마침표를 이용하여 대상 컬럼을 명시해줄 수도 있습니다.결과는 동일합니다.

import pandas as pd

dict_item = {
    'item_id': [1, 2, 3, 4],
    'item_name': ['a', 'b', 'c', 'd'],
    'price': [1000, 2000, 3000, 4000]
}
df_item = pd.DataFrame(dict_item)

df_item_new = df_item.assign(
    price=lambda x: x.price * 2
)
print(df_item)
print(df_item_new)



- Output of df_item
   item_id item_name  price
0        1         a   1000
1        2         b   2000
2        3         c   3000
3        4         d   4000

 - Output of df_item_new
   item_id item_name  price
0        1         a   2000
1        2         b   4000
2        3         c   6000
3        4         d   8000



 

 

 

자 그러면 assign의 내부에 있던 lambda x에서 x에는 대체 어떤 데이터가 전달되는걸까요?

아래 코드를 봅시다.

 

import pandas as pd

dict_item = {
    'item_id': [1, 2, 3, 4],
    'item_name': ['a', 'b', 'c', 'd'],
    'price': [1000, 2000, 3000, 4000]
}
df_item = pd.DataFrame(dict_item)

df_item_new = df_item.assign(
    price1=lambda x: print(x)
)


-- Result
   item_id item_name  price
0        1         a   1000
1        2         b   2000
2        3         c   3000
3        4         d   4000

 

위 코드는 assign 내부에서 lambda x의 x에 전달되는 데이터가 뭔지 보기 위해 print를 사용했습니다.

그 결과를 보니 df_item 전체가 전달되었습니다.

 

assign 내부에서 사용되는 lambda 식의 x에는 assign이 적용된 대상이 할당됩니다.

위 코드를 보면 df_item.assign( 이런식으로 assign이 df_item에 적용되었죠.

따라서 assign 내부의 lambda 식에서 쓰이는 x에는 assign이 적용된 DataFrame인 df_item 전체가 전달되는 것입니다.

 

 

이렇게 lambda x에 전달되는 값이 DataFrame이기 때문에 당연히 DataFrame에 사용할 수 있는 method들을 x에도 사용할 수 있습니다.

 

import pandas as pd

dict_item = {
    'item_id': [1, 2, 3, 4],
    'item_name': ['a', 'b', 'c', 'd'],
    'price': [1000, 2000, 3000, 4000]
}
df_item = pd.DataFrame(dict_item)

df_item_new = df_item.assign(
    price1=lambda x: print(x),
    price2=lambda x: print(x.loc[:, 'item_id']),
    price3=lambda x: print(x.loc[:, 'item_name']),
    price4=lambda x: print(x.loc[:, 'price'])
)



-- Result
   item_id item_name  price
0        1         a   1000
1        2         b   2000
2        3         c   3000
3        4         d   4000

0    1
1    2
2    3
3    4
Name: item_id, dtype: int64

0    a
1    b
2    c
3    d
Name: item_name, dtype: object

0    1000
1    2000
2    3000
3    4000
Name: price, dtype: int64

 

위 코드를 보시면 lambda x 내에서 x에 loc를 적용하여 각 컬럼의 데이터를 출력한 결과입니다.

lambda x의 x에는 DataFrame이 전달되므로 당연히 x에도 loc method를 사용할 수 있죠.

따라서 마치 일반 DataFrame에 loc를 적용한 것 처럼 하나의 컬럼 전체가 Series의 형태로 출력됩니다.

 

 

 

 

import pandas as pd

dict_item = {
    'item_id': [1, 2, 3, 4],
    'item_name': ['a', 'b', 'c', 'd'],
    'price': [1000, 2000, 3000, 4000]
}
df_item = pd.DataFrame(dict_item)

df_item_new = df_item.assign(
    price1=lambda x: print(x),
    price2=lambda x: print(x['item_id']),
    price3=lambda x: print(x['item_name']),
    price4=lambda x: print(x['price'])
)


-- Result
   item_id item_name  price
0        1         a   1000
1        2         b   2000
2        3         c   3000
3        4         d   4000

0    1
1    2
2    3
3    4
Name: item_id, dtype: int64

0    a
1    b
2    c
3    d
Name: item_name, dtype: object

0    1000
1    2000
2    3000
3    4000
Name: price, dtype: int64

 

굳이 loc method를 사용하지 않고도 컬럼 이름만 명시해서 컬럼을 추출할 수도 있습니다.

 

 

 

 

 

 

import pandas as pd

dict_item = {
    'item_id': [1, 2, 3, 4],
    'item_name': ['a', 'b', 'c', 'd'],
    'price': [1000, 2000, 3000, 4000]
}
df_item = pd.DataFrame(dict_item)

df_item_new = df_item.assign(
    price1=lambda x: print(x),
    price2=lambda x: print(x.item_id),
    price3=lambda x: print(x.item_name),
    price4=lambda x: print(x.price)
)



-- Result
   item_id item_name  price
0        1         a   1000
1        2         b   2000
2        3         c   3000
3        4         d   4000

0    1
1    2
2    3
3    4
Name: item_id, dtype: int64

0    a
1    b
2    c
3    d
Name: item_name, dtype: object

0    1000
1    2000
2    3000
3    4000
Name: price, dtype: int64

 

또는 위처럼 단순히 x.item_id 와 같이 .을 이용해서 특정 컬럼에 접근할 수도 있습니다.

 

 

 

 

 

 

 

 

그러면 assign이 작동하는 방식을 대략적으로 알아봅시다.

 

df_item_new = df_item.assign(
    price=lambda x: x.price * 2
)

 

assign의 적용 부분만 따로 떼어봤습니다.

 

- df_item.assign()

일단 이걸 보면 assign method가 df_item이라는 DataFrame에 적용되고 있습니다.

 

 

- price=lambda x: x.price * 2

그리고 이 부분이 핵심입니다. 이 부분을 다시 쓰면 아래와 비슷합니다.

df_item.loc[:, 'price'] = df_item.loc[:, 'price'] * 2

즉, df_item의 price 컬럼에 2를 곱한 값을 price컬럼에 넣으라는 의미입니다.

 

lambda x에서 x는 assign이 적용된 DataFrame을 받아온다고 했습니다.

즉, price=lambda x: x.price * 2라는 것은 x의 price 컬럼 전체에 2를 곱해서 그걸 price라는 컬럼에 다시 넣어라 라는 의미입니다.

이렇게 assign 내부에서는 컬럼단위의 연산이 진행됩니다.

 

 

- df_item_new = df_item.assign(

그리고 이렇게 연산이 끝난 df_item DataFrame을 return합니다.

그래서 위 예시에서는 df_item_new라는 새로운 변수에 assign을 적용한 df_item DataFrame을 할당하게 됩니다.

 

 

 

df_item_new = df_item.assign(
    price=lambda row: row.price * 2
)
df_item_new = df_item.assign(
    price=lambda df: df.price * 2
)

lambda의 변수 x는 위처럼 마음대로 바꿔도 됩니다.

 

 

 

 

 

 

 

 

 


 

 

 

 

 

이번엔 assign의 성능적인 측면을 알아봅시다.

assign의 성능을 알아보기 위해 비교할 method는 apply입니다.

 

apply method 관련 내용 = https://cosmosproject.tistory.com/8

 

Python Pandas : DataFrame.apply & DataFrame.applymap

DataFrame.apply apply는 DataFrame에 존재하는 컬럼들의 데이터를 받아 특정 함수를 적용시켜서 해당 함수에 있는 로직대로 데이터를 반환한 데이터를 특정 컬럼에 넣어주는 기능을 가집니다. 마치 엑

cosmosproject.tistory.com

 

assign과 마찬가지로 apply도 DataFrame의 값에 접근하여 원하는 연산을 수행한 후 그 결과를 기존 column이나 새로운 column에 할당하는 등의 기능을 가집니다.

 

즉, 두 method는 굉장히 비슷한 기능을 가지고있죠.

 

그럼 과연 성능적인 측면에서는 어떠할까요?

 

아래 두 코드를 봅시다.

 

 

import pandas as pd

dict_item = {
    'item_id': [1, 2, 3, 4],
    'item_name': ['a', 'b', 'c', 'd'],
    'price': [1000, 2000, 3000, 4000]
}
df_item = pd.DataFrame(dict_item)

df_item.loc[:, 'price'] = df_item.assign(
    price=lambda x: x['price'] * 2
)

print(df_item_new)


-- Result
   item_id item_name  price
0        1         a   2000
1        2         b   4000
2        3         c   6000
3        4         d   8000

 

import pandas as pd

dict_item = {
    'item_id': [1, 2, 3, 4],
    'item_name': ['a', 'b', 'c', 'd'],
    'price': [1000, 2000, 3000, 4000]
}
df_item = pd.DataFrame(dict_item)

df_item.loc[:, 'price'] = df_item.apply(
    lambda x: x['price'] * 2,
    axis=1
)

print(df_item)


-- Result
   item_id item_name  price
0        1         a   2000
1        2         b   4000
2        3         c   6000
3        4         d   8000

 

위 2개의 코드는 df_item의 price column에 있는 값에 2를 곱한 후 그걸 다시 price 컬럼에 할당하는 코드입니다.

2개의 코드는 완전히 동일한 기능을 수행하며 결과도 동일합니다.

 

근데 위 2개 방식의 성능 차이는 분명히 존재합니다.

 

위 기능을 수행할 때 assign과 apply 중 어떤 것이 더 성능이 좋을지 짐작하려면 각 method 내부의 lambda x 부분에서 x에 어떤 값이 전달되는지를 보면 됩니다.

 

아래 코드를 보시죠.

 

import pandas as pd

dict_item = {
    'item_id': [1, 2, 3, 4],
    'item_name': ['a', 'b', 'c', 'd'],
    'price': [1000, 2000, 3000, 4000]
}
df_item = pd.DataFrame(dict_item)

df_item.loc[:, 'price'] = df_item.assign(
    price=lambda x: print(x['price'])
)


-- Result
0    1000
1    2000
2    3000
3    4000
Name: price, dtype: int64

 

먼저 assign method입니다.

assign 내부의 lambda x식에서 x에 어떤 값이 전달되는지는 위에서 봤죠.

따라서 x에는 assign이 적용된 DataFrame이 전달되고, x['price']를 print하면 전달된 DataFrame의 price column이 출력됩니다.

 

 

 

 

import pandas as pd

dict_item = {
    'item_id': [1, 2, 3, 4],
    'item_name': ['a', 'b', 'c', 'd'],
    'price': [1000, 2000, 3000, 4000]
}
df_item = pd.DataFrame(dict_item)

df_item.loc[:, 'price'] = df_item.apply(
    lambda x: print(x['price']),
    axis=1
)


-- Result
1000
2000
3000
4000

 

이번엔 apply입니다.

apply method 내부의 lambda x 식에서 x['price']값을 출력했더니 df_item의 price column값이 나오는게 아니라 price column에 존재하는 값들이 1개씩 출력됩니다.

1000, 2000, 3000, 4000이 모두 출력됐다고해서 이게 assign처럼 DataFrame에서 price 컬럼 전체를 Series의 형태로 가져온 것이 아닙니다.

price 컬럼에 있는 1000, 2000, 3000, 4000이라는 값에 하나씩 접근한 것입니다.

 

혹시 좀 감이 오시나요?

apply 내부에서는 lambda x 식을 통해 모든 하나하나의 행에 접근을 하여 연산을 진행합니다.

 

이해를 돕기위해 apply에 관해 하나만 더 봅시다.

 

import pandas as pd

dict_item = {
    'item_id': [1, 2, 3, 4],
    'item_name': ['a', 'b', 'c', 'd'],
    'price': [1000, 2000, 3000, 4000]
}
df_item = pd.DataFrame(dict_item)

df_item.loc[:, 'price'] = df_item.apply(
    lambda x: print(x),
    axis=1
)


-- Result
item_id         1
item_name       a
price        1000
Name: 0, dtype: object
item_id         2
item_name       b
price        2000
Name: 1, dtype: object
item_id         3
item_name       c
price        3000
Name: 2, dtype: object
item_id         4
item_name       d
price        4000
Name: 3, dtype: object

 

apply 내부의 lambda x에서 x를 출력한 결과입니다.

 

결과를 보면 총 4개의 세트가 출력됐는데 그 내용을 보면 df_item의 하나하나의 행 정보가 출력된걸 볼 수 있습니다.

 

이제 좀 확실해지시죠.

 

assign 내부의 lambda x에서 x는 assign이 적용된 DataFrame 전체를 받아 이것을 실제 DataFrame처럼 다룹니다. 따라서 컬럼 전체에 접근해서 어떤 연산을 진행하죠.

 

apply 내부의 lambda x에서 x는 apply가 적용된 DataFrame 전체를 받는 것이 아니라 DataFrame에 존재하는 행을 하나씩 받습니다.

 

위 내용은 성능에 있어서 상당히 중요한 의미를 지닙니다.

 

현재까지 사용한 예시들은 사실 DataFrame의 크기가 작습니다.

행의 개수가 4개이죠.

 

근데 실제 상황에서는 엄청나게 많은 행(row)을 가진 DataFrame을 다루는 경우가 많을겁니다.

행이 200만개인 DataFrame을 다룬다고 생각해봅시다.

 

행이 200만개인 DataFrame에 존재하는 columnA와 columnB의 합을 구해서 새로운 columnC에 넣는 연산을 진행한다고 할 때 

assign을 이용하면 columnA 전체의 값을 가져오고 columnB의 전체 값을 가져와서 덧셈을 할겁니다.

 

근데 apply를 이용하면 DataFrame의 행에 하나씩 접근하여 아래와 같이 행마다 연산을 할 것입니다.

1 -> 1행에 있는 columnA 값 + 1행에 있는 columnB값

2 -> 2행에 있는 columnA 값 + 2행에 있는 columnB값

3 -> 3행에 있는 columnA 값 + 3행에 있는 columnB값

...

200만 -> 200만행에 있는 columnA값 + 200만행에 있는 columnB값

 

즉, apply는 DataFrame에 존재하는 모든 행에 하나씩 접근하기 때문에 느려질 수 밖에 없습니다.

따라서 기본적으로 대부분의 경우에서 DataFrame에 연산을 진행할 때에는 assign을 사용하는게 성능적인 측면에서 유리합니다.

 

 

 

 

그러면 무조건 assign이 더 좋은걸까요?

아닙니다. apply만의 장점이 따로 있습니다.

아래 코드를 보시죠.

 

import pandas as pd

dict_item = {
    'item_id': [1, 2, 3, 4],
    'item_name': ['a', 'b', 'c', 'd'],
    'price': [1000, 2000, 3000, 4000]
}
df_item = pd.DataFrame(dict_item)

df_item.loc[:, 'price_flag'] = df_item.apply(
    lambda x: 'high' if x['price'] >= 3000 else 'low',
    axis=1
)

print(df_item)


-- Result
   item_id item_name  price price_flag
0        1         a   1000        low
1        2         b   2000        low
2        3         c   3000       high
3        4         d   4000       high

 

apply method를 이용해서 price가 3000 이상이면 high라는 flag를, 3000원 미만이면 low라는 flag를 달아주는 코드를 작성했습니다.

 

 

그러면 위와 동일한 코드를 assign을 이용해서 작성해봅시다.

 

import pandas as pd

dict_item = {
    'item_id': [1, 2, 3, 4],
    'item_name': ['a', 'b', 'c', 'd'],
    'price': [1000, 2000, 3000, 4000]
}
df_item = pd.DataFrame(dict_item)

df_item.loc[:, 'price_flag'] = df_item.assign(
    price_flag=lambda x: 'high' if x['price'] >= 3000 else 'low'
)


-- Result
ValueError: The truth value of a Series is ambiguous. Use a.empty, a.bool(), a.item(), a.any() or a.all().

 

분명히 코드만 보면 apply와 비슷한 것 같습니다.

단순히 assign으로 변경한건데 에러가 발생합니다.

왜일까요?

이것 또한 lambda x 식에서 x에 전달되는 값이 무엇인지를 다시 생각해보면 알 수 있습니다.

 

apply의 lambda x에서 x에는 DataFrame의 하나의 행 정보가 할당된다고 했습니다.

따라서 apply의 lambda x 식에서 x['price'] 처럼 참조하게 되면 1행에 있는 price column의 값을 참조하는 것과 동일합니다.

즉, 어떤 DataFrame의 행 또는 컬럼 전체가 아니라 특정 위치에 있는 그 값을 참조하는 것이죠.

따라서 if ~ else 구문 등에 적용할 수 있습니다.

 

반면에 assign의 lambda x에서 x에는 DataFrame 전체가 할당된다고 했습니다.

따라서 assign의 lambda x 식에서 x['price'] 처럼 참조를 하면 이것은 DataFrame에 있는 price 컬럼 전체를 추출할 Series입니다.

즉, x['price'] >= 3000 처럼 연산을 하게되면 price 컬럼에 있는 하나의 값이 3000이상인지 아닌지가 아니라 price 컬럼 전체에 있는 Series에 대한 연산이 적용되는 것입니다.

 

 

 

import pandas as pd

dict_item = {
    'item_id': [1, 2, 3, 4],
    'item_name': ['a', 'b', 'c', 'd'],
    'price': [1000, 2000, 3000, 4000]
}
df_item = pd.DataFrame(dict_item)

df_item.loc[:, 'price_flag'] = df_item.assign(
    price_flag=lambda x: print(x['price'] >= 3000)
)


-- Result
0    False
1    False
2     True
3     True
Name: price, dtype: bool

 

그래서 실제로 assign의 lambda x 식 내부에서 x['price'] >= 3000 의 결과를 print해보면

위처럼 Series 전체에 대해 부등호 연산을 한 결과가 담긴 Series를 얻을 수 있습니다.

 

이러니 당연히 if ~ else 구문을 적용할 수 없죠.

 

 

 

import pandas as pd

dict_item = {
    'item_id': [1, 2, 3, 4],
    'item_name': ['a', 'b', 'c', 'd'],
    'price': [1000, 2000, 3000, 4000]
}
df_item = pd.DataFrame(dict_item)

df_item.loc[:, 'price_flag'] = df_item.assign(
    price_flag=lambda x: x['price'].apply(lambda y: 'high' if y >= 3000 else 'low')
)

print(df_item)


-- Result
   item_id item_name  price price_flag
0        1         a   1000        low
1        2         b   2000        low
2        3         c   3000       high
3        4         d   4000       high

 

만약 assign에서 특정 컬럼에 있는 데이터들에 대해 하나의 행마다 접근해야한다면 위처럼 내부에 apply를 한번 더 적용해야 합니다.

 

lambda x: x['price'].apply(lambda y: 'high' if y >= 3000 else 'low')

보시면 x['price']는 DataFrame의 price 컬럼 전체가 Series의 형태로 선택된 상태입니다.

거기에 apply를 적용하면 price 컬럼에 있는 데이터들에 대해 하나의 행마다 접근해서 if ~ else 연산을 하게되죠.

 

import pandas as pd

dict_item = {
    'item_id': [1, 2, 3, 4],
    'item_name': ['a', 'b', 'c', 'd'],
    'price': [1000, 2000, 3000, 4000]
}
df_item = pd.DataFrame(dict_item)

df_item.loc[:, 'price_flag'] = df_item.assign(
    price_flag=lambda x: print(x['price'].apply(lambda y: 'high' if y >= 3000 else 'low'))
)

-- Result
0     low
1     low
2     high
3     high
Name: price, dtype: object

 

x['price']에 apply를 적용한 결과를 print해보면

x['price'] 컬럼에 apply(lambda y ~~)를 적용한 그 결과를 Series의 형태로 return하고 있습니다.

즉, assign의 lambda x는 어떤 컬럼 전체에 대해 연산을 진행한 후 그 결과를 Sereis의 형태로 return하여 price_flag 처럼 lambda x 식의 등호 왼쪽에 있는 컬럼에 할당하는 과정으로 진행됩니다.

 

 

 

 

 

 

import pandas as pd

dict_item = {
    'item_id': [1, 2, 3, 4],
    'item_name': ['a', 'b', 'c', 'd'],
    'price': [1000, 2000, 3000, 4000]
}
df_item = pd.DataFrame(dict_item)

df_item.loc[:, 'price_flag'] = df_item.assign(
    price_flag=lambda x: x['price'].apply(lambda y: print(y))
)


-- Result
1000
2000
3000
4000

 

실제로 내부 apply의 lambda y 식에서 y를 print해보면 위처럼 price 컬럼에 있는 모든 행 각각의 값에 접근한 것을 알 수 있습니다.

마치 처음부터 apply method를 적용했을 때 처럼요.

 

 

 

 

 

 

import pandas as pd

dict_item = {
    'item_id': [1, 2, 3, 4],
    'item_name': ['a', 'b', 'c', 'd'],
    'price': [1000, 2000, 3000, 4000]
}
df_item = pd.DataFrame(dict_item)

df_item.loc[:, 'price_flag'] = df_item.apply(
    lambda x: 'high' if x['price'] >= 3000 else 'low',
    axis=1
)

print(df_item)


-- Result
   item_id item_name  price price_flag
0        1         a   1000        low
1        2         b   2000        low
2        3         c   3000       high
3        4         d   4000       high

 

import pandas as pd

dict_item = {
    'item_id': [1, 2, 3, 4],
    'item_name': ['a', 'b', 'c', 'd'],
    'price': [1000, 2000, 3000, 4000]
}
df_item = pd.DataFrame(dict_item)

df_item.loc[:, 'price_flag'] = df_item.assign(
    price_flag=lambda x: x['price'].apply(lambda y: 'high' if y >= 3000 else 'low')
)

print(df_item)


-- Result
   item_id item_name  price price_flag
0        1         a   1000        low
1        2         b   2000        low
2        3         c   3000       high
3        4         d   4000       high

 

이제 코드를 다시 비교해봅시다.

 

apply는 컬럼 전체의 연산이 아니라 컬럼에서도 각각의 행에 존재하는 값들에 따라 다른 조건의 값이 return되어야하는 경우에 if ~ else 구문을 쉽게 적용할 수 있다는 장점이 있습니다.

 

하지만 assign의 경우 복잡한 if문 등을 사용하기 위해선 assign 내부에 apply method를 다시 한번 사용해야한다는 불편함이 있죠.

 

 

 

apply는 아래의 코드처럼 custom function을 만들어 custom function 내에서 DataFrame의 각 행에 대한 값을 받아 복잡한 조건 등 다양한 연산을 진행할 수도 있습니다.

 

import pandas as pd

dict_item = {
    'item_id': [1, 2, 3, 4],
    'item_name': ['a', 'b', 'c', 'd'],
    'price': [1000, 2000, 3000, 4000]
}
df_item = pd.DataFrame(dict_item)

def my_func(price):
    if price >= 3000:
        return 'high'
    else:
        return 'low'

df_item.loc[:, 'price_flag'] = df_item.apply(
    lambda x: my_func(x['price']),
    axis=1
)

print(df_item)


-- Result
   item_id item_name  price price_flag
0        1         a   1000        low
1        2         b   2000        low
2        3         c   3000       high
3        4         d   4000       high

 

결론을 말하면 apply method는 복잡한 조건을 DataFrame에 적용할 때 유용합니다.

하지만 성능을 원한다면 assign을 사용하는게 좋습니다.

 

사실 DataFrame의 크기가 크지 않으면 뭘 사용해도 상관없습니다만, DataFrame의 크기가 커지면 apply와 assign의 성능 차이는 굉장히 클겁니다.

 

그래서 condition을 적용해 일부 조건을 만족하는 곳에만 assign을 적용시킨다던지 등의 방법을 사용할 수 있죠.

 

 

 

 

 

 

 

 


 

 

 

 

 

 

이제 assign의 응용 예시 몇가지를 봅시다.

 

 

 

커스텀 함수를 assign의 lambda속에 적용시킬 수도 있습니다.

import pandas as pd

dict_item = {
    'item_id': [1, 2, 3, 4],
    'item_name': ['a', 'b', 'c', 'd'],
    'price': [1000, 2000, 3000, 4000]
}
df_item = pd.DataFrame(dict_item)

def my_func(price):
    return price * 2

df_item_new = df_item.assign(
    price=lambda x: my_func(x['price'])
)
print(df_item)
print(df_item_new)
 
 
 
 - Output of df_item
   item_id item_name  price
0        1         a   1000
1        2         b   2000
2        3         c   3000
3        4         d   4000

 - Output of df_item_new
   item_id item_name  price
0        1         a   2000
1        2         b   4000
2        3         c   6000
3        4         d   8000



위 예시에선 assign에서 함수를 적용한 후의 데이터를 저장할 컬럼을 price라고 하였습니다. (price=lambda x: x['price'] * 2 -> 이 부분)

 

하지만 원본 DataFrame인 df_item에 이미 price컬럼이 있기 때문에 새로운 로직이 적용된 데이터가 price컬럼에 덮어씌워졌습니다.

 

그래서 만약 원본 DataFrame을 보존하면서 새로운 로직을 적용한 컬럼을 추가하고싶다면,

컬럼명을 new_price처럼 새로운 컬럼명으로 바꿔주면 됩니다.

import pandas as pd

dict_item = {
    'item_id': [1, 2, 3, 4],
    'item_name': ['a', 'b', 'c', 'd'],
    'price': [1000, 2000, 3000, 4000]
}
df_item = pd.DataFrame(dict_item)

df_item_new = df_item.assign(
    new_price=lambda x: x.price * 2
)
print(df_item)
print(df_item_new)
 
 
 
 - Output of df_item
   item_id item_name  price
0        1         a   1000
1        2         b   2000
2        3         c   3000
3        4         d   4000

 - Output of df_item_new
   item_id item_name  price  new_price
0        1         a   1000       2000
1        2         b   2000       4000
2        3         c   3000       6000
3        4         d   4000       8000



동시에 여러 개의 컬럼을 생성할 수도 있습니다.

import pandas as pd

dict_item = {
    'item_id': [1, 2, 3, 4],
    'item_name': ['a', 'b', 'c', 'd'],
    'price': [1000, 2000, 3000, 4000]
}
df_item = pd.DataFrame(dict_item)

df_item_new = df_item.assign(
   new_price_1=lambda x: x.price * 2,
   new_price_2=lambda x: x.price * 3
)
print(df_item)
print(df_item_new)
 
 
 
 - Output of df_item
   item_id item_name  price
0        1         a   1000
1        2         b   2000
2        3         c   3000
3        4         d   4000

 - Output of df_item_new
   item_id item_name  price  new_price_1  new_price_2
0        1         a   1000         2000         3000
1        2         b   2000         4000         6000
2        3         c   3000         6000         9000
3        4         d   4000         8000        12000

 

 

 

 

import pandas as pd

dict_item = {
    'item_id': [1, 2, 3, 4],
    'item_name': ['a', 'b', 'c', 'd'],
    'price': [1000, 2000, 3000, 4000]
}
df_item = pd.DataFrame(dict_item)

con = (df_item['item_id'] >= 3)
df_test = df_item.loc[con, :].assign(
    new_price_1=lambda row: row.price * 2
)

print(df_test)


-- Result
   item_id item_name  price  new_price_1
2        3         c   3000         6000
3        4         d   4000         8000

 

위처럼 loc와 이용해서 특정 조건을 만족하는 row에 대해서만 assign을 적용하여 새로운 DataFrame을 만들 수 있습니다.


이 내용을 좀 더 응용하면 아래와 같은 코드도 가능합니다.

 

import pandas as pd

dict_item = {
    'item_id': [1, 2, 3, 4],
    'item_name': ['a', 'b', 'c', 'd'],
    'price': [1000, 2000, 3000, 4000]
}
df_item = pd.DataFrame(dict_item)

con = (df_item['item_id'] >= 3)
df_item.loc[con, 'new_price_1'] = df_item.loc[con, :].assign(
    new_price_1=lambda row: row.price * 2
).loc[con, ['new_price_1']]

print(df_item)


-- Result
   item_id item_name  price  new_price_1
0        1         a   1000          NaN
1        2         b   2000          NaN
2        3         c   3000       6000.0
3        4         d   4000       8000.0

 

특정 조건을 만족하는 행(row)에 대해서만 assign 연산을 진행한 후 그 결과가 담긴 new_price_1 컬럼을 바로 원본 DataFrame인 df_item에 삽입하는 코드입니다.

 

아주 단순한 응용이지만 실제 사용하면 코드의 라인 수를 단축할 수 있습니다.

 

 

 

 

 

 

728x90
반응형
Comments