[Basic] 데이터 정제 - 그룹화, 기술통계

고보·2024년 2월 2일

https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.groupby.html

1 그룹화

  • a = df.groupby('year'): 하면 year을 기준으로 그룹화한 'DataFrameGroupBy'객체 반환. => 집계 기준이 인덱스가 됨 => 다양한 집계 함수를 사용하면, 그 인덱스 기준으로 집계가 이루어짐.
    이 자체로는 시각화가 되지 않는다. 그룹으로 묶고 그걸로 집계 함수를 안썼기 때문.
  • a = df.groupby('year')['lifeExp'].mean(): 이렇게 집계 함수를 걸면, 데이터타입이 다시 DataFrame으로 되고, 시각화도 된다.
    이 경우는 1개의 기준, 1개의 컬럼 => 시리즈.
    인덱스가 연도.
  • a = df.groupby(['year', 'continent'])['lifeExp'].mean(): 2개 기준을 순서대로 개층화. 여전히 1개의 컬럼이므로 => 시리즈. 인덱스는 (year, continent)의 튜플들.
  • a = df.groupby(['year', 'continent'])['lifeExp', gdpPercap']].mean() => 2개 기준 계층화, 2개 열 추출.
  • a.reset_index() => 그룹으로 계층화됐을 때, 계층 구조 없애기.
  • (dropna=False)를 지정하면, 그 행도 포함된다. default는 제외.
  • as_index=False는, 그룹화 기준열인 sex를 인덱스로 사용하지 않는다는 뜻.

2 관련 함수, 메서드

2-1 기타 메서드

  • groupedDf.groups: 속성 사용하면, 각 그룹에 속한 데이터프레임 인덱스 확인 가능.
  • groupedDf.get_group('Female') => sex로 나눈 그룹에서 Female만 들고 온다.
    => grouped_mean.loc['Female']과 같다.
    grouped.get_group(('First', 'female')) => 만약 class, sex로 2개의 기준열로 그룹화를 했을 때는, 이렇게 튜플이 인덱스가 되기 때문에 이렇게 지정.
    => grouped_mean.loc['First'].loc['Female']과 같다.
    => grouped_mean.loc[('First','Female')]도 같다.
grouped = titanic1.groupby(['class', 'sex'])
grouped_mean = grouped.mean()

#1
grouped_mean.loc['First']
#혹은
grouped_mean.loc[('First',)]
#2
grouped_mean[grouped_mean['class'] == 'First']
  • 위와 같이 class, sex로 그룹화해서, class에서 First인 것만을 추출하려고 할 때 #1은 되고, #2는 안된다.
    이유는 class와 sex 열이 인덱스가 됐기 때문.
    #1은 .loc['First']는 loc으로 행 인덱스로 접근을 해서, 저 행인 것을 추출하는 거다.
    #2는 class라는 열에서 First와 일치하는 불리언을 추출하려는 시도. 하지만 class라는 열이 없다.
    어거지로 #2와 비슷하게 하려면
  • grouped_mean[grouped_mean.index.get_level_values('class') == 'First']
    get_leve_values는 multiindex 객체에서 특정 레벨의 값을 가져오는 것. 저렇게 하면 class의 값만 추출 가능하다.
    grouped_mean.loc[(slice(None), 'First'), :] 이런 식으로 두 번째 인덱스에만 접근 가능하다. slice는 iterable에서 슬라이싱을 하는 함수이고, 안에 None을 넣으면 :와 같은 의미로 그냥 모든 것이 된다. 즉 첫 번째 인덱스는 모두, 두 번째 인덱스는 First.
  • .reset_index(). 인덱스 초기화하고, 처음부터 다시 준다. 그룹화된 객체는 인덱스가 그룹 기준으로 되어있고, DataFrameGroupBy 객체인데, 이를 통해 인덱스를 다시 부여하면 평탄화되면서 DataFrame 객체가 된다.

2-2 집계 함수에 대해

  • df.describe(): 기본적인 기술통계
    include = ''로 데이터타입 지정 가능. object하면 object타입만 보여줌.
    percentiles = [0.5]로 백분위수도 보여줄지 지정 가능. default는 중앙값. 0.3 입력하면 30% 값도.
  • .value_counts(): 시리즈에서 고유한 값들의 빈도를 반환.
    df['country'].value_counts():시리즈에서 고유한 값들의 빈도를 반환. country에서 각 값이 몇 번씩 나왔는지.
  • df['열이름'].mean(): 시리즈의 평균값
    np.mean(df[열이름]). df[열이름]이 들어가는 위치에, df[열이름1]*df[열이름2]처럼 연산 넣으면, 그 결과값의 평균 반환.
    np.average(): 도 평균. 계산 시에 weights=1/df[열이름]처럼 가중치 입력 가능.
    평균 계산하는 식도 방법 따라 결과 달라질 수 있으니 주의.
  • numeric_only = True하면 DF 전체에 수인 열에만 적용. 나머지는 제외.
  • df['열이름'].median(): 시리즈의 중앙값.
    np.median(df['열이름']
  • df['열이름'].min(): 시리즈의 최소값
    np.min(['열이름'])
  • df['열이름'].max(): 시리즈의 최대값
    np.max(df['열이름'])
  • df['열이름'].quantile([], interpolation='linear'): 시리즈의 분위수. 숫자는 0.3 이런 식으로, interpolation은 보간법 지정. linear, midpoint, nearest, lower, higher 등.
    np.quantile(df['열이름'], method=) 여기선 method가 interpolation. np가 pd보다 더 다양한 방법 지원한다.
    np.percentile(df['열이름'], [0~100])
  • df['열이름'].var(ddf=): 시리즈의 분산. ddof=1은 자유도. ddof 는 degree of freedom correction으로 자유도보정. 표본 데이터에 대한 추정치에 얼마나 자유도 보정할지. 디폴트가 1(즉, n-1로 계산해서 마지막 1의 샘플 추정). 0으로 하면, 모집단 분산 추정치 사용.
    np.var(df['열이름'], ddof=)
  • df['열이름'].std(): 시리즈의 표준편차
    np.std(df['열이름'])
  • df['열이름'].mode(): 시리즈의 최빈값
    values, count = np.unique(df['열이름'], return_counts=True) 고유값 리스트를 return한다. return_counts=True로 하면, 그 갯수도 세서 리스트에 리턴한다.
  • np.argmax([시리즈]): 주어진 배열의 최댓값의 인덱스를 반환.
  • df.groupby('a').size(): 그룹별로 사이즈 크기를 계산. 그냥 시리즈의 경우 크기를 반환.
  • 위의 기능들은 대부분 DataFrame 전체에도 적용 가능. 그러면 각 열별로 적용이 됨. value_counts는 완전히 열 별로 안되고 전체적으로 적용.
    df.value_counts(): 데이터 프레임에서 각 값의 경우의 수가 모두 어떻게 나왔는지. 앞부터 순서대로 계층화해서.
    df.groupby('continent')['country'].value_counts() continent로 그룹화된 열 country의 행의 갯수를 셈.




  • 더 궁금하면 Pandas 홈페이지에

3 agg(), transform(), filter()

3-1 agg()

  • agg()는 Pandas 내장이 아닌 방식으로 groupby에 집계를 할 때 이용. 사용자 정의, 넘파이 함수, 기존 메서드의 사용자 응용 등을 적용할 수 있음.
    df.groupby('year')['lifeExp'].agg([사용자 정의 함수, np.mean()])
    이 같이 사용자가 정의한 함수, numpy의 함수 등을 적용 가능.
  • df.groupby('year').agg({'lifeExp':['mean', 'sum'], 'gdpPercap':'median'})
    이 경우 year별로 liefExp라는 열에 맞춰, mean, sum 값을 한거번에 DataFrame으로 낸다. 여러 개의 함수를 줄 때는 리스트로. 여러 개의 열을 넘겨줄 때는 딕셔너리로.
  • agg_mean_diff = df.groupby('year')['lifeExp'].agg(my_mean_diff, diff_value = global_mean)
    이 경우에도 함수에 인수가 2개 이상 들어가면, 인수를 키워드로 지정. 아래의 my_mean_diff라는 사용자 정의 함수는 values, diff_value 2개의 argments를 받음. 그래서 ['lifeExp'] 외에 diff_vlalue를 지정해서 넘김.
  • welfare.groupby('age').agg(mean_income = ('income', 'mean'))
    agg()는 그룹화에 집계 연산 수행. mean_income=('income', 'mean')은 income열에 mean 연산을 하고, 이걸 mean_income 이라는 새로운 열 이름으로 결과 저장한다는 것.
    그리고 여기서 mean은 사용자 정의 함수나 파이썬 함수가 아니라, pandas에서 agg메소드에 사용할 수 있는 문자열 식별자들. 함수를 지칭하는 게 아니다. sum, mean, max, min, count, size, std, var, median, prod, first, last, nunique 등.

3-1 transform 메서드와 차이

  • agg
    • .agg() 메서드는 그룹화된 데이터의 각 그룹에 대해 지정된 연산을 수행한 후, 그 그룹화된 모양대로 결과를 집계하여 반환합니다. 각 그룹에 대해 하나의 값(스칼라)이 결과로 반환됩니다.
    • 주로 집계 함수를 사용하여 각 그룹의 특성을 요약하는 데 사용됩니다.
  • transform() 메서드:
    • .transform() 메서드는 그룹화된 데이터의 각 그룹에 대해 지정된 연산을 수행한 후, 원본 데이터프레임과 동일한 크기와 형태로 결과를 반환합니다.
    • 각 그룹에 대해 계산된 결과를 원본 데이터프레임에 매핑하여 각 원소의 새로운 값을 생성합니다.
    • 각 그룹 내에서 개별적인 변환을 수행하고 싶을 때 유용합니다.

원본

agg는 그룹화된 걸 그룹별로 집계 함수를 적용해서, 그룹화된 채로 나온다.

transform은 그룹화된 것을 포함해서 계산을 하지만, 그걸 개별행에 적용. 여기서는 평균, 표준편차를 이용한 z-score를 개별적으로 비교.

3-3 apply와 transform의 차이?

  • 공통점: 둘 다 데이터프레임 전체를 조작하는데 사용.
    둘 다 단일 열에 적용 가능.
  • apply 메서드는 각 열(혹은 행) 전체에 함수 적용해서 다양한 형태의 결과를 반환.
    대상: axis=0 또는 axis=1로 설정해서 함수를 적용.
    반환 값: 적용된 함수에 따라, 요약 통계 계산, 사용자 지정 변환을 수행한 후에는 스칼라, 시리즈 데이터 프레임 등 다양한 형태 반환.
    속도: 각 열 또는 행에 대해 적용해서 큰 데이터셋에 처리 시간이 느릴 수 있다.
  • transform은 각 원소에 함수를 적용하고, 원본 데이터프레임과 동일한 크기와 형태의 결과를 반환
    대상: 각 행 또는 열의 각 원소에 함수를 적용. 그룹화된 데이터에도 사용 가능. 각 원소의 개별적 변환을 수행해서, 원본 프레임과 동일한 형태, 크기의 결과를 반환.
    속도: 각 원소에 적용하므로, 보다 빠르게 처리. 특히 그룹화된 데이터에 대해.
    => 실제로 사용해보면서 익혀야 더 확 와닿을 듯!

3-4 실습: 결측값을 groupby로 구분해서 경우에 맞게 채우기

  • 결측값을 남/여로 나눠서, 남/여의 평균값으로 각각 채워넣기
# 결측값을 랜덤으로 만들기. re.random Module 참고. 
np.random.seed(42)
tips_10 = sns.load_dataset('tips').sample(10)

tips_10.loc[
    np.random.permutation(tips_10.index)[:4], 'total_bill'
] = np.NaN

# 함수 정의
def fill_na_mean(x):
    avg = x.mean()
    return x.fillna(avg)
    
# groupby로 채워넣기
total_bill_group_mean = (tips_10.groupby('sex')['total_bill'].transform(fill_na_mean))
  • tips_10.groupby('sex') => 남여로 그룹화
  • ['total_bill'] 그 중 total_bill 열을 쪼갠다. .total_bill
  • transform(fill_na_mean)으로 그룹화된 각각에 fill_na_mean을 적용
  • 아래와 같이, groupby를 하지 않으면, total_bill 열이 통째로 들어가서, 빈값에 모두 전체 평균이 들어간다.
    total_bill_group_mean = (tips_10['total_bill'].transform(fill_na_mean))

3-5 filter()

  • 데이터프레임의 행 또는 열을 선택하는데 사용되는 메서드. 주어진 함수 기준으로 조건을 만족하는 행, 열을 선택.
  • 일반적으로 그룹화된 메서드에서, 각 그룹에 대해 특정 조건을 만족하는 행이나 열을 선택할 때 사용.
  • tips_filtered = (tips.groupby('size').filter(lambda x: x['size'].count() >= 30))
    여기서 lambda x의 x자리에 tips.groupby('size')라는 DataFrameGroupBy객체가 들어간다(집계까지 한 dataframe말고 groupby 객체에 filter를 걸어준다)
    거기서 ['size'] 열의 갯수를 센 게 30보다 크거나 작으면 그 행은 통과된다.

profile
일본에서 일하는 게임 기획자. 시시해서 죽어버리지 않게, 재밌고 의미 있는 컨텐츠에 관심 있습니다. 그 도구로 데이터, AI도 찝적댑니다.

0개의 댓글