[pandas] 수치형/연속형 데이터 구간 나누기

최근에 파이썬 pandas에서 특정 컬럼에 대해 범위에 따라 구간을 나누고, 그 구간에 속한 값들의 평균을 구할 수 있냐는 질문을 받았다.

(구간, 범주, 구역, 경계…? 어떤 표현이 좋은지 모르겠지만 아무튼 히스토그램 그릴 때처럼 그렇게 나누는 거.)

수치형, 연속형 데이터를 범주형 데이터로 바꾸는 과정인 셈이다.


우선 엑셀에서 읽어온 데이터가 이런 모양으로 생겼다고 해보자.

import pandas as pd
df = pd.read_excel("ex.xlsx")

내가 그냥 random으로 숫자를 넣은 거다. 요약을 보면

df.describe()

최소 150, 최대 199라는 걸 알았다.

이제 해보자. 10단위로 끊어서 구간을 구분하고, 그 안의 값들의 평균을 구하기.

난 두 가지 방법으로 해보았다.


방법 1. range와 for 반복문을 활용한다.

range 괄호 안에 (시작, 끝, 간격)을 차례로 입력해서 for 반복문을 돌릴 수 있다.

150~ 199 사이에서 10 단위로 반복문을 돌고, 데이터프레임에서 행을 조건에 따라 필터링해서, 평균을 출력했다.

for x in range(150, 200, 10):
    start = x
    end = x+10
    temp = df[(df["height"] >= start) & (df["height"] < end)]
    print("{}이상 {}미만 : {}".format(start, end, temp["height"].mean()))

결과는 아래와 같다.

150이상 160미만 : 155.0625
160이상 170미만 : 164.83333333333334
170이상 180미만 : 174.92307692307693
180이상 190미만 : 185.1
190이상 200미만 : 194.1

성공!

방법 2. pandas의 pd.cut()을 활용해 구간을 나누고 groupby()를 사용한다.

이번엔 애초에 구간을 나눠서 레이블링을 우선 해놓는 전략이다.

우선 구간을 나누는 기준점을 리스트를 만들어 놓아야 한다.

bins = list(range(150, 201, 10))
[150, 160, 170, 180, 190, 200]

여기서 중요한 건, 맨 끝 edge인 200도 포함해야 한다는 것.

그리고 이 기준점 사이에 들어갈 구간에 대한 레이블도 만들어놓자.

bins_label = [str(x)+"이상 "+str(x+10)+"미만" for x in bins]
['150이상 160미만',
 '160이상 170미만',
 '170이상 180미만',
 '180이상 190미만',
 '190이상 200미만',
 '200이상 210미만']

맨 끝에 200~210이 있네. 이건 나중에 버리면 된다.

이제 pd.cut()을 사용해서 값에 따라 구간을 나눌 수 있다.

df["level"] = pd.cut(df["height"], bins, right=False, labels=bins_label[:-1])

여기서 right=False로 지정한 이유는 특정 값 “미만”으로 구역을 나누기 때문이다. 그리고 5개 구간으로 나뉘기 때문에 labels를 지정할 때 미리 만들어 놓았떤 bins_label에서 맨 끝 값은 제외했다. (이렇게 안 하면 숫자가 안 맞아서 오류가 난다. 구간이 5개인데 레이블은 왜 6개냐고.)

아무튼 이렇게 다시 df를 찍어보면

구간에 따라 레이블을 붙인 컬럼이 생긴 걸 볼 수 있다.

이제 groupby를 사용해서 평균을 구하면

첫 번째 방법으로 구한 값과 동일하다.


나도 이메일로 질문을 받은 덕분에 이렇게 한 번 정리하고 넘어가게 되네.

질문은 언제나 환영입니다.

저도 파이썬 잘 못하지만요.

추천 글

“[pandas] 수치형/연속형 데이터 구간 나누기”의 3개의 댓글

  1. 데이터 값을 기준으로 나누는게 아니라
    데이터 갯수로 나누는 방법도 있을까요?
    어… 0~99번까지의 행에서의 평균값, 100~199까지의 평균값 이런 식으로요

    1. 아… 애초에 그렇게 인덱스로 데이터를 쪼갤 일이 발생하는 게 아름다운 상황은 아닐 거 같네요…
      아무튼 그 경우에는 데이터 프레임 나눠서 평균 구하는 게 좋을 거 같아요.
      http://hleecaster.com/python-pandas-selecting-data/
      요 포스팅 보면 인덱싱하는 방법 적어놓긴 했는데
      df.iloc[:100]
      df.iloc[100:200]
      이런 식으로 데이터프레임 나누도록 반복문 사용하시면 될 거 같긴 합니다!

댓글 남기기