이전 포스팅에서 Pandas 의 함수를 활용해서 결측값을 채우거나 행을 제거하기, GroupBy operator를 사용해서 그룹별 (가중)평균을 구하는 방법을 소개했었습니다. 


이번 포스팅에서는 이전 포스팅의 내용들을 결합하여 '결측값을 그룹 별 평균값으로 채우기 (Fill missing values using the group means)' 를 해보겠습니다. 





먼저 예제로 사용할 'a'와 'b' 두 개의 그룹을 가지고 있고, 'col_1'과 'col_2'의 두 개의 칼럼을 가지고 있는 간단한 데이터프레임을 만들어보겠습니다. 



In [1]: import numpy as np

   ...: import pandas as pd


In [2]: np.random.seed(123) # for reproducibility


In [3]: df = pd.DataFrame({'grp': ['a', 'a', 'a', 'a', 'b', 'b', 'b', 'b'],

   ...: 'col_1': np.random.randn(8),

   ...: 'col_2': np.random.randn(8)})

   ...:

   ...: df

Out[3]:

col_1 col_2 grp

0 -1.085631 1.265936 a

1 0.997345 -0.866740 a

2 0.282978 -0.678886 a

3 -1.506295 -0.094709 a

4 -0.578600 1.491390 b

5 1.651437 -0.638902 b

6 -2.426679 -0.443982 b

7 -0.428913 -0.434351 b

 




다음으로 그룹 'a'와 'b'별로 'col_1'과 'col_2' 칼럼에 각각 하나씩 결측값(missing value, NaN)을 집어넣어보겠습니다. 



In [4]: df.loc[[1, 6], ['col_1', 'col_2']] = np.nan

   ...: df

Out[4]:

col_1 col_2 grp

0 -1.085631 1.265936 a

1 NaN NaN a

2 0.282978 -0.678886 a

3 -1.506295 -0.094709 a

4 -0.578600 1.491390 b

5 1.651437 -0.638902 b

6 NaN NaN b

7 -0.428913 -0.434351 b

 



'a'와 'b' 그룹별 'col_1'과 'col_2' 칼럼의 평균을 계산해보니 아래와 같군요. 이 그룹별 칼럼별 평균 값으로 결측값을 대체하려는 것입니다. 



In [5]: df.groupby('grp').mean()

   ...:

Out[5]:

      col_1       col_2

grp

a -0.769649    0.164114

b  0.214641    0.139379

 



자, 이제 준비가 되었으니 GroupBy operator lambda 함수, 그리고 apply() 를 사용해서 그룹별 칼럼별 평균을 가지고 결측값을 채워(imputation)보겠습니다. 



In [6]: fill_mean_func = lambda g: g.fillna(g.mean())


In [7]: df.groupby('grp').apply(fill_mean_func)

Out[7]:

            col_1         col_2       grp

grp

a     0  -1.085631    1.265936    a

      1  -0.769649    0.164114   a

      2   0.282978   -0.678886    a

      3  -1.506295   -0.094709    a

b    4  -0.578600    1.491390    b

      5   1.651437   -0.638902    b

      6   0.214641    0.139379    b

      7  -0.428913   -0.434351    b

 





만약에 각 "그룹별"로 "특정 값(specific value)"을 가지고 결측값을 대체하고 싶다면 아래의 코드를 참고하세요. 



In [8]: fill_values = {'a': 1.0, 'b': 0.5}


In [9]: fill_func = lambda d: d.fillna(fill_values[d.name])


In [10]: df.groupby('grp').apply(fill_func)

Out[10]:

      col_1            col_2 grp

0   -1.085631       1.265936 a

1    1.000000       1.000000 a

2    0.282978      -0.678886 a

3   -1.506295      -0.094709 a

4   -0.578600       1.491390 b

5    1.651437      -0.638902 b

6    0.500000       0.500000 b

7   -0.428913      -0.434351 b

 


많은 도움이 되었기를 바랍니다. 


이번 포스팅이 도움이 되었다면 아래의 '공감~'를 꾹 눌러주세요. ^^



728x90
Posted by R Friend Rfriend

댓글을 달아 주세요

  1. Das 2019.05.11 18:15  댓글주소  수정/삭제  댓글쓰기

    정말 많은 도움이 됐습니다 ^^

  2. RRR 2019.08.24 21:54  댓글주소  수정/삭제  댓글쓰기

    안녕하세요!! R을 사용하는데 정말 많은 도움이 되고 있습니다!!
    혹시 R에서 결측값을 그룹별 평균값으로 채우기 위해서는 어떤 코드를 이용해야할까요?

  3. RRR 2019.08.24 22:11  댓글주소  수정/삭제  댓글쓰기

    답변 감사합니다!!
    그런데 저는 변수별 평균으로 대체하는 방법이 아닌 위의 내용처럼 그룹별 평균으로 대체하는 방법이 필요해서요..

    • R Friend Rfriend 2019.08.24 22:53 신고  댓글주소  수정/삭제

      R을 사용해서 그룹별 평균으로 대체하는 방법은 아래의 dplyr 의 mutate 함수 사용하는 방법 참고하세요.

      library(dplyr)
      df %>%
      group_by(grp) %>%
      mutate(val = ifelse(is.na(val), mean(val, na.rm=TRUE), val))

    • RRR 2019.08.24 23:13  댓글주소  수정/삭제

      와 너무 감사합니다!! 아무리 구글링을 해도 못찾았었는데 너무 큰 도움이 되었어요!!

    • R Friend Rfriend 2019.08.24 23:17 신고  댓글주소  수정/삭제

      잘 해결되었다니 다행입니다. R 결측값 처리 포스팅의 본문 내용 업데이트 해놔야겠네요.

  4. 보근이파워 2020.02.14 16:27  댓글주소  수정/삭제  댓글쓰기

    안녕하세요! 항상 양질의 포스팅으로 지식의 유희를 주셔서 감사함을 느끼고 있습니다!
    질문 한가지만 드려도 될까요?
    위 내용에서
    In [6]: fill_mean_func = lambda g: g.fillna(g.mean())
    In [7]: df.groupby('grp').apply(fill_mean_func)
    이렇게 적용을 했지만 데이터 프레임에는 inplace가 되지 않는데, 가능한 방법이 있을까요? ㅜㅜ

    • R Friend Rfriend 2020.02.14 16:36 신고  댓글주소  수정/삭제

      안녕하세요.
      결측값을 각 그룹의 평균으로 채운 결과를 반환받아서 데이터프레임을 만들면(같은 이름으로 반환하면 업데이트) 됩니다.

      df = df.groupby('grp').apply(fill_mean_func)

  5. 미류 2020.12.20 20:42  댓글주소  수정/삭제  댓글쓰기

    안녕하세요 블로그 글 보면서 그룹별 평균값으로 결측치 채우는것으로 파이썬을 만들었는데
    그룹으로 만든 월별 데이터안에 모든 데이터가 비었을 시 이전달의 데이터로 가져오는것을 만들려고 하는데 잘 안되네요 저는 lambda와 if 조건문으로 할려고 하는데 key expression 에러가 나네요 한번 봐주시면 감사하겠습니다 ^^

    apply(lambda i:i.fillna(i.mean()) if i.all() != 'NaN' else i.fillna(method = 'pad'))

    • R Friend Rfriend 2020.12.20 23:36 신고  댓글주소  수정/삭제

      안녕하세요 미류님.

      월 그룹별로 결측값을 월별 평균으로 채우고, 만약 월 그룹 내 데이터가 모두 비어있는 경우는 이전달의 데이터로 채우려면 아래처럼 순차적으로 진행해주시면 됩니다.

      groupby('grp') 를 하게 되면 그룹별로 데이터프레임이 내부적으로는 '분할(split)'이 됩니다. 따라서 월별로 분할이 된 상태에서는 이전월의 데이터를 가져와서 forward fill이 안됩니다.

      즉, 그냥 쉽게 생각해서 (1) 월 그룹별로 결측값을 월 평균값으로 먼저 채워주고, (2) 만약 월의 모든 데이터가 결측값이라면 (1)번 수행 후에도 해당 월은 모두 결측값으로 비어있을 것이고, 나머지 월은 결측값이 월 평균값으로 채워져있는 상태에서, forward fill method 로 채워주기를 하면 이전월의 데이터를 가져와서 통째로 비어있는 월의 결측값을 채울 수 있습니다. 단, 이때 날짜 기준으로 정렬(sorting)이 되어 있어야 겠네요.


      # (step 1) fillna by group's mean
      fill_mean = lambda g: g.fillna(g.mean())
      df.groupby('grp').apply(fill_mean)

      # (step 2) sorting by timestamp
      df.sort_values(by=['timestamp'], axis=0, inplace=True)

      # (step 3) fillna by forward method
      df.fillna(method='pad')

  6. 미류 2020.12.21 20:47  댓글주소  수정/삭제  댓글쓰기

    감사합니다 덕분에 쉽게 했습니다 ㅎㅎ 그룹바이를 4개기준으로 하다보니 컬럼과 인덱스가 같아져서 컬럼(시간관련)의 이름을 수정후 자료를 구할 수 있었습니다. ^^