Py) 전처리 - 결측치 처리-01

Py) 전처리 - 결측치 처리-01

Pandas 객체에 결측치를 처리하는 다양한 패턴을 알아본다. 여기서는 .groupby()메서드와 NumPywhere() 함수를 활용한 결측치 대치 방법을 다룬다.


본 게시물을 온전히 이해하기 위해서는 결측치 처리 기초 게시물 “Py) 기초 - Pandas(결측치)”를 먼저 충분히 숙지하는 것을 추천한다.

개요

단일 변수를 사용한 결측치 대치는 주로 .fillna() 또는 .isna() 를 활용한 방법을 꼽을 수 있다. 하지만 결측값 대치를 위해서 다른 변수의 정보를 끌어와서 해야한다면 상황이 조금 복잡해진다.

예를 들어 다음과 같은 문제상황이 있다고 하자.

두 명목형 변수 AB가 있을 때 B 변수의 결측값을 대치하기 위해 A 변수의 각 범주에 해당하는 B 변수의 범주에 결측치가 존재할 경우 A 변수의 각 범주에 해당하는 B 변수의 범주의 최빈값으로 결측값을 대치하시오.

해당 문제를 보고 .groupby()는 바로 떠올릴 수 있지만 그 다음은 어떻게 핸들링 해야하는지 한 번에 떠올리기 어렵다. 이제 실습 코드를 통해 알아보도록 하자.

참고로 본 게시물은 27 - Pandas(결측값 처리)를 충분히 숙지하고 있는 사람을 위한 게시물이며 되도록이면 해당 게시물을 먼저 숙지하고 다음을 보는 것을 권장한다.

실습

데이터 준비

다음과 같이 두 범주형 변수가 있는 데이터프레임 “df”를 준비한다. 객체 탐색이 용이하도록 일부러 정렬을 하였고 임의의 위치에 결측값을 배치시켰다.

1
2
3
4
5
6
df = pd.DataFrame(dict(flavour = ["sweet", "other", "spicy", "spicy", 
"other", "sweet", "spicy", "other"] * 5,
region = ["south", "north", "west", "east"] * 10))
df = df.sort_values(["flavour", "region"]).reset_index(drop = True)
df.iloc[[3, 7, 15, 26, 38], 1] = np.nan
df.head(8)
flavour region
0 other east
1 other east
2 other east
3 other NaN
4 other east
5 other north
6 other north
7 other NaN

“df” 객체의 “flavour”변수를 기준으로 “region” 변수의 각 원소 최빈값을 구해보자. 우선 데이터프레임에 있는 .mode() 메서드를 사용해보자.

1
df.mode()
flavour region
0 other north
1 spicy south
2 NaN west

“flavour” 변수에는 범주가 3개가 있는데 .mode() 메서드 실행 결과는 원하는 결과가 아닌 것 같다. 그럼 .value_counts()를 사용하여 “flavour” 범주별 “region” 변수의 최빈값을 확인해보자.

1
2
3
4
5
6
7
8
9
10
df.groupby("flavour")["region"].value_counts()
## flavour region
## other south 5
## east 4
## north 4
## spicy west 9
## east 4
## sweet north 5
## south 4
## Name: count, dtype: int64

“flavour” 변수의 각 범주별로 “region” 범주의 최빈값은 “south”, “west”, “north”인 것을 알 수 있다. 그리고 해당 최빈값은 최대값의 인덱스와 같음으로 .idxmax()를 사용해볼 수 있겠다. .value_counts() 메서드와 같이 연계해서 사용하기 위해 .agg() 메서드와 lambda 함수를 추가로 사용한 코드는 다음과 같다.

1
2
3
4
5
6
df.groupby("flavour")["region"].agg(lambda x: x.value_counts().idxmax())
## flavour
## other south
## spicy west
## sweet north
## Name: region, dtype: object

이번에는 제대로된 결과가 나온 것으로 판단된다. 그럼 이제 상기 결과를 조금 정제해보자.

1
2
3
4
ser_mode = df.groupby("flavour")["region"].agg(lambda x: x.value_counts().idxmax())
df_mode = ser_mode.reset_index()
df_mode.columns = ["flavour", "region_m"]
df_mode
flavour region_m
0 other south
1 spicy west
2 sweet north

시리즈 객체를 데이터프레임으로 변환하고 변수명을 미리 바꾸었는데, 이렇게 정제를 한 이유는 데이터프레임 객체 “df”에 최빈값 정보를 병합하기 위함이다. 이제 두 객체를 병합하면 다음과 같다.

1
2
df_join = df.merge(df_mode, on = "flavour")
df_join.head(8)
flavour region region_m
0 other east south
1 other east south
2 other east south
3 other NaN south
4 other east south
5 other north south
6 other north south
7 other NaN south

이제 대망의 결측치 대치이다. “region” 변수를 기준으로 해당 변수가 결측치이면 최빈값이 있는 변수 “region_m” 변수의 원소를 가져오고, 그렇지 않으면 그대로 “region” 변수의 원소를 가져올 수 있도록 NumPy 라이브러리의 where() 함수를 사용할수 있다.

1
2
3
4
df_join["region_fill"] = np.where(df_join["region"].isna(), 
df_join["region_m"],
df_join["region"])
df_join.head(8)
flavour region region_m region_fill
0 other east south east
1 other east south east
2 other east south east
3 other NaN south south
4 other east south east
5 other north south north
6 other north south north
7 other NaN south south

마지막으로 “region” 변수의 결측값이 있는 행을 별도 필터링하여 확인해보자.

1
df_join.loc[df_join["region"].isna(), ]
flavour region region_m region_fill
3 other NaN south south
7 other NaN south south
15 spicy NaN west west
26 spicy NaN west west
38 sweet NaN north north
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×