이번 포스팅은 두 개의 연속형 변수에 대한 관계를 파악하는데 유용하게 사용할 수 있는 산점도(Scatter Plot) 의 세번째 포스팅으로서 4개의 연속형 변수를 사용하여 X축, Y축, 점의 색깔(color)과 크기(size)을 다르게 하는 방법을 소개합니다. 


즉, 산점도를 사용하여 4차원의 데이터를 2차원에 시각화하는 방법입니다. 




(1) 산점도 (Scatter Plot)

(2) 그룹별 산점도 (Scatter Plot by Groups)

(3) 4개 변수로 점의 크기와 색을 다르게 산점도 그리기 (Scatter plot with different size, color)

(4) 산점도 행렬 (Scatter Plot Matrix)

 



예제로 활용할 데이터는 iris 의 4개의 연속형 변수들입니다. 



# importing libraries

import numpy as np

import pandas as pd


import matplotlib.pyplot as plt

import seaborn as sns

plt.rcParams['figure.figsize'] = [10, 8] # setting figure size

 


 

# loading 'iris' dataset from seaborn

iris = sns.load_dataset('iris')

iris.shape

(150, 5)


iris.head()

sepal_lengthsepal_widthpetal_lengthpetal_widthspecies
05.13.51.40.2setosa
14.93.01.40.2setosa
24.73.21.30.2setosa
34.63.11.50.2setosa
45.03.61.40.2setosa





  (1) matplotlib에 의한 4개 연속형 변수를 사용한 산점도 (X축, Y축, 색, 크기)


plt.scatter() 함수를 사용하며, 점의 크기는 s, 점의 색깔은 c 에 변수를 할당해주면 됩니다. 



# 4 dimensional scatter plot with different size & color

plt.scatter(iris.sepal_length, # x

           iris.sepal_width, # y

           alpha=0.2, 

           s=200*iris.petal_width, # marker size

           c=iris.petal_length, # marker color

           cmap='viridis')

plt.title('Scatter Plot with Size(Petal Width) & Color(Petal Length)', fontsize=14)

plt.xlabel('Sepal Length', fontsize=12)

plt.ylabel('Sepal Width', fontsize=12)

plt.colorbar()

plt.show()





점(marker)의 모양을 네모로 바꾸고 싶으면 marker='s' 로 설정해주면 됩니다. 



# 4 dimensional scatter plot with different size & color

plt.scatter(iris.sepal_length, # x

           iris.sepal_width, # y

           alpha=0.2, 

           s=200*iris.petal_width, # marker size

           c=iris.petal_length, # marker color

           cmap='viridis'

           marker = 's') # square shape

plt.title('Size(Petal Width) & Color(Petal Length) with Square Marker', fontsize=14)

plt.xlabel('Sepal Length', fontsize=12)

plt.ylabel('Sepal Width', fontsize=12)

plt.colorbar()

plt.show()

 





  (2) seaborn에 의한 4개 연속형 변수를 사용한 산점도 (X축, Y축, 색, 크기)


seaborn 의 산점도 코드는 깔끔하고 이해하기에 쉬으며, 범례도 잘 알아서 색깔과 크기를 표시해주는지라 무척 편리합니다. 



# 4 dimensional scatter plot by seaborn

sns.scatterplot(x='sepal_length', 

                y='sepal_width', 

                hue='petal_length',

                size='petal_width',

                data=iris)

plt.show()





  (3) pandas에 의한 4개 연속형 변수를 사용한 산점도 (X축, Y축, 색, 크기)


pandas의 DataFrame에 plot(kind='scatter') 로 해서 color=iris['petal_length']로 색깔을 설정, s=iris['petal_width'] 로 크기를 설정해주면 됩니다. pandas 산점도 코드도 깔끔하고 이해하기 쉽긴 한데요, 범례 추가하기가 쉽지가 않군요. ^^; 



iris.plot(kind='scatter'

          x='sepal_length', 

          y='sepal_width', 

          color=iris['petal_length'],

          s=iris['petal_width']*100)


plt.title('Size(Petal Width) & Color(Petal Length) with Square Marker', fontsize=14)

plt.show()




참고로, 산점도의 점(marker)의 모양(shape)을 설정하는 심벌들은 아래와 같으니 참고하시기 바랍니다. 



# set the size

plt.rcParams['figure.figsize'] = [10, 8]


# remove ticks and values of axis

plt.xticks([])

plt.yticks([])


# markers' shape

all_shape=['.','o','v','^','>','<','s','p','*','h','H','D', 'd', '', '', '']


num = 0

for x in range(1, 5):

    for y in range(1, 5):

        num += 1

        

        plt.plot(x, y, 

                 marker = all_shape[num-1], 

                 markerfacecolor='green', 

                 markersize=20, 

                 markeredgecolor='black')

        

        plt.text(x+0.1, y, 

                 all_shape[num-1], 

                 horizontalalignment='left', 

                 size='medium', 

                 color='black', 

                 weight='semibold')

        

plt.title('Markers', fontsize=20)        

plt.show()



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


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


다음번 포스팅에서는 산점도 행렬 (Scatter Plot Matrix) 에 대해서 소개하겠습니다. 



Posted by R Friend R_Friend

이번 포스팅에서는 Python pandas의 GroupBy()와 apply() 함수를 사용하여 그룹별로 무작위 표본 추출 (Random Sampling and Permutation by Groups) 하는 방법을 소개하겠습니다. 


무작위 표본 추출을 하는 데는 np.random.permutation() 함수를 사용해서 순열을 무작위로 뒤섞은 후에 n개 만큼 indexing 해오는 방법을 사용하였습니다. 



[ 그룹 별 무작위 표본 추출 (Random Sampling and Permutation by Groups) ]




먼저 예제로 사용한 'grp_1'과 'grp_2'의 두 개의 그룹을 포함한 DataFrame을 만들어보겠습니다. 



import numpy as np

import pandas as pd


# setting seed number for reproducibility

np.random.seed(123)


# Make a DataFrame

df = pd.DataFrame({'grp': ['grp_1']*10 + ['grp_2']*10, 

                          'col_1': np.random.randint(20, size=20), 

                          'col_2': np.random.randint(20, size=20)})

 

df

col_1col_2grp
01316grp_1
124grp_1
2217grp_1
363grp_1
4172grp_1
5197grp_1
6102grp_1
7115grp_1
8016grp_1
9177grp_1
10159grp_2
1193grp_2
1206grp_2
13141grp_2
1402grp_2
15151grp_2
161912grp_2
17148grp_2
1843grp_2
19010grp_2





사용자가 지정한 샘플링 비율(sample_pct) 만큼 무작위 표본 추출을 해주는 사용자 정의 함수 (User Defined Function)을 정의해보겠습니다. 



# UDF of random sampling

def sampling_func(data, sample_pct):

    np.random.seed(123)

    N = len(data)

    sample_n = int(len(data)*sample_pct) # integer

    sample = data.take(np.random.permutation(N)[:sample_n])

    return sample

 



이제 위의 무작위 샘플링 사용자 정의 함수(UDF)를 GroupBy()와 apply() 함수에 적용해서 그룹별 특정 비율(이 예에서는 80%)만큼 무작위 표본 추출(random sampling and permutation by groups)을 해보겠습니다.  



# Apply random sampling UDF to groups

sample_set = df.groupby('grp').apply(sampling_func, sample_pct=0.8)


sample_set.sort_index()

col_1col_2grp
grp
grp_101316grp_1
124grp_1
363grp_1
4172grp_1
5197grp_1
6102grp_1
7115grp_1
8016grp_1
grp_210159grp_2
1193grp_2
13141grp_2
1402grp_2
15151grp_2
161912grp_2
17148grp_2
1843grp_2

 



위의 무작위 표본 추출 결과에서 group_keys 를 없애려면 group_keys = False 를 지정해주면 됩니다. 



# disable group_keys

sample_set = df.groupby('grp', group_keys=False).apply(sampling_func, sample_pct=0.8)


sample_set.sort_index()

col_1col_2grp
01316grp_1
124grp_1
363grp_1
4172grp_1
5197grp_1
6102grp_1
7115grp_1
8016grp_1
10159grp_2
1193grp_2
13141grp_2
1402grp_2
15151grp_2
161912grp_2
17148grp_2
1843grp_2




위의 80% 무작위 샘플링이 된 sample_set 에 있는 데이터셋을 training_set 이라고 가정해보고, 위의 sample_set에는 없지만 원래 데이터에는 있던 나머지 20% 데이터를 test_set 으로 별도로 만들어보겠습니다. 



test_set = df.drop(df.index[sample_set.index])

test_set

col_1col_2grp
2217grp_1
9177grp_1
1206grp_2
19010grp_2

 



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


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



Posted by R Friend R_Friend

여러번에 걸친 지난 포스팅에서는 groupby()와 apply()를 이용하여 그룹 별로 통계량을 구한다든지, 결측값을 대체한다든지, 변수 간 상관계수를 구하는 방법을 소개하였습니다.  


이번 포스팅에서는 groupby()와 apply()를 사용하여 그룹 별로 선회회귀모형을 적합(Group-wise Linear Regression)하는 방법을 소개하겠습니다. 


그룹 개수가 많고, 그룹별로 회귀계수를 비교하고자 할 때 이번 포스팅을 참고하면 그룹별로 일일이 하나씩 모형을 적합하지 않고도 짧은 코드로 간편하게 그룹별 회귀모형을 적합할 수 있습니다. 



[ 그룹 별 선형회귀모형 적합 (Group-wise Linear Regression) ]




예제에 사용할 diabetes (당뇨병) 데이터와 선형회귀모형을 적합할 때 사용할 sklearn의 linear_model 모듈을 importing 하겠습니다. 


당뇨병 환자 442명의 'age', 'sex', bmi', bp', 's1', 's2', 's3', 's4', 's5', 's6' 등의 10개 설명변수와, 우리가 예측하고자 하는 'target' 반응변수가 있는 데이터셋입니다. 설명변수는 표준화가 되어있군요. 



import numpy as np

import pandas as pd

from sklearn import datasets, linear_model


diabetes = datasets.load_diabetes()


diabetes.DESCR 

'Diabetes dataset
================
Notes
-----
Ten baseline variables, age, sex, body mass index, average blood

npressure, and six blood serum measurements were obtained for each of n =442 diabetes patients, as well as the response of interest, a\nquantitative measure of disease progression one year after baseline.\n\nData Set Characteristics:\n\n :Number of Instances: 442\n\n :Number of Attributes: First 10 columns are numeric predictive values\n\n :Target: Column 11 is a quantitative measure of disease progression one year after baseline\n\n :Attributes:\n :Age:\n :Sex:\n :Body mass index:\n :Average blood pressure:\n :S1:\n :S2:\n :S3:\n :S4:\n :S5:\n :S6:\n\nNote: Each of these 10 feature variables have been mean centered and scaled by the standard deviation times `n_samples` (i.e. the sum of squares of each column totals 1).\n\nSource URL:\nhttp://www4.stat.ncsu.edu/~boos/var.select/diabetes.html\n\nFor more information see:\nBradley Efron, Trevor Hastie, Iain Johnstone and Robert Tibshirani (2004) "Least Angle Regression," Annals of Statistics (with discussion), 407-499.\n(http://web.stanford.edu/~hastie/Papers/LARS/LeastAngle_2002.pdf)\n'


diabetes.feature_names

['age', 'sex', 'bmi', 'bp', 's1', 's2', 's3', 's4', 's5', 's6']

diabetes.target[:5]
array([151.,  75., 141., 206., 135.])




이번 포스팅이 '그룹'별 선형회귀모형 분석이므로 'sex(성별)'을 'M (Male)', 'F (Female)' 의 2개 그룹으로 나누어서 'age', 'bmi' 의 2개의 설명변수만을 사용하여 'target' 을 예측하는 그룹별 회귀모형을 적합하여 보겠습니다. 필요한 변수만 선별해서 DataFrame을 만들어보겠습니다. 



# Make a DataFrame of Y ('target')

diabetes_Y = pd.DataFrame(diabetes.target, columns = ['target'])

diabetes_Y[:5]

target
0151.0
175.0
2141.0
3206.0
4135.0

 


# Make a DataFrame of X with age, sex, bmi

diabetes_X = pd.DataFrame(diabetes.data[:, 0:3], 

                          columns = ['age', 'sex', 'bmi']) # age, sex, bmi


diabetes_X[:5]

agesexbmi
00.0380760.0506800.061696
1-0.001882-0.044642-0.051474
20.0852990.0506800.044451
3-0.089063-0.044642-0.011595
40.005383-0.044642-0.036385



diabetes_df = pd.concat([diabetes_Y, diabetes_X], axis=1)

diabetes_df[:5]

targetagesexbmi
0151.00.0380760.0506800.061696
175.0-0.001882-0.044642-0.051474
2141.00.0852990.0506800.044451
3206.0-0.089063-0.044642-0.011595
4135.00.005383-0.044642-0.036385




'sex' 변수를 가지고 'M', 'F' 를 class로 가지는 'grp'라는 범주형 변수를 만든 후에, 'sex' 변수는 삭제하겠습니다. 



diabetes_df['grp'] = np.where(diabetes_df['sex'] > 0, 'M', 'F')

diabetes_df.drop(columns=['sex'], inplace=True) 

diabetes_df[:3]

targetagebmigrp
0151.00.0380760.061696M
175.0-0.001882-0.051474F
2141.00.0852990.044451M




이제 GroupBy()의 apply()에 사용할 선형회귀모형 사용자 정의 함수(UDF)를 정의해보겠습니다. 각 그룹별 age와 bmi변수의 회귀계수를 비교하고 싶다고 했으므로 사용자 정의 함수에서 그룹별 회귀모형의 회귀계수와 절편을 결과로 반환하도록 하였습니다. 


# UDF of linear regression model

def lin_regress(data, yvar, xvars):


    # output, input variables

    Y = data[yvar]

    X = data[xvars]

    

    # Create linear regression object

    linreg = linear_model.LinearRegression()

    

    # Fit the linear regression model

    model = linreg.fit(X, Y)

    

    # Get the intercept and coefficients

    intercept = model.intercept_

    coef = model.coef_

    result = [intercept, coef]

    

    return result

 



다음으로 GroupBy()와 apply()를 사용해서 성별 그룹('M', 'F')별 선형회귀모형을 적합해보겠습니다. 



# GroupBy

grouped = diabetes_df.groupby('grp')

# Apply the UDF of linear regression model by Group

lin_reg_coef = grouped.apply(lin_regress, 'target', ['age', 'bmi'])

 



남성('M')과 여성('F') 그룹별 Y절편과 age, bmi 변수의 회귀계수 적합 결과를 조회해보겠습니다. 



lin_reg_coef

grp
F    [152.40684676047456, [23.199210147823813, 814....
M    [148.21507864445124, [291.7563226838977, 1092.... 

dtype: object 


lin_reg_coef['M']

[148.21507864445124, array([ 291.75632268, 1092.80118705])]


lin_reg_coef['F']

[152.40684676047456, array([ 23.19921015, 814.50932703])]




위의 그룹별 선형회귀무형 적합 결과로부터 우리는 아래의 모형을 얻었습니다. 

  • 남성('M') 그룹의 당뇨병 진단 target = 148.2 + 291.8*age + 1,092.8*bmi
  • 여성('F') 그룹의 당뇨병 진단 target = 152.4 + 23.2*age + 814.5*bmi

(이때 age, bmi 는 표준화한 후의 input 값임)


따라서 다른 변수가 고정되었다고 했을 때 (표준화한) bmi 값이 한 단위 증가할 때 '남성('M')' 그룹의 당뇨병 진단 target 은 1,092.8 만큼 증가하는 반면에 '여성('F') 그룹의 당뇨병 진단 target은 814.5 만큼 증가하는 것으로 나왔습니다. 


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


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



Posted by R Friend R_Friend

이번 포스팅에서는 그룹 별로 변수 간 상관관계 분석 (correlation with columns by groups) 하는 방법을 소개하겠습니다. 


Python pandas는 Pearson Correlation Coefficient를 구할 수 있는 corr(), corrwith() 함수를 제공합니다. 이들 함수를 groupby() 와 apply(lambda func)를 함께 사용함으로써 그룹 별 변수 간 상관계수를 구할 수 있습니다. 



[ 피어슨 상관계수 ( Pearson Correlation Coefficient) ]



먼저 예제로 사용할 'group 1'과 'group 2'의 2개의 그룹을 가진 간단한 DataFrame을 만들어보겠습니다. 




import numpy as np

import pandas as pd

from pandas import DataFrame


# making DataFrame with 4 random variables

np.random.seed(123) # for reproducibility

df= DataFrame(np.random.randn(10, 4), 

                    columns=['a', 'b', 'c', 'd'])

# setting index with 2 group, 'grp1' and 'grp2'

df['group'] = ['grp1', 'grp1', 'grp1', 'grp1', 'grp1', 

                  'grp2', 'grp2', 'grp2', 'grp2', 'grp2']


df = df.set_index('group')


df

abcd
group
grp1-1.0856310.9973450.282978-1.506295
grp1-0.5786001.651437-2.426679-0.428913
grp11.265936-0.866740-0.678886-0.094709
grp11.491390-0.638902-0.443982-0.434351
grp12.2059302.1867861.0040540.386186
grp20.7373691.490732-0.9358341.175829
grp2-1.253881-0.6377520.907105-1.428681
grp2-0.140069-0.861755-0.255619-2.798589
grp2-1.771533-0.6998770.927462-0.173636
grp20.0028460.688223-0.8795360.283627







  (1) 'd' 변수와 나머지 모든 변수 간 그룹 별 상관계수 구하기 : x.corrwith(x['d'])



# correlation with columns: corrwith()

corr_with_d = lambda x: x.corrwith(x['d'])


grouped = df.groupby('group')


grouped.apply(corr_with_d) 

abcd
group
grp10.8468220.0994170.0892051.0
grp20.3074770.832473-0.3904691.0





  (2) 'a'변수와 'd'변수 간 그룹 별 상관계수 구하기 : g['a'].corr[g['d'])



# inter-column correlation: corr()

corr_a_d = lambda g: g['a'].corr(g['d'])


grouped = df.groupby('group')


DataFrame(grouped.apply(corr_a_d))

0
group
grp10.846822
grp20.307477

 



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


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



Posted by R Friend R_Friend

이번 포스팅에서는 groupby() 를 사용할 때 


(1) pd.cut()으로 동일 길이로 나누어서 범주를 만든 후 GroupBy()로 그룹별 통계량 구하기

(2) pd.qcut()으로 동일 개수로 나누어서 범주를 만든 후 GroupBy()로 그룹별 통계량 구하기


를 해보겠습니다. 




먼저, 예제로 사용할 간단한 DataFrame을 만들어보겠습니다. 



import numpy as np

import pandas as pd

from pandas import DataFrame


np.random.seed(123)

df = DataFrame({'col_1': np.random.randint(20, size=20), 

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


df

col_1col_2
0131.730024
121.232650
22-0.823598
36-0.118201
417-0.576103
5191.695731
610-0.074394
71-1.900637
80-0.777655
9171.313462
10151.804458
119-0.965550
120-1.316480
1314-0.625785
140-0.326946
1515-0.308209
16190.827117
17141.070781
184-3.055577
1901.005932

 




  (1) pd.cut : 동일 길이로 나누어서 범주 만들기(equal-length buckets categorization)


'col_1' 칼럼에 대해서 4개의 동일한 길이로 범주를 만들어보겠습니다. 

카테고리의 구간이 [(-0.019, 4.75] < (4.75, 9.5] < (9.5, 14.25] < (14.25, 19.0]] 로서 4개의 각 구간의 길이가 동일함을 알 수 있습니다.



factor_col_1 = pd.cut(df.col_1, 4)

factor_col_1

0 (9.5, 14.25]

1     (-0.019, 4.75]
2     (-0.019, 4.75]
3        (4.75, 9.5]
4      (14.25, 19.0]
5      (14.25, 19.0]
6       (9.5, 14.25]
7     (-0.019, 4.75]
8     (-0.019, 4.75]
9      (14.25, 19.0]
10     (14.25, 19.0]
11       (4.75, 9.5]
12    (-0.019, 4.75]
13      (9.5, 14.25]
14    (-0.019, 4.75]
15     (14.25, 19.0]
16     (14.25, 19.0]
17      (9.5, 14.25]
18    (-0.019, 4.75]
19    (-0.019, 4.75]
Name: col_1, dtype: category
Categories (4, interval[float64]): [(-0.019, 4.75] < (4.75, 9.5] < (9.5, 14.25] < (14.25, 19.0]]



이제 'factor_col_1'이라는 'col_1' 칼럼에 대한 4개 구간의 범주를 GroupBy() 에 넣어서 각 범주의 그룹별로 agg() 함수로 개수(count), 평균(mean), 표준편차(std), 최소값(min), 최대값(max) 값을 계산해보겠습니다. 



grouped_col_1 = df.col_1.groupby(factor_col_1)

grouped_col_1.agg(['count', 'mean', 'std', 'min', 'max'])

countmeanstdminmax
col_1
(-0.019, 4.75]81.1251.45773804
(4.75, 9.5]27.5002.12132069
(9.5, 14.25]412.7501.8929691014
(14.25, 19.0]617.0001.7888541519



위와 동일한 결과를 아래 처럼 통계집계를 하는 사용자정의함수와 apply() 를 사용해서 구할 수도 있습니다. 


 

def summary_func(group):

    return {'count': group.count()

             'mean': group.mean()

             'std': group.std()

             'min': group.min()

             'max': group.max()}


grouped_col_1.apply(summary_func)

col_1                
(-0.019, 4.75]  count     8.000000
                max       4.000000
                mean      1.125000
                min       0.000000
                std       1.457738
(4.75, 9.5]     count     2.000000
                max       9.000000
                mean      7.500000
                min       6.000000
                std       2.121320
(9.5, 14.25]    count     4.000000
                max      14.000000
                mean     12.750000
                min      10.000000
                std       1.892969
(14.25, 19.0]   count     6.000000
                max      19.000000
                mean     17.000000
                min      15.000000
                std       1.788854
Name: col_1, dtype: float64




위의 결과를 좀더 보기에 좋도록 unstack()를 사용해서 길게(long) 제시된 결과를 옆으로 넓게(wide) 표형식으로 만들어보겠습니다. 



grouped_col_1.apply(summary_func).unstack()

countmaxmeanminstd
col_1
(-0.019, 4.75]8.04.01.1250.01.457738
(4.75, 9.5]2.09.07.5006.02.121320
(9.5, 14.25]4.014.012.75010.01.892969
(14.25, 19.0]6.019.017.00015.01.788854

 



위의 결과의 'count' 개수 부분을 보면 각 범주 구간  [(-0.019, 4.75] < (4.75, 9.5] < (9.5, 14.25] < (14.25, 19.0]] 그룹 별로 개수가 8개, 2개, 4개, 6개로서 각각 다릅니다. 이는 랜덤 숫자에 대해서 구간별 길이를 동일하게 했기 때문에 구간 그룹별 개수가 다르게 된 것입니다.


그러면, 다음으로 구간별 '동일한 개수(equal-size)'로 범주 바구니(bucket categorization)를 만들어보겠습니다.




  (2) pd.qcut() : 동일 개수로 나누어서 범주 만들기 (equal-size buckets categorization)


pd.qcut() 함수를 사용하여 'col_2'에 대해서 각 범주 바구니별로 동일하게 4개의 개수를 가지도록 범주를 만들어보겠습니다. 이때 labels=False 로 설정하여 label이 0, 1, 2, 3 이런 식으로 0부터 순차적으로 1씩 증가하게 하였습니다. 



bucket_qcut_col_2 = pd.qcut(df.col_2, 4, labels=False)

bucket_qcut_col_2

0     3
1     3
2     0
3     2
4     1
5     3
6     2
7     0
8     1
9     3
10    3
11    0
12    0
13    1
14    1
15    1
16    2
17    2
18    0
19    2
Name: col_2, dtype: int64




아래처럼 labels=np.arange(4, 0, -1)로 직접 지정을 해주면 label이 4, 3, 2, 1 이런식으로 4부터 1씩 줄어드는 순서로 할당이 됩니다. 위의 label 과 정 반대로 할당이 되었습니다. 



bucket_qcut_label_col_2 = pd.qcut(df.col_2, 4, labels=np.arange(4, 0, -1))

bucket_qcut_label_col_2

0     1
1     1
2     4
3     2
4     3
5     1
6     2
7     4
8     3
9     1
10    1
11    4
12    4
13    3
14    3
15    3
16    2
17    2
18    4
19    2
Name: col_2, dtype: category
Categories (4, int64): [4 < 3 < 2 < 1]




그럼 [4 < 3 < 2 < 1] 순서로 동일 개수로 나눈 4개의 그룹별 통계량을 계산해보겠습니다. 



grouped = df.col_2.groupby(bucket_qcut_label_col_2)

grouped.apply(summary_func).unstack()

countmaxmeanminstd
col_2
45.0-0.823598-1.612369-3.0555770.907474
35.0-0.308209-0.522940-0.7776550.201746
25.01.0707810.542247-0.1182010.589903
15.01.8044581.5552651.2326500.262163



'count' 개수가 4개의 각 그룹별로 모두 '5'로서 동일한 것을 알 수 있습니다. 


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


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



Posted by R Friend R_Friend

이번 포스팅에서는 Pandas DataFrame에서 groupby() 를 사용해 그룹 단위로 요약 통계량(group-level statistics aggregation)을 집계하여 원래의 DataFrame에 transform() 함수를 사용하여 통계량 칼럼을 추가하는 방법을 소개하겠습니다. 




예제로 사용할 간단한 Pandas DataFrame을 만들어보겠습니다. 



 import numpy as np

 import pandas as pd

 from pandas import DataFrame


 df = DataFrame({'group_1': ['a', 'a', 'a', 'a', 'a',  

                                      'b', 'b', 'b', 'b', 'b',], 

                       'group_2': ['c', 'c', 'c', 'd', 'd', 

                                     'e', 'e', 'e', 'f', 'f'], 

                       'col': [1, 2, np.NaN, 4, np.NaN, 

                              6, 7, np.NaN, 9, 10]})

 

 df

colgroup_1group_2
01.0ac
12.0ac
2NaNac
34.0ad
4NaNad
56.0be
67.0be
7NaNbe
89.0bf
910.0bf




위의 DataFrame에 대해서 


(1)  GroupBy() 로 'group_1'과 'group_2' 칼럼을 모두 적용한 그룹을 만들어서
     [그룹 ('a', 'c'), 그룹 ('a', 'd'), 그룹 ('b'e, 'e'), 그룹 ('b', 'f')]


(2) 그룹별로 transform() 함수를 사용하여 NaN이 아닌 원소의 '개수(count)', '합(sum)', '최대값(max)'을 계산하여 


(3) df의 DataFrame에 새로운 칼럼을 추가해보겠습니다. 



  (a) 그룹 별 NaN이 아닌 원소의 개수 구하여 데이터프레임에 새로운 칼럼 추가하기



# 'count' grouped by (['group_1', 'group_2'])

 df['count_col'] = df.groupby(['group_1', 'group_2']).col.transform('count')

 df

col

group_1group_2

count_col

01.0ac2.0
12.0ac2.0
2NaNac2.0
34.0ad1.0
4NaNad1.0
56.0be2.0
67.0be2.0
7NaNbe2.0
89.0bf2.0
910.0bf2.0

 




  (b) 그룹 별 NaN이 아닌 원소의 합을 구하여 데이터프레임에 새로운 칼럼 추가하기



 # 'sum' grouped by (['group_1', 'group_2'])

 df['sum_col'] = df.groupby(['group_1', 'group_2']).col.transform('sum')

 df

colgroup_1group_2count_colsum_col
01.0ac2.03.0
12.0ac2.03.0
2NaNac2.03.0
34.0ad1.04.0
4NaNad1.04.0
56.0be2.013.0
67.0be2.013.0
7NaNbe2.013.0
89.0bf2.019.0
910.0bf2.019.0





  (c) 그룹 별 NaN이 아닌 원소 중 최대값을 구하여 데이터프레임에 새로운 칼럼 추가하기



 # 'max' grouped by (['group_1', 'group_2'])

 df['max_col'] = df.groupby(['group_1', 'group_2']).col.transform('max')

 df

colgroup_1group_2count_colsum_colmax_col
01.0ac2.03.02.0
12.0ac2.03.02.0
2NaNac2.03.02.0
34.0ad1.04.04.0
4NaNad1.04.04.0
56.0be2.013.07.0
67.0be2.013.07.0
7NaNbe2.013.07.0
89.0bf2.019.010.0
910.0bf2.019.010.0




이번 포스팅에서는 groupby() 연산자와 transform('statistics function') 함수를 사용하여 그룹별로 통계량을 계산하여 기존의 DataFrame에 새로운 그룹별 집계된 통계량을 새로운 변수로 추가하는 방법에 대해서 알아보았습니다. 


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


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



Posted by R Friend R_Friend
이전 포스팅에서 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

 


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


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



Posted by R Friend R_Friend

이번 포스팅에서는 Python pandas의 GroupBy operation을 이용해서 그룹 별 가중평균(Group weighted average)을 구하는 방법을 소개하겠습니다. 


앞서 GroupBy 연산자의 원리에서 소개드렸던 것처럼, Split => Apply => Combine 의 절차를 거치면서 각 그룹별 GroupBy 연산을 실행하게 됩니다. 


[GroupBy 를 이용한 그룹별 가중 평균 구하기 절차]




예제로 사용할 'a'와 'b'의 두 개 그룹별로 'value 값'과 'weight 가중치'를 가지고 있는 간단한 데이터 프레임을 만들어보겠습니다. 



import pandas as pd

import numpy as np


df = pd.DataFrame({'grp_col' : ['a', 'a', 'a', 'a', 'a', 'b', 'b', 'b', 'b', 'b'], 

                   'val' : np.arange(10)+1,                   

                   'weight' : [0.0, 0.1, 0.2, 0.3, 0.4, 0.0, 0.1, 0.2, 0.3, 0.4]})


df

grp_colvalweight
0a10.0
1a20.1
2a30.2
3a40.3
4a50.4
5b60.0
6b70.1
7b80.2
8b90.3
9b100.4



  (1) GroupBy 를 활용하여 그룹 별 가중 평균 구하기


이제 (1) GroupBy 객체를 만들고, (2) 가중평균을 구하는 lambda 함수를 정의한 후에, (3) grouped.apply(function) 로 그룹별 가중평균을 구해보겠습니다. 


 

# group weighted average by category

grouped = df.groupby('grp_col')

weighted_avg_func = lambda g:np.average(g['val'], weights=g['weight'])

grouped.apply(weighted_avg_func)

grp_col
a    4.0
b    9.0
dtype: float64




 (2) 수작업으로 그룹 별 가중 평균 구하기 (Split -> Apply -> Combine) 


위에서 처럼 GroupBy 를 사용하지 않는 다면 아래에 소개한 것처럼 각 그룹별로 하나씩 Split -> Apply 하고 마지막에 Combine 을 해주는 단순 반복작업을 그룹의 개수만큼 해주어야 합니다. 


그룹의 개수가 적으면 할만 한데요, 그룹의 개수가 많아지면 수고롭기도 하고, 자칫 실수도 유발할 수 있으니 위의 (1)번 GroupBy 연산자를 사용하는 방법이 좀더 추천할만 하다고 하겠습니다. 비교를 위해서 소개합니다. 


  • (a) Split


# split

df_a = df[df['grp_col']=='a']

df_b = df[df['grp_col']=='b']

 


 df_a

grp_colvalweight
0a10.0
1a20.1
2a30.2
3a40.3
4a50.4

 df_b

grp_colvalweight
5b60.0
6b70.1
7b80.2
8b90.3
9b100.4



  • (b) Apply


# apply

weighted_avg_a = sum((df_a['val']*df_a['weight']))/sum(df_a['weight'])

weighted_avg_b = sum((df_b['val']*df_b['weight']))/sum(df_b['weight'])



 weighted_avg_a

4.0

 weighted_avg_b

9.0



  • (c) Combine


# combine

weighted_avg_ab = pd.DataFrame({'grp_col': ['a', 'b'], 

                               'weighted_average': [weighted_avg_a, weighted_avg_b]})


weighted_avg_ab

grp_colweighted_average
0a4.0
1b

9.0




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



Posted by R Friend R_Friend

지난번 포스팅에서는 Python pandas에서 Group By 집계 시에 grouped.agg()안에 칼럼과 함수를 매핑한 Dict를 사용하여 칼럼별로 특정 GroupBy 집계 함수를 적용하는 방법을 소개하였습니다. 


이번 포스팅에서는 Python pandas에서 Group By 집계 시에 grouped.apply()를 사용하여 여러개의 칼럼(Multiple Columns)에 대해서 각기 다른 함수들을 적용하는 방법을 소개하겠습니다. 지난번 포스팅과 비교를 해보시고, 편리하거나 이해하기 쉽다고 느껴지는 방법을 사용하면 되겠습니다. 


(1) 데이터프레임에서 여러개의 칼럼에 대해 다른 함수 적용하여 Group By 집계하기

    : grouped.apply(function)


(2) 계층적 인덱스를 가진 데이터프레임의 여러개의 칼럼에 대해 다른 함수 적용하여 Group By 집계하기

    : grouped(level=['index1', 'index2']).apply(function)






  (1) 데이터프레임에서 여러개의 칼럼에 대해 다른 함수 적용하여 Group By 집계하기

      : grouped.apply(function)



예제로 사용할 간단한 데이터프레임을 만들어보겠습니다. 



import numpy as np

import pandas as pd


df = pd.DataFrame({'grp_col_1' : ['a', 'a', 'a', 'a', 'a', 'b', 'b', 'b', 'b', 'b'], 

                   'grp_col_2' : ['c', 'c', 'd', 'd', 'd', 'e', 'e', 'f', 'f', 'f'], 

                   'val_1' : np.arange(10),                   

                   'val_2' : np.random.randn(10)})

 

df

grp_col_1grp_col_2val_1val_2
0ac00.829698
1ac1-0.766809
2ad2-2.119938
3ad30.885744
4ad4-1.135036
5be50.126890
6be62.755351
7bf70.441467
8bf8-1.166549
9bf9-0.712455





위에서 만든 데이터프레임의 'val_1' 칼럼에 대해서 평균(mean)과 표준편차(std)를 구하고, 'val_2' 칼럼에 대해서는 최대값(max)과 최소값(min), 그리고 범위(range)를 구하는 사용자 정의 함수 func()를 정의해보겠습니다. 처음에 빈 Dict 를 선언하고, 각 칼럼별 함수의 이름(key)과 함수를 적용한 결과(value)를 Dict의 key와 value로 매핑한 후에, 마지막에 pandas Series 로 반환하는 사용자 정의 함수입니다.



def func(x):

    d = {}

    d['val_1_mean'] = x['val_1'].mean()

    d['val_1_std'] = x['val_1'].std()

    d['val_2_max'] = x['val_2'].max()

    d['val_2_min'] = x['val_2'].min()

    d['val_2_range'] = x['val_2'].max() - x['val_2'].min()

    return pd.Series(d, index=['val_1_mean', 'val_1_std', 'val_2_max', 'val_2_min', 'val_2_range'])

 




위에서 정의한 사용자 정의 함수 func()를 Group By 집계 시 grouped.apply(func) 처럼 apply() 괄호 안에 함수이름을 써주면 됩니다. Group By 집계에 'grp_col_1', 'grp_col_2'의 두개 변수를 사용하였으므로 아래의 결과처럼 계층이 있는 인덱스(Hierarchical Index)를 가진 데이터프레임이 반환되었습니다. 



df_return = df.groupby(['grp_col_1', 'grp_col_2']).apply(func)

df_return


val_1_meanval_1_stdval_2_maxval_2_minval_2_range
grp_col_1grp_col_2
ac0.50.7071070.829698-0.7668091.596508
d3.01.0000000.885744-2.1199383.005682
be5.50.7071072.7553510.1268902.628461
f8.01.0000000.441467-1.1665491.608016

 




계층적 인덱스를 사용하고 싶지 않다면 reset_index() 로 인덱스를 칼럼으로 변환해주면 됩니다. 



df_return.reset_index()

grp_col_1grp_col_2val_1_meanval_1_stdval_2_maxval_2_minval_2_range
0ac0.50.7071070.829698-0.7668091.596508
1ad3.01.0000000.885744-2.1199383.005682
2be5.50.7071072.7553510.1268902.628461
3bf8.01.0000000.441467-1.1665491.608016

 




계층적 인덱스 전부 말고 일부만 선별해서 칼럼으로 변환을 하고 싶으면 reset_index(level='index_name') 처럼 reset_index() 의 안에 level 을 지정해주면 됩니다. 



df_return.reset_index(level='grp_col_2')

grp_col_2val_1_meanval_1_stdval_2_maxval_2_minval_2_range
grp_col_1
ac0.50.7071070.829698-0.7668091.596508
ad3.01.0000000.885744-2.1199383.005682
be5.50.7071072.7553510.1268902.628461
bf8.01.0000000.441467-1.1665491.608016

 




계층적 인덱스를 아예 삭제하고 싶으면 reset_index(drop=True) 처럼 drop=True 를 추가해주면 됩니다. (물론, 이렇게하면 Group By가 무엇을 기준으로 되었는지 파악이 불가능하기 때문에 사용하면 안될거 같긴 합니다. ^^;;;)



df_return.reset_index(drop=True)

val_1_meanval_1_stdval_2_maxval_2_minval_2_range
00.50.7071070.829698-0.7668091.596508
13.01.0000000.885744-2.1199383.005682
25.50.7071072.7553510.1268902.628461
38.01.0000000.441467-1.1665491.608016

 




Group By로 집계되어 반환된 객체가 데이터프레임으로 특정 칼럼만 선택할 수 있습니다. 예를 들어, 'val_1_mean', 'val_1_std'의 두 개 칼럼만 선택해보면 아래와 같습니다. 



df_return[['val_1_mean', 'val_1_std']]

val_1_meanval_1_std
grp_col_1grp_col_2
ac0.50.707107
d3.01.000000
be5.50.707107
f8.01.000000

 





  (2) 계층적 인덱스를 가진 데이터프레임의 여러개의 칼럼에 대해 다른 함수 적용하여 Group By 집계하기

      : grouped(level=['index1', 'index2']).apply(function)


위의 (1)번 예에서 들었던 데이터프레임과 'val_1', 'val_2' 의 숫자형 변수와 값은 동일하며, Group By 집계를 하는 기준이 되는 'grp_col_1', 'grp_col_2' 변수는 'grp_idx_1', 'grp_idx_2' 라는 이름의 인덱스 이름으로 하여 계층적 인덱스로 하여 데이터프레임을 만들어보겠습니다. 


pd.MultiIndex.from_arrays() 를 사용하여 계층적 인덱스를 만들어줍니다. (R이나 SQL을 사용하다가 Python 의 계층적 인덱스를 처음 보면 '이게 뭐지?' 싶었는데요, 자꾸 써보니 나름 편리합니다)


pd.DataFrame() 으로 데이터프레임을 만들 때 index 에  pd.MultiIndex.from_arrays() 로 만든 인덱스를 할당해주면 됩니다. 



# How to make a DataFrame with Hierarchical Index

arrays = [['a', 'a', 'a', 'a', 'a', 'b', 'b', 'b', 'b', 'b'], 

         ['c', 'c', 'd', 'd', 'd', 'e', 'e', 'f', 'f', 'f']]


myindex = pd.MultiIndex.from_arrays(arrays, names=('grp_idx_1', 'grp_idx_2'))

 


df2 = pd.DataFrame({'val_1': np.arange(10), 

                    'val_2': np.random.randn(10)}, 

                   index = myindex)


df2

val_1val_2
grp_idx_1grp_idx_2
ac0-0.491750
c10.507519
d20.099639
d30.669201
d40.714737
be5-0.865795
e6-1.174955
f70.878559
f8-0.877475
f9-0.323399

 




계층적 인덱스를 가진 데이터프레임에 대해 Group By 집계를 할 때는 groupby(level=['index_1', 'index_2']).apply(function) 의 형태로 지정을 해주면 됩니다. groupby() 로 집계 기준을 설정하고 apply(function)으로 함수를 지정해주는 것은 위의 (1)번과 같은데요, groupby()의 안에 level=['index_1', 'index_2'] 처럼 입력하는 부분이 다릅니다. 



df2.groupby(level=['grp_idx_1', 'grp_idx_2']).apply(func)

val_1_meanval_1_stdval_2_maxval_2_minval_2_range
grp_idx_1grp_idx_2
ac0.50.7071070.507519-0.4917500.999269
d3.01.0000000.7147370.0996390.615099
be5.50.707107-0.865795-1.1749550.309161
f8.01.0000000.878559-0.8774751.756035

 



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

Posted by R Friend R_Friend

지난번 포스팅에서는 Python pandas의 GroupBy 집계 메소드와 함수에 대해서 알아보았습니다. 


이번 포스팅에서는 Python pandas의 GroupBy 집계를 할 때 grouped.agg() 를 사용하여 다수의 함수를 적용하는 몇 가지 방법을 소개하고자 합니다. 


(1) 함수 리스트(List)를 사용하여 다수의 GroupBy 집계 함수를 동일한 칼럼에 적용하기

(2) 칼럼과 함수를 매핑한 Dict를 사용하여 칼럼별로 특정 GroupBy 집계 함수를 적용하기

(3) (이름, 함수)의 튜플 (Tuples of (name, function))을 사용하여 GroupBy 집계 함수에 이름 부여하기



[ Python pandas: GroupBy with multiple functions using lists, Dicts, tuples ]



예제로 사용할 데이터는 UCI Machine Learning Repository에 있는 Abalone data set 입니다. 전복의 둘레, 두께, 높이, 전체 무게, 껍질 무게 등 4,177개의 전복을 측정해 놓은 데이터셋입니다. 



[ UCI Machine Learning Repository ]

Name Data Type Meas. Description ---- --------- ----- ----------- Sex nominal M, F, and I (infant) Length continuous mm Longest shell measurement Diameter continuous mm perpendicular to length Height continuous mm with meat in shell Whole weight continuous grams whole abalone Shucked weight continuous grams weight of meat Viscera weight continuous grams gut weight (after bleeding) Shell weight continuous grams after being dried Rings integer +1.5 gives the age in years



UCI machine learning repository 웹사이트로부터 Abalone 데이터셋을 csv파일을 다운로드 받아서 pandas DataFrame로 불러오도록 하겠습니다. 



# Importing common libraries

import numpy as np

import pandas as pd

 

# Import Abalone data set from UCI machine learning repository directly

import csv

import urllib2

url = 'https://archive.ics.uci.edu/ml/machine-learning-databases/abalone/abalone.data'

downloaded_data  = urllib2.urlopen(url)

abalone = pd.read_csv(downloaded_data, 

                      names = ['sex', 'length', 'diameter', 'height', 

                               'whole_weight', 'shucked_weight', 'viscera_weight', 

                               'shell_weight', 'rings'], 

                      header = None)


abalone.head()

sexlengthdiameterheightwhole_weightshucked_weightviscera_weightshell_weightrings
0M0.4550.3650.0950.51400.22450.10100.15015
1M0.3500.2650.0900.22550.09950.04850.0707
2F0.5300.4200.1350.67700.25650.14150.2109
3M0.4400.3650.1250.51600.21550.11400.15510
4I0.3300.2550.0800.20500.08950.03950.0557




예제에서 GroupBy 집계 시 그룹을 나누는 기준으로 사용할 용도로 'length' 변수에 대해 중앙값을 기준으로 큰지, 작은지 여부에 따라 'length_cat' 라는 범주형 변수를 하나 더 만들어보겠습니다. 



abalone['length_cat'] = np.where(abalone.length > np.median(abalone.length), 

                                 'length_long', # True

                                 'length_short') # False


abalone[['length', 'length_cat']][:10]

lengthlength_cat
00.455length_short
10.350length_short
20.530length_short
30.440length_short
40.330length_short
50.425length_short
60.530length_short
70.545length_short
80.475length_short
90.550length_long

 





 (1) 함수 리스트(List)를 사용하여 다수의 GroupBy 집계 함수를 동일한 칼럼에 적용하기


'sex' ('F', 'I', 'M' 계급), 'length_cat' ('length_short', 'length_long' 계급) 의 두 개의 범주형 변수를 사용하여 GroupBy 집계 시 그룹을 나누는 기준으로 사용하겠으며, 'whole_weight' 연속형 변수에 대해 GroupBy 집계 함수를 적용하여 그룹 집계를 해보겠습니다.  



grouped_ww = abalone.groupby(['sex', 'length_cat'])['whole_weight']

grouped_ww

<pandas.core.groupby.SeriesGroupBy object at 0x10a7e0290> 





먼저, 복습을 하는 차원에서 지난번 포스팅에서 소개했던 '단일 함수'를 사용하여 GroupBy 집계하는 두가지 방법, 즉 (1) GroupBy method를 사용하거나 아니면 (2) grouped.agg(함수)를 사용하는 방법을 소개하면 아래와 같습니다. 하나의 집계함수를 적용하면 반환되는 결과는 Series 가 됩니다. 


(방법1) GroupBy methods

(방법2) grouped.agg(function)

 

grouped_ww.mean() # Series

sex  length_cat  
F    length_long     1.261330
     length_short    0.589702
I    length_long     0.923215
     length_short    0.351234
M    length_long     1.255182
     length_short    0.538157
Name: whole_weight, dtype: float64

 

grouped_ww.agg('mean') # Series

sex  length_cat  
F    length_long     1.261330
     length_short    0.589702
I    length_long     0.923215
     length_short    0.351234
M    length_long     1.255182
     length_short    0.538157
Name: whole_weight, dtype: float64




이제부터 '여러개의 함수'를 적용하여 GroupBy 집계하는 방법을 소개하겠습니다. 먼저, GroupBy 집계하려는 함수들의 문자열 리스트(list)로 grouped.agg() 에 적용하는 방법입니다.  이처럼 여러개의 집계함수를 적용하면 반환되는 결과는 DataFrame이 됩니다. 



grouped_ww.agg(['size', 'mean', 'std', 'min', 'max']) # DataFrame

sizemeanstdminmax
sexlength_cat
Flength_long8891.2613300.3296560.64052.6570
length_short4180.5897020.2024000.08001.3580
Ilength_long1880.9232150.2183340.55852.0495
length_short11540.3512340.2042370.00201.0835
Mlength_long9661.2551820.3546820.59902.8255
length_short5620.5381570.2464980.01551.2825




function_list = ['size', 'mean', 'std', 'min', 'max']

grouped_ww.agg(function_list)

sizemeanstdminmax
sexlength_cat
Flength_long8891.2613300.3296560.64052.6570
length_short4180.5897020.2024000.08001.3580
Ilength_long1880.9232150.2183340.55852.0495
length_short11540.3512340.2042370.00201.0835
Mlength_long9661.2551820.3546820.59902.8255
length_short5620.5381570.2464980.01551.2825

 




물론, "다수의 칼럼"에 대해서 여러개의 함수를 적용하는 것도 가능합니다. 아래의 예에서는 'whole_weight', 'shell_weight'의 두 개의 칼럼에 대해서 GroupBy 집계 함수 리스트(list)를 적용하여 집계하여 보았습니다. 



grouped = abalone.groupby(['sex', 'length_cat'])

function_list = ['size', 'mean', 'std']

groupby_result = grouped['whole_weight', 'shell_weight'].agg(function_list)

groupby_result

whole_weightshell_weight
sizemeanstdsizemeanstd
sexlength_cat
Flength_long8891.2613300.3296568890.3600130.104014
length_short4180.5897020.2024004180.1786500.063085
Ilength_long1880.9232150.2183341880.2732470.064607
length_short11540.3512340.20423711540.1045490.061003
Mlength_long9661.2551820.3546829660.3516830.102636
length_short5620.5381570.2464985620.1621410.075629




GroupBy 집계 결과가 pandas DataFrame으로 반환된다고 하였으므로, DataFrame에서 사용하는 Indexing 기법을 그대로 사용할 수 있습니다. 예를 들어, 칼럼을 기준으로 집계 결과 데이터프레임인 groupby_result 로 부터 'shell_weight' 변수에 대한 결과만 Indexing 해보겠습니다. 



groupby_result['shell_weight']

sizemeanstd
sexlength_cat
Flength_long8890.3600130.104014
length_short4180.1786500.063085
Ilength_long1880.2732470.064607
length_short11540.1045490.061003
Mlength_long9660.3516830.102636
length_short5620.1621410.075629

 


groupby_result['shell_weight'][['size', 'mean']]

sizemean
sexlength_cat
Flength_long8890.360013
length_short4180.178650
Ilength_long1880.273247
length_short11540.104549
Mlength_long9660.351683
length_short5620.162141





GroupBy 집계 결과 데이터프레임으로부터 row를 기준으로 Indexing을 할 수도 있습니다. DataFrame에서 row 기준으로 indexing 할 때 DataFrame.loc[] 를 사용하는 것과 동일합니다. 



groupby_result.loc['M']

whole_weightshell_weight
sizemeanstdsizemeanstd
length_cat
length_long9661.2551820.3546829660.3516830.102636
length_short5620.5381570.2464985620.1621410.075629

 


groupby_result.loc['M', 'shell_weight']

sizemeanstd
length_cat
length_long9660.3516830.102636
length_short5620.1621410.075629






 (2) 칼럼과 함수를 매핑한 Dict를 사용하여 칼럼별로 특정 GroupBy 집계 함수를 적용하기 


먼저, 범위(range)와 IQR(Inter-Quartile Range)를 구하는 사용자 정의 함수를 정의한 후에 grouped.agg() 에 적용해보겠습니다.  



def range_func(x):

    max_val = np.max(x)

    min_val = np.min(x)

    range_val = max_val - min_val

    return range_val


def iqr_func(x):

    q3, q1 = np.percentile(x, [75, 25])

    iqr = q3 - q1

    return iqr

 




이제 Dicts를 사용하여 'whole_weight' 칼럼에는 size(), mean(), std() 메소드를 매핑하여 GroupBy 집계에 적용하고, 'shell_weight' 칼럼에는 range_func, iqr_func 사용자 정의 함수를 매핑하여 GroupBy 집계에 적용해보겠습니다. 


size(), mean(), std() 등의 메소드는 문자열(string)로 grouped.agg() 안에 넣어주어야 해서 작은따옴표('method_name')로 감싸주었으며, 사용자 정의 함수(UDF)는 작은따옴표 없이 그냥 써주면 됩니다. 



grouped.agg({'whole_weight': ['size', 'mean', 'std'], # put method's name as a string

            'shell_weight': [range_func, iqr_func]}) # UDF name

whole_weightshell_weight
sizemeanstdrange_funciqr_func
sexlength_cat
Flength_long8891.2613300.3296560.8500.127000
length_short4180.5897020.2024000.3780.080500
Ilength_long1880.9232150.2183340.4850.067875
length_short11540.3512340.2042370.3490.092750
Mlength_long9661.2551820.3546820.7760.124000
length_short5620.5381570.2464980.3750.102750

 





 (3) (이름, 함수)의 튜플 (Tuples of (name, function))을 사용하여 GroupBy 집계 함수에 이름 부여하기


위의 (2)번에서 Dicts를 사용하여 shell_weight 변수에 대해 range_func, iqr_func 사용자 정의 함수를 적용하여 GroupBy 집계를 하였는데요, 집계 결과로 반환된 데이터프레임의 변수 이름이 그대로 'range_func', 'iqr_func' 여서 왠지 좀 마음에 들지 않군요.  이럴 때 (이름, 함수) 의 튜플 (Tuples of (name, function))을 사용하여 함수에 특정 이름을 부여할 수 있습니다. 


아래 예제에서는 알아보기에 쉽도록 'range_func'는 'Range'라는 이름으로, 'iqr_func'는 'Inter-Quartile_Range'라는 이름을 부여하여 변경을 해보겠습니다. 



# (name, function) tuples

grouped.agg({'whole_weight': ['size', 'mean', 'std'], 

            'shell_weight': [('Range', range_func),  # (name, function) tuple

                                    ('Inter-Quartile_Range', iqr_func)]}) # (name, function) tuple

whole_weightshell_weight
sizemeanstdRange

Inter-Quartile_Range

sexlength_cat
Flength_long8891.2613300.3296560.8500.127000
length_short4180.5897020.2024000.3780.080500
Ilength_long1880.9232150.2183340.4850.067875
length_short11540.3512340.2042370.3490.092750
Mlength_long9661.2551820.3546820.7760.124000
length_short5620.5381570.2464980.3750.102750



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

Posted by R Friend R_Friend