Pandas 객체에 결측치를 처리하는 다양한 패턴을 알아본다. 여기서는 .groupby()
메서드와 NumPy의 where()
함수를 활용한 결측치 대치 방법을 다룬다.
본 게시물을 온전히 이해하기 위해서는 결측치 처리 기초 게시물 “Py) 기초 - Pandas(결측치)”를 먼저 충분히 숙지하는 것을 추천한다.
개요
단일 변수를 사용한 결측치 대치는 주로 .fillna()
또는 .isna()
를 활용한 방법을 꼽을 수 있다. 하지만 결측값 대치를 위해서 다른 변수의 정보를 끌어와서 해야한다면 상황이 조금 복잡해진다.
예를 들어 다음과 같은 문제상황이 있다고 하자.
두 명목형 변수 A와 B가 있을 때 B 변수의 결측값을 대치하기 위해 A 변수의 각 범주에 해당하는 B 변수의 범주에 결측치가 존재할 경우 A 변수의 각 범주에 해당하는 B 변수의 범주의 최빈값으로 결측값을 대치하시오.
해당 문제를 보고 .groupby()
는 바로 떠올릴 수 있지만 그 다음은 어떻게 핸들링 해야하는지 한 번에 떠올리기 어렵다. 이제 실습 코드를 통해 알아보도록 하자.
참고로 본 게시물은 27 - Pandas(결측값 처리)를 충분히 숙지하고 있는 사람을 위한 게시물이며 되도록이면 해당 게시물을 먼저 숙지하고 다음을 보는 것을 권장한다.
실습
데이터 준비
다음과 같이 두 범주형 변수가 있는 데이터프레임 “df”를 준비한다. 객체 탐색이 용이하도록 일부러 정렬을 하였고 임의의 위치에 결측값을 배치시켰다.
1 | df = pd.DataFrame(dict(flavour = ["sweet", "other", "spicy", "spicy", |
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 | df.groupby("flavour")["region"].value_counts() |
“flavour” 변수의 각 범주별로 “region” 범주의 최빈값은 “south”, “west”, “north”인 것을 알 수 있다. 그리고 해당 최빈값은 최대값의 인덱스와 같음으로 .idxmax()
를 사용해볼 수 있겠다. .value_counts()
메서드와 같이 연계해서 사용하기 위해 .agg()
메서드와 lambda 함수를 추가로 사용한 코드는 다음과 같다.
1 | df.groupby("flavour")["region"].agg(lambda x: x.value_counts().idxmax()) |
이번에는 제대로된 결과가 나온 것으로 판단된다. 그럼 이제 상기 결과를 조금 정제해보자.
1 | ser_mode = df.groupby("flavour")["region"].agg(lambda x: x.value_counts().idxmax()) |
flavour | region_m | |
---|---|---|
0 | other | south |
1 | spicy | west |
2 | sweet | north |
시리즈 객체를 데이터프레임으로 변환하고 변수명을 미리 바꾸었는데, 이렇게 정제를 한 이유는 데이터프레임 객체 “df”에 최빈값 정보를 병합하기 위함이다. 이제 두 객체를 병합하면 다음과 같다.
1 | df_join = df.merge(df_mode, on = "flavour") |
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 | df_join["region_fill"] = np.where(df_join["region"].isna(), |
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 |