티스토리 뷰

#16 판다스 chapter 16. 그룹연산 #1

 

그룹 연산

데이터 집계

데이터 집계하기

앞서 배웠던 갭마인더 데이터 집합으로 각 연도의 평균 수명을 구했던 것을 토대로 수집한 데이터를 바탕으로 평균이나 합 등을 구하여 의미있는 값을 도출해 내는 것을 '집계'라고 한다. 데이터를 집계하면 전체 데이터를 요약, 정리하여 볼 수 있기 때문에 데이터 분석이 훨씬 편해진다. 그러면 groupby 메서드로 평균값을 구하는 과정을 통해 데이터 집계가 무엇인지 알아보자.

 

  • groupby 메서드로 평균값 구하기

1) 먼저 갭마인더 데이터 집합을 불러온다.

 

import pandas as pd
df =  pd.read_csv('C:/Users/이재윤/doit_pandas_study/data/gapminder.tsv', sep='\t')

 

2) 다음은 year열을 기준으로 데이터를 그룹화한 다음 lifeExp 열의 평균을 구한 것이다.

 

avg_life_exp_by_year = df.groupby('year').lifeExp.mean()
print(avg_life_exp_by_year)

year
1952    49.057620
1957    51.507401
1962    53.609249
1967    55.678290
1972    57.647386
1977    59.570157
1982    61.533197
1987    63.212613
1992    64.160338
1997    65.014676
2002    65.694923
2007    67.007423
Name: lifeExp, dtype: float64

 

분할-반영-결합 과정 살펴보기 - groupby 메서드

앞서 groupby 메서드를 사용해 lifeExp 열의 연도별 평균값을 구했습니다. 그러면 실제로 groupby 메서드는 어떤 과정을 통해 데이터를 집계할까? groupby 메서드 자체를 분해하여 살펴보는 것은 불가능하기 때문에 비슷한 연산을 수행하는 메서드를 순서대로 실행하며 알아보자.

 

  • 분할-반영-결합 과정 살펴보기

1) 실제로 groupby 메서드에 life 열을 전달하면 가장 먼저 연도별로 데이터를 나누는 과정이 진행된다. 다음은 year 열의 데이터를 중복 없이 추출한 것이다. groupby 메서드에 열 이름을 전달하면 이런 '분할' 작업이 먼저 일어난다.

 

years = df.year.unique()
print(years)

[1952 1957 1962 1967 1972 1977 1982 1987 1992 1997 2002 2007]

 

2) 그런 다음 연도별로 평균값을 구한다. 그러려면 일단 각 연도별로 데이터를 추출해야 된다. 다음은 1952년의 데이터를 추출한 것이다. 이 과정을 '반영' 작업의 한 부분이라고 이해하면 된다.

 

y1952 = df.loc[df.year == 1952, :] 
print(y1952.head())

      country continent  year  lifeExp       pop    gdpPercap
0   Afghanistan      Asia  1952   28.801   8425333   779.445314
12      Albania    Europe  1952   55.230   1282697  1601.056136
24      Algeria    Africa  1952   43.077   9279525  2449.008185
36       Angola    Africa  1952   30.015   4232095  3520.610273
48    Argentina  Americas  1952   62.485  17876956  5911.315053

 

3) 아직 lifeExp 열의 평균값을 구하지 않았다. 다음은 과정 2에서 추출한 1952년 데이터에서 lifeExp 열의 평균값을 구한 것이다. 이 과정도 '반영'작업의 한 부분이다.

 

y1952_mean = y1952.lifeExp.mean() 
print(y1952_mean)

49.057619718309866

 

4) 과정 2~3을 반복하여 남은 연도의 평균값을 구하면 비로소 '반영' 작업이 끝난다.

 

y1957 = df.loc[df.year == 1957, :] 
y1957_mean = y1957.lifeExp.mean( )
print(y1957_mean)

y1962 = df.loc[df.year == 1962, :] 
y1962_mean = y1962.lifeExp.mean( )
print(y1962_mean)

y2007 = df.loc[df.year == 2007, :] 
y2007_mean = y2007.lifeExp.mean( )
print(y2007_mean)

51.50740112676056
53.609249014084504
67.00742253521126

 

5) 마지막으로 연도별로 계산한 lifeExp의 평균값을 합친다. 바로 이 과정이 '결합 작업이다.

 

df2 = pd.DataFrame({"year":[1952, 1957, 1962, 2007], 
                    "":[y1952_mean, y1957_mean,y1962_mean,y2007_mean]}) 
print(df2)

   year           
0  1952  49.057620
1  1957  51.507401
2  1962  53.609249
3  2007  67.007423

 

groupby 메서드와 함께 사용하는 집계 메서드

다음은 집계 메서드를 정리한 표이다.

 

  • 집계 메서드
  • count : 누락값을 제외한 데이터 수를 변환
  • size : 누락값을 포함한 데이터 수를 반환
  • mean : 평균값 반환
  • std : 표준편차 반환
  • min : 최솟값 반환
  • quantile(q=0.25) : 백분위수(25%)
  • quantile(q=0.50) : 백분위수(50%)
  • quantile(q=0.75) : 백분위수(75%)
  • max : 최댓값 반화
  • sum : 전체 합 반환
  • var : 분산 반환
  • sem : 평균의 표준편차 반환
  • describe : 데이터 수, 평균, 표준편차, 최소값, 백분위수(25,50,75%), 최댓값을 모두 반환
  • first : 첫 번째 행 반환
  • last : 마지막 행 반환
  • nth : n번째 행 반환

agg 메서드로 사용자 함수와 groupby 메서드 조합하기

라이브러리에서 제공하는 집계 메서드로 원하는 값을 계산할 수 없는 경우에는 직접 함수를 만들어서 사용해야 한다. 이번에는 사용자 함수와 groupby 메서드를 조합해서 사용해 보자. 사용자 함수와 groupby 메서드를 조합하려면 agg 메서드를 이용해야 한다.

 

  • 평균값을 구하는 사용자 함수와 groupby 메서드

1) 다음은 입력받은 열의 평균값을 구하는 함수이다.

 

def my_mean(values):
    n = len(values)
    
    sum = 0 
    for value in values:
        sum += value
    
    return sum / n

 

2) 다음은 과정 1에서 만든 함수를 groupby 메서드와 조합하기 위해 agg 메서드를 사용한 것이다. 결과를 보면 mean 메서드를 사용하여 얻은 값과 동일하다는 것을 알 수 있다.

 

agg_my_mean = df.groupby('year').lifeExp.agg(my_mean) 
print(agg_my_mean)

year
1952    49.057620
1957    51.507401
1962    53.609249
1967    55.678290
1972    57.647386
1977    59.570157
1982    61.533197
1987    63.212613
1992    64.160338
1997    65.014676
2002    65.694923
2007    67.007423
Name: lifeExp, dtype: float64

 

  • 2개의 인잣값을 받아 처리하는 사용자 함수와 groupby 메서드

1) 이번엔 2개의 인잣값을 받아 처리하는 사용자 정의 함수(my_mean_diff)를 만들어 보자. 다음은 첫 번째 인자로 받은 열의 평균값을 구하여 두 번째 인자로 받은 값과의 차이를 계산한 다음 반환하는 함수다.

 

def my_mean_diff(values, diff_value):
    n = len(values)
    sum = 0
    for value in values:
        sum += value 
    mean = sum / n 
    return mean - diff_value

 

 

2) 다음은 연도별 평균 수명에서 전체 평균 수명을 뺀 값을 구한 것이다. agg 메서드의 첫 번째 인자에 my_mean_diff 함수를 전달하고 두 번째 인자에 전체 평균 수명값을 전달 했다.

 

global_mean = df.lifeExp.mean()
print(global_mean)

59.474439366197174

agg_mean_diff = df.groupby('year').lifeExp.agg(my_mean_diff, diff_value=global_mean)
print(agg_mean_diff)

year
1952   -10.416820
1957    -7.967038
1962    -5.865190
1967    -3.796150
1972    -1.827053
1977     0.095718
1982     2.058758
1987     3.738173
1992     4.685899
1997     5.540237
2002     6.220483
2007     7.532983
Name: lifeExp, dtype: float64

 

여러 개의 집계 메서드 한 번에 사용하기

여러 개의 집계 메서드를 한 번에 사용하고 싶다면 어떻게 해야 할까? 집계 메서드를 리스트나 딕셔너리에 담아 agg 메서드에 전달하면 된다.

 

  • 집계 메서드를 리스트, 딕셔너리에 담아 전달하기

1) 다음은 연도별로 그룹화한 lifeExp 열의 0이 아닌 값의 개수, 평균, 표준편차를 한 번에 계산하여 출력한 것이다. 넘파이 메서드인 count_nonzero, mean, std를 리스트에 담아 agg 메서드에 전달했다.

 

import numpy as np
gdf = df.groupby('year').lifeExp.agg([np.count_nonzero, np.mean, np.std])
print(gdf)

      count_nonzero       mean        std
year                                     
1952          142.0  49.057620  12.225956
1957          142.0  51.507401  12.231286
1962          142.0  53.609249  12.097245
1967          142.0  55.678290  11.718858
1972          142.0  57.647386  11.381953
1977          142.0  59.570157  11.227229
1982          142.0  61.533197  10.770618
1987          142.0  63.212613  10.556285
1992          142.0  64.160338  11.227380
1997          142.0  65.014676  11.559439
2002          142.0  65.694923  12.279823
2007          142.0  67.007423  12.073021

 

2) 이번엔 집계 메서드를 딕셔너리에 담아 agg메서드에 전달해 보자. 딕셔너리의 키로 집계 메서드를 적용할 열 이름을 전달하고 딕셔너리의 값으로 집계 메서드를 전달하면 된다.

 

gdf_dict = df.groupby('year').agg({'lifeExp': 'mean', 'pop': 'median', 'gdpPercap': 'median'})
print(gdf_dict)

        lifeExp         pop    gdpPercap
year                                    
1952  49.057620   3943953.0  1968.528344
1957  51.507401   4282942.0  2173.220291
1962  53.609249   4686039.5  2335.439533
1967  55.678290   5170175.5  2678.334740
1972  57.647386   5877996.5  3339.129407
1977  59.570157   6404036.5  3798.609244
1982  61.533197   7007320.0  4216.228428
1987  63.212613   7774861.5  4280.300366
1992  64.160338   8688686.5  4386.085502
1997  65.014676   9735063.5  4781.825478
2002  65.694923  10372918.5  5319.804524
2007  67.007423  10517531.0  6124.371108

 

데이터 변환

이번에는 데이터 변환 메서드에 대해 알아 보자. 데이터 변환 메서드는 데이터와 메서드를 일대일로 대응시켜 계산하기 때문에 데이터의 양이 줄어들지 않는다. 말 그대로 데이터를 변환하는 데 사용한다.

표준점수 계산하기

통계 분야에서는 데이터의 평균과 표준편차의 차이를 표준점수라고 부른다. 표준점수를 구하면 변환한 데이터의 평균값이 0이 되고 표준편차는 1이 된다. 그러면 데이터가 표준화되어 서로 다른 데이터를 쉽게 비교할 수 있다.

 

  • 표준점수 계산하기

1) 다음은 표준점수를 계산하는 함수이다.

 

def my_zscore(x):
    return (x - x.mean()) / x.std()

 

2) 다음은 각 연도별 lifeExp 열의 표준점수를 계산한 것이다. my_zscore 함수를 적용 하기 위해 transform 메서드를 사용했다.

 

transform_z = df.groupby('year').lifeExp.transform(my_zscore)

print(transform_z.head())

0   -1.656854
1   -1.731249
2   -1.786543
3   -1.848157
4   -1.894173
Name: lifeExp, dtype: float64

 

3) my_zscore 함수는 데이터를 표준화할 뿐 집계는 하지 않는다. 즉, 데이터의 양이 줄어들지 않는다. 다음은 원본 데이터프레임의 데이터 크기와 변환한 데이터프레임의 데이터 크기를 비교한 것이다.

 

print(df.shape)

print(transform_z.shape)

(1704, 6)
(1704,)

 

누락값을 평균값으로 처리하기

앞서 누락값을 처리하는 다양한 방법에 대해 살펴보았다. 하지만 가끔은 누락값을 평균값으로 처리하는 것이 더 좋을 때가 있다.

 

  • 누락값을 평균값으로 처리하기

1) 다음은 seaborn 라이브러리의 tips 데이터 집합에서 10개의 행 데이터만 가져온 다음 total_bill 열의 값 4개를 임의로 선택하여 누락값으로 바꾼 것이다.

 

import seaborn as sns
import numpy as np

np.random.seed(42)

tips_10 =  sns.load_dataset('tips').sample(10)
tips_10.loc[np.random.permutation(tips_10.index)[:4], 'totall_bill'] = np.NaN
print(tips_10)

    total_bill   tip     sex smoker   day    time  size  totall_bill
24        19.82  3.18    Male     No   Sat  Dinner     2          NaN
6          8.77  2.00    Male     No   Sun  Dinner     2          NaN
153       24.55  2.00    Male     No   Sun  Dinner     4          NaN
211       25.89  5.16    Male    Yes   Sat  Dinner     4          NaN
198       13.00  2.00  Female    Yes  Thur   Lunch     2          NaN
176       17.89  2.00    Male    Yes   Sun  Dinner     2          NaN
192       28.44  2.56    Male    Yes  Thur   Lunch     2          NaN
124       12.48  2.52  Female     No  Thur   Lunch     2          NaN
9         14.78  3.23    Male     No   Sun  Dinner     2          NaN
101       15.38  3.00  Female    Yes   Fri  Dinner     2          NaN

 

2) 그런데 total_bill 열의 누락값을 단순히 total_bill 열의 평균값으로 채우면 안된다. 현재 tips_10의 데이터는 여성보다 남성이 더 많다. 즉, 여성과 남성을 구분하여 total_bill 열의 평균값을 구하지 않으면 여성 데이터가 남성 데이터의 영향을 많이 받아 여성의 데이터가 훼손될 수 있다. 다음은 성별로 그룹화한 다음 각 열의 데이터 수를 구한 것이다. total_bill 열을 살펴보면 남성의 누락값은 3개, 여성의 누락값은 1개라는 것을 알 수 있다.

 

count_sex = tips_10.groupby('sex').count()
print(count_sex)

total_bill  tip  smoker  day  time  size  totall_bill
sex                                                          
Male             7    7       7    7     7     7            0
Female           3    3       3    3     3     3            0

 

3) 다음은 성별을 구분하여 total_bill 열의 데이터를 받아 평균값을 구하는 함수이다.

 

def fill_na_mean(x):
    avg = x.mean()
    return x.fillna(avg)

 

4) 다음은 성별을 구분한 다음 total_bill 열의 데이터를 fill_na_mean 함수에 전달하여 평균값을 구한 다음 tips_10에 새로운 열로 추가한 것이다. 남성과 여성의 누락값을 고려하여 계산한 평균값으로 잘 채워져 있는 것을 알 수 있다.

 

total_bill_group_mean = tips_10.groupby('sex').total_bill.transform(fill_na_mean)
tips_10['fill_total_bill'] = total_bill_group_mean
print(tips_10)

 total_bill   tip     sex smoker   day    time  size  totall_bill  \
24        19.82  3.18    Male     No   Sat  Dinner     2          NaN   
6          8.77  2.00    Male     No   Sun  Dinner     2          NaN   
153       24.55  2.00    Male     No   Sun  Dinner     4          NaN   
211       25.89  5.16    Male    Yes   Sat  Dinner     4          NaN   
198       13.00  2.00  Female    Yes  Thur   Lunch     2          NaN   
176       17.89  2.00    Male    Yes   Sun  Dinner     2          NaN   
192       28.44  2.56    Male    Yes  Thur   Lunch     2          NaN   
124       12.48  2.52  Female     No  Thur   Lunch     2          NaN   
9         14.78  3.23    Male     No   Sun  Dinner     2          NaN   
101       15.38  3.00  Female    Yes   Fri  Dinner     2          NaN   

     fill_total_bill  
24             19.82  
6               8.77  
153            24.55  
211            25.89  
198            13.00  
176            17.89  
192            28.44  
124            12.48  
9              14.78  
101            15.38  

 

 

출처 : 데이터 분석을 위한 판다스 입문

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
글 보관함