Post

연속적인 값에 대해 Group-by operation 수행하기





들어가며

Pandas의 Group-by operation은 applytransform 메서드를 이용하여 데이터 그룹에 대한 처리를 편하게 할 수 있게 해줍니다. Pandas를 이용해서 데이터를 처리하다보면 어떻게든 한 번은 사용하게 되는 operation이죠. 하지만 매번 단순한 데이터 전처리를 하게 되지는 않습니다. 원하는 형태로 데이터를 만지다 보면 독특한 상황을 맞닥뜨리게 됩니다. 오늘 포스트에선 그런 상황 중 하나에 대해서 다뤄보도록 하겠습니다.

문제

다음과 같은 데이터 프레임이 있다고 가정해보겠습니다.

1
2
3
4
5
6
7
8
import numpy as np
import pandas as pd

np.random.seed(0)
name = np.random.choice(["A", "B", "C"], size=10, replace=True)
value = np.random.choice(10, size=10, replace=True)

df = pd.DataFrame({"name": name, "value": value})
1
2
3
4
5
6
7
8
9
10
11
  name  value
0    A      1
1    B      6
2    A      7
3    B      7
4    B      8
5    C      1
6    A      5
7    C      9
8    A      8
9    A      9

이 데이터에 대해 일반적인 group-by operation이 아닌 name 컬럼을 기준으로 연속적인 값들을 하나의 그룹으로 설정하여 연산을 하고자 합니다. 만약 연속적인 값들을 그룹으로 설정하여 value가 가장 큰 경우를 반환한다면 이렇게 되겠죠.

1
2
3
4
5
6
7
8
9
  name  value
0    A      1
1    B      6
2    A      7
3    B      8
4    C      1
5    A      5
6    C      9
7    A      9

어떻게 하면 될까요?

해결

조금 어려워 보이지만 몇 가지 트릭을 사용하면 생각보다 간단하게 해결할 수 있습니다. 우선 name 컬럼을 한 칸씩 아래로 밀었다고 생각해봅시다. 그 컬럼의 이름을 name_push라고 한다면 아래와 같습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
df["name_push"] = df["name"].diff()

  name name_push
0    A       NaN
1    B         A
2    A         B
3    B         A
4    B         B
5    C         B
6    A         C
7    C         A
8    A         C
9    A         A

여기서 namename_push가 같은 행이 있다면 그 행은 바로 이전 행과 연속적인 값을 이루고 있다는 것을 알 수 있습니다. 그럼 새로운 컬럼인 flag에 해당 정보를 저장하겠습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
df["flag"] = df["name"] != df["name_push"]

  name name_push   flag
0    A       NaN   True
1    B         A   True
2    A         B   True
3    B         A   True
4    B         B  False
5    C         B   True
6    A         C   True
7    C         A   True
8    A         C   True
9    A         A  False

이제 이 정보를 이용해서 매우 간단하게 연속적인 값을 가지는 경우 그룹을 짓도록 할 수 있습니다. flag 컬럼에 대해 누적합을 계산하면 됩니다. 연속적인 값이 있을 때 flag 컬럼의 값은 False가 되기 때문에 누적합의 변동이 발생하지 않기 때문입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
df["group"] = df["flag"].cumsum()

  name name_push   flag  group  value
0    A       NaN   True      1      1
1    B         A   True      2      6
2    A         B   True      3      7
3    B         A   True      4      7
4    B         B  False      4      8
5    C         B   True      5      1
6    A         C   True      6      5
7    C         A   True      7      9
8    A         C   True      8      8
9    A         A  False      8      9

다섯 번째 행과 마지막 행을 살펴보면 name == name_push가 되면서 누적합의 변화가 발생하지 않아 동일한 그룹으로 설정이 되었습니다. 마지막으로 group 컬럼에 대해 groupby() 메서드를 사용하면 됩니다.

1
2
3
4
5
6
7
8
9
10
11
df.groupby("group").max()[["name", "value"]].reset_index(drop=True)

  name  value
0    A      1
1    B      6
2    A      7
3    B      8
4    C      1
5    A      5
6    C      9
7    A      9

지금까지의 복잡한 과정을 조금 더 단순하게 하면 아래 코드가 됩니다.

1
2
df["group"] = (df["name"] != df["name"].shift()).cumsum()
df.groupby(["group"]).max().reset_index(drop=True)


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