ㅅㅇ

Pandas _ 04 groupby 관련메소드 및 일괄처리 메소드 본문

AI_STUDY/Pandas

Pandas _ 04 groupby 관련메소드 및 일괄처리 메소드

SO__OS 2022. 6. 10. 19:54
플레이데이터 빅데이터캠프 공부 내용 _ 6/10

Pandas_04 groupby 관련메소드 및 일괄처리 메소드

그룹으로 묶인 애들을 집계말고 다른 처리가 가능하다.

1.  filter()

DataFrameGroupBy.filter(func, dropna=True, *args, **kwargs)

 

- 처리 대상 : 그룹바이한 데이터프레임. 

- 목적 : 특정 집계 조건을 만족하는 Group의 행들만 조회한다.

             이때, 집계결과가 아닌 조건에 맞는 데이터 자체를 보고 싶은 것.

 

- 방법 (집계처리 비교 함수 만들기 -> filter문 작성)

   1. 함수가 호출되면 'DataFrameGroupBy의 group로 한 group씩 DataFrame을 함수의 매개변수로 전달'한다.
    2. 함수는 받은 DataFrame을 이용해 집계하여, 집계한 값의 조건을 비교해서 반환한다.(반환타입: Bool) 
    3. 반환값이 True인 Group들의 모든 행들로 구성된 DataFrame을 반환한다. -> filter

 

- 매개변수
    - func: filtering 조건을 구현한 함수

        - 사용자 정의 함수 객체
        - 첫번째 매개변수로 "Group으로 묶인 DataFrame"을 받는다.


    - dropna=True
          - 필터를 통과하지 못한 group의 DataFrame의 값들을 drop시킨다. 

          -  False로 설정하면, 조건에 만족하지 않는 행들도 NA 처리해서 반환한다.


    - \*args, \*\*kwargs: filter 함수의 매개변수에 전달할 전달인자값.
    
** cf) df.filter (items , regex ..) : 데이터프레임에 대한. // 이름만 같음. 클래스 다름    

 

 

(1) filter 기본 예제 

    ex ) 과일중 cnt1의 평균이 20 이상인 과일들만 보기
           집계결과가 아니라 조건을 만족하는 행들을 다 출력하고 싶다.

 

** 아래 코드는 집계 결과. 이걸 보고 싶은 게 아님.

# 집계결과. 평균이 20이상인 과일들의 평균값을 확인하는 것.
r = df.groupby('fruits').mean()
i = r[r['cnt1']>=20]
i

# df[(df['fruits'] =='귤' )|( df['fruits'] == '딸기')] # 지금 이걸 보고 싶다.

 

- 함수정의 ( filter에 사용할 함수 )


    매개변수  :  x  -  (group별로 나뉜)  dataframe을 받는다. (그룹 하나씩 받음. 그룹 4개면 네번 호출)
                            - 그룹으로 묶인 데이터프레임 행들 을 받는 것.
    반환값 : bool  -  특정 조건을 만족하는지 여부

      받아온 데이터프레임 행들에서 cnt1 컬럼의 평균 계산해서 20 부터 큰지 여부를 확인하고 그 bool 결과를 return

def check_cnt1_mean(x):
    return x['cnt1'].mean() >= 20

 

- 함수 호출 (filter 문 작성)

# 그룹별로 특정 조건을 만족하는 행들을 조회 해주는 함수  
df.groupby('fruits').filter(check_cnt1_mean)

 filter 안에 함수 넣으면 그룹별 하나씩 함수 매개변수로 넣어주는 것.

 - > 받아온 데이터프레임 행들에서

 - > cnt1 컬럼의 평균 계산해서 20 부터 큰지 여부를 확인하고

 - > 그 bool 결과를 return

 - > 그룹 별 bool 결과를 보고 filtering 한다.

 

- lambda 식 구현 

  현재 함수는 단순 리턴으로 되어 있다. 그리고 이때, 일회성. 재사용 안 할거면.

df.groupby('fruits').filter(lambda x : x['cnt1'].mean() >= 20)

 

- dropna = True, False

 defult : True

 False로 설정하면, 조건에 만족하지 않는 행들도 NA 처리해서 반환한다.

df.groupby('fruits').filter(check_cnt1_mean, dropna = False)

df.groupby('fruits').filter(lambda x : x['cnt1'].mean() >= 20, dropna = False)

 

 

(2) 매개변수 있는 filter 함수 예제

 

위에는 어떤 컬럼을 조건에 들어가는 정수값이 뭔지를 함수에서 적어주었다.
이를 매개변수로 받으면 어떨까

 

- 함수 정의

    [Parameter]
        - x : DateFrame - group 별 DataFrame
        - col_name : str - 평균을 계산할 컬럼명
        - mean_thresh : int - 컬럼의 평균값이 이 값 인지 비교할 기준값
    [return]
        - bool : 컬럼의 평균이 mean_thresh 이상인지 여부

 

def check_mean(x, col_name, mean_thresh):

    return x[col_name].mean() >= mean_thresh

 

- 함수 호출

  키워드 인자 쓰는 게 좋다.

df.groupby('fruits').filter(check_mean, col_name ='cnt1', mean_thresh = 20)
df.groupby('fruits').filter(check_mean, col_name = 'cnt2', mean_thresh = 200)

 

** 람다는 일회성. 재사용하여 인자 받을 거면 람다식 쓸 필요 없음.

 

 

 

 agg(fuct), filter(fuct), transfrom(fuct) 

- > dataframeGroupby의 메소드 (어떤 집계를 할 지를 넣는데 그 종류가 두 개)

     :  판다스 제공 집계함수 - 문자열,   사용자 정의 집계 함수 - 함수 객체

 ex) df.groupby().agg()      df.groupby().filter()     df.groupby().transform()

 filter 도 판다스 제공 집계함수로 가능한가? 사실상 불필요하긴 하지만

 

2.  transform()

함수에 의해 처리된 값(반환값)으로 원래 값들을 변경(tranform) 해서 반환    

- (값을 다 바꿈. 그룹별로 다 값 같게 됨.)

 

 DataFrameGroupBy.transform(func, *args)

SeriesGroupBy.transform(func, *args)


    - func : 매개변수로 그룹별로 Series를 받아  Series의 값들을 변환하여 (Series로)반환하는 함수객체
        - DataFrameGroupBy은 모든 컬럼의 값들을 group 별 컬럼 별Series로 전달한다.
    - *args : 함수에 전달할 추가 인자값이 있으면 매개변수 순서에 맞게 값을 전달한다. (위치기반 argument)

 

 

- df.groupby().transform() :  대상이 데이터프레임 즉, 값 대체가 전체의 컬럼에 적용도 가능하고

- df.groupby()["col1"].transform()  : 대상이 series 즉, 특정 컬럼만 값 대체 적용도 가능하다.

  둘 다 어쨋든

  함수가 호출되었을 때, 매개변수가 전달할 때는 group 별 colum 별 series 로 전달된다.

 

 

[주 사용]

  - transform() 함수를 groupby() 와 사용하면 

    컬럼의 각 원소들을 자신이 속한 그룹의 통계량으로 변환된 데이터셋을 생성할 수 있다.

     - > DataFrame에 Group 단위 통계량을 추가할 때 유용하다. (그룹별로 데이터 값, 통계 값들을 같이 비교하고 싶을 때)

        
  - 컬럼의 값과 통계값을 비교해서 보거나, 결측치 처리등에 사용할 수있다.

 

 

(1) 기본예제 :  판다스 제공 통계함수

     특정 컬럼이 아니라, 데이터프레임 모든 컬럼에 적용

df.groupby('fruits').transform("mean")

 

(2) 기본예제 : 사용자 정의 통계함수

     특정 컬럼이 아니라, 데이터프레임 모든 컬럼에 적용

 

     - 매개변수 : Series (그룹별로 컬럼별로 -> series 단위로 )
     -  반환 : 처리한 통계값

 

데이터프레임은 과일명을 기준으로 그룹화되었다. 그리고 transform 구문을 통해

그룹화된 데이터프레임에서 그룹별 그중에서도 컬럼별로 max_min_diff 매개변수에 담긴다.

전달된 series 는 집계 연산을 하고 return된다. 

이렇게 그룹별, 컬럼별 모든 항목들이 다 호출되고 

처리한 통계값들은 기존 데이터를 대신해 채운다.

def max_min_diff(x):
	return x.max()-x.min()
    
df.groupby("fruits").transform(max_min_diff)

# lambda식
df.groupby("fruits").transform(lambda x : x.max()-x.min())

 

(3) 응용 예제 : 원본에 통계치 붙여서 비교하기

    그냥 붙이면 가장 마지막 컬럼으로 붙는다.
    우리는 평균 낸 대상 컬럼의 옆에 붙이고 싶다.

 

ex.) 특정 컬럼 cnt1 의 과일 별 평균을   df에 행별로 붙이기

df.insert (  컬럼을 삽입할 위치 순번,  컬럼명,   넣을 컬럼 값

    => 원본을 변경, 삽입하고 옆 컬럼 오른쪽으로 한 칸 씩 밀림

cnt1_mean = df.groupby('fruits')['cnt1'].transform('mean')

df.insert(2, 'cnt1_mean', cnt1_mean)
df

 

(4)  sampling : 표본추출

- 행 중 랜덤하게 n 개를 추출하고 싶을 때 (알아서 섞임)

- frac = 샘플 비율(0~1 : 1= 100%, 0.1 = 10%)

df.sample(frac = 0.1)

# index명까지 섞인다. 
   fruits   cnt1   cnt2
13   배	    3	   7.0
11   배	    7	  9.0

# 섞인 index명을 자동증가 정수로 변환시켜주기.
df.sample(frac = 0.1).reset_index(drop = True)

 

(5) 결측치 처리

- 결측치 : 모르는 값, 없는 값. 
  모르는 값이 있으면 분석하기 힘들다. 이 결측치를 처리해줘야 함.

- 결측치 처리
    1. 제거 (행/열단위)   : df.dropna(axis = 0)

        - axis = 0 : 결측치가 있는 행(기본)을 제거

        - axis = 1 : 결측치가 있는 열을 제거

         - 행, 열 단위로 제거되면, 의미있는 데이터도 사라지게 된다.

             = >  데이터 양이 충분할 때만 제거. 

 

# 결측치가 있는 행(기본)로 제거
df.dropna()
 # 결측치가 있는 열 제거
df.dropna(axis  = 1)

 


    2. 대체  :  바꿀 열(series). fillna(대체값) 
      
        - 결측치를 표현하는 값으로 대체 (NAN 대신 모르는 값이라는 의미를 담고있는)


        - 가장 가능성이 높은 값을 대체 (평균, 중앙값, 최빈값)
         - transform이용해서 결측치를 같은 그룹의 평균값으로 변환
             = > 전체 평균보다 좀더 정확할 수 있다.

 

 

ex) cnt2 의 Na 결측치 값을 cnt2의 평균 값(반올림해서)으로 대체하라.

      결측치가 있는 컬럼에 대해서 처리. 하나의 컬럼이 대상이니 -> series 단위.

rep = round(df['cnt2'].mean(), 2)
df['cnt2'].fillna(rep, inplace = True) # 원본 바꿈.

 

- fillna() 함수에서 a와 동일한 size의 seires를 넣으면 결측치와 동일한 index명의 값으로 결측치를 채운다.

a = pd.Series([1,2,None, 3,None, 4])
b = pd.Series([100,200,300,400,500, 600])

a.fillna(b) 

-----------------
0      1.0
1      2.0
2    300.0
3      3.0
4    500.0
5      4.0
dtype: float64

 

 

- 대체를 할 때 전체 대표값 보다는 각 그룹 별 대표값 평균으로 대체 

df['cnt2'].fillna(df.groupby('fruits')['cnt2'].transform('mean'), inplace = True)
df

각 그룹별로 그룹의 평균 값들이 NaN을 대체할 값으로 넣어주고 싶다.

자신의 그룹에 맞춰, 그룹의 평균값이 들어가야 하므로 transform 을 이용한다.

  => NaN을 가진 데이터 값들은 자신의 그룹의 평균값으로 대체된다.

 

3. pivot_table()

엑셀의 pivot table 기능을 제공하는 메소드.    

 : 분류별 집계(Group으로 묶어 집계)를 처리하는 함수로 

    역할은 groupby() 를 이용한 집계와 같다.

 :  group으로 묶고자 하는 컬럼을  => 행과 열로 위치시키고 집계값을 값으로 보여준다.   

       - groupby() 의 경우 다중 그룹화를 할 경우, 해당 그룹을 모두 인덱스명으로 보여준다. 가독성 안 좋음.


** cf) pivot() 함수와 역할이 다르다.   
pivot() 은 index와 column의 형태를 바꾸는 reshape 함수.


[ 구문 ]

DataFrame.pivot_table(values=None, 
                        index=None, 
                        columns=None, 
                        aggfunc='mean', 
                        fill_value=None,
                        margins=False,
                        dropna=True,
                        margins_name='All')


- 매개변수

    - values
        - 문자열 또는 리스트(여러개면). 집계할 대상 컬럼들 


    - index
        - 문자열 또는 리스트(여러개면). index로 올 컬럼들 =>  groupby였으면 묶었을 컬럼
    - columns
        - 문자열 또는 리스트(여러개면). column으로 올 컬럼들 => groupby였으면 묶었을 컬럼 

     (index/columns가 묶여서 groupby에 묶을 컬럼들이 된다.)  
        
    - aggfunc
        - 집계함수 지정.

          함수이름 (문자열),  사용자 정의 함수(객체로)

           함수리스트 (여러개면 ),   dict: 집계할 함수 (컬럼마다 각각 다르면)
        - 기본(생략시): 평균을 구한다. (mean이 기본값)
        
    - fill_value, dropna
        - fill_value : 집계시 NA가 나올경우 채울 값
        - dropna : boolean(기본: True)    컬럼의 전체값이 NA인 경우 그 컬럼 제거
        
    - margins/margins_name
        - 그룹별 집계값 보고 마지막 행마다 총 집계값 보여줌.
        - margin : boolean(기본: False)     총집계결과를 만들지 여부.
        - margin_name  :  margin의 이름 문자열로 지정 (생략시 All)

 

(1) groupby와 pivot_table 차이

# groupby =>  multi index로 series 나옴.
flights.groupby(['AIRLINE', 'MONTH'])['AIR_TIME'].mean()

 

- 2차 그룹으로 집계하면  : multi index로 => 가독성이 안 좋다.
  이를 행과 열로 나눠 보자. -> 피벗테이블

flights.pivot_table(values = ['AIR_TIME'], # 집계 대상
                    index = 'AIRLINE', # Grouping 할 대상 컬럼 중 index 로 올 컬럼
                    columns = 'MONTH', # Grouping 할 대상 컬럼 중 columns 로 올 컬럼
                    aggfunc = "mean",   # 집계 함수
                    margins = True,
                    margins_name = "총통계치"
                   )

index = AIRLINE,    columns = MONTH,      data = AIR_TIME의 mean

 

 

(2) NaN 값 대체

- NAN 값은 그 group들의 값을 가진 행이 없는 경우에 나온다.

  즉, 첫번째 그룹과 두번째 그룹을 동시에 만족하는 데이터는 없을 때.
- 0 값은 합계를 구했더니 진짜 0인것. 집계할 값이 없는 게 아님.!

 

 - fill_value = "결측치"  

flights.pivot_table(values = ['AIR_TIME'], # 집계 대상
                    index = 'AIRLINE', # Grouping 할 대상 컬럼 중 index 로 올 컬럼
                    columns = 'MONTH', # Grouping 할 대상 컬럼 중 columns 로 올 컬럼
                    aggfunc = "mean",   # 집계 함수
                    fill_value = "결측치",
                    margins = True,
                    margins_name = "총통계치"
                   )

 

(3) 여러 집계값 / 집계대상컬럼 / index/ colums 의 컬럼을 여러 개 넣을 경우 리스트로 묶어서 전달.

 

flights.pivot_table(values= 'ARR_DELAY',
                    index = ['AIRLINE','ORG_AIR'],
                    columns = 'MONTH',
                    aggfunc = ['min','max'],
                    margins = True)

- min 에 대한 총 집계 , max에 대한 총 집계 따로 나옴.

 

 

4. apply() - Series, DataFrame의 데이터 일괄 처리

데이터프레임의 행들과 열들 또는 Series의 원소들 공통된 처리를 할 때

apply 함수를 이용하면 반복문을 사용하지 않고 일괄 처리가 가능하다.

 

- DataFrame.apply(함수, axis=0, args=())
    - 인수로 행이나 열을 받는 함수를 apply 메서드의 인수로 넣으면

      데이터프레임의 행이나 열들을 하나씩 함수에 전달한다.
    
    - 매개변수
        - 함수: DataFrame의 행들 또는 열들을 전달할 함수
        - axis : 0  -행을 전달, 1  -  열을 전달 (기본값 0)   
        - args: 행/열 이외에 전달할 매개변수를 위치기반(순서대로) 튜플로 전달
        
        
- Series.apply(함수, args=())
    - 인수로 Series의 원소들을 받는 함수를 apply 메소드의 인수로 넣으면

      Series의 원소들을 하나씩 함수로 전달한다.
    
    - 매개변수
        - 함수: Series의 원소들을 전달할 함수
        - args : 원소 이외에 전달할 매개변수를 위치기반(순서대로) 튜플로 전달

 

 

df의 원소 => series -> Seires(df의 한 행 또는 한 열 씩) 넘어가 일괄처리
series의 원소 => value -> 원소 values  별로 넘아가 일괄처리

 

 

1)  기본예제

[ 함수 정의 ]

 

# apply에 전달할 함수 정의
def func(x):

    return x**2

 

[ 함수 호출 ]

    매개변수 :  x 
    - datafram.apply() -> Seires(df의 한 행 또는 한 열) 가 전달
    - series.apply() -> Series 원소가 전달.

 

 

(1) DataFrame.apply()   -> Seires(df의 한 행 또는 한 열) 가 전달

df.apply(func) # 열단위로 전달
df.apply(func, axis =1) # axis = 1 : 행단위로 전달

1. 결과치 (열, 행 둘 다 동일한 결과)  2. 열단위로 전달    3. 행 단위로 전달

 

(2) Series.apply()  -> Series 원소가 전달.

 

df['no1'].apply(func)

결과값.

 

원소 별로 전달.

 

2) 추가 매개변수가 있는 경우

def func2(x, value):
    return x + value 

df.apply(func2, value = 100)

- axis 매개변수 때문에, 키워드 인자로 작성해주기

 

3) 람다식으로도 가능

df.apply(lambda x : x*2, axis = 1)  # 행단위

 

5.  cut()/qcut() - 연속형(수치)을 범주형으로 변환

binning 구간화하여 이를 범주형 변수로 사용한다.

 

- cut() : 나눌 지점을 지정하여, 그 기준으로 구간을 나눠 그룹으로 묶는다.
             규칙없이 내가 원하는 지점에서 구간을 나누고 싶을 때


    -  pd.cut(x, bins,right=True, labels=None)
    - 매개변수
        - x: 범주형으로 바꿀 대상. 1차원 배열형태(Series, 리스트, ndarray)의 자료구조 (cf. 당연히 df 안됨)
        - bins: 범주로 나눌때의 기준값(구간경계)들을 리스트로 묶어서 전달한다.
        - right: 구간경계의 오른쪽(True-기본)을 포함할지, 왼쪽(False)을 포함할지  (ex: True :  (10, 20], ()-포함안함, []-포함)
        - labels: 각 구간(범주)의 label을 리스트로 전달
            - 생략하면 범위를 범주명으로 사용한다.

- qcut() :  대상배열의 최대값 ~ 최소값을 지정한 개수의 동등한 size(원소의개수)가 되도록 나눈다.

    - n 등분을 지정하면 동일한 원소의 갯수로 N 개의 그룹으로 나눈다.
    - 구간의 원소의 갯수를 똑같게 나누고 싶을 때

    - pd.qcut(x, q, labels)
    - 매개변수
        - x: 나눌 대상. 1차원 배열형태의 자료구조
        - q: 나눌 개수
        - labels: 각 구간(범주)의 label을 리스트로 전달

** pd의 메소드들이다.

 

 

(1) cut() 

 

    # right = True (dafalut)

pd.cut(ages, bins= [-1,10,30,40,50]) 

# ages의 각 index의 값이 어느 그룹에 포함되는지를 반환

dtype: category
Categories (4, interval[int64, right]): [(-1, 10] < (10, 30] < (30, 40] < (40, 50]]

 

- right = T ,F   =>   괄호 와 대괄호

    (   ) => 포함 안 한다는 의미     [   ] => 포함 한다는 의미

 

right = T : ( 기본값 ) 오른쪽 큰쪽을 포함.    (오른쪽을 포함하니? )

                  [(-1, 10] < (10, 30] < (30, 40] < (40, 50]]

                  = > (10, 30]  : 10 미포함 30 포함.    10 <    <=30

                  =>  0 ~ 10,  11~30,  31 ~ 40,  41~50 

 

right = F : 왼쪽 작은쪽을 포함.

                [[-1, 10) < [10, 30) < [30, 40) < [40, 50)]
                 = >  [10,30)  : 30 포함. 40 미포함     10 <=   <30

                 = >  -1~9 ,  10~29,   30~39,   40~49  

 

(2) qcut() 

    n 등분을 지정하면 동일한 원소의 갯수로 N 개의 그룹으로 나눈다.

pd.qcut((ages), 2)

dtype: category
Categories (2, interval[float64, right]): [(0.999, 22.5] < (22.5, 48.0]]

 

(3) 라벨 붙이기  범주형 타입 데이터 만들기

- 구간 수와 라벨 수 맞게 적기.  오름차순 정렬대로 라벨이 붙는다.

-  age_cate 는 범주형 타입의 컬럼 sereis 로 DF 에 붙일 수 있다.

labels = ['19세 이하', '10,20대 ', '30대', '40대']
age_cate = pd.cut(ages, bins=bins, labels = labels)

# 데이터프레임으로 만들기
age_df = pd.DataFrame({'나이':ages, '나이대':age_cate})
age_df

 

(4) 구간화하여 범주값을 가지는 컬럼으로 DF 추가

      이거 하려고 배운 것

 

EX. price 컬럼을 '고가', '중가', '저가' 세개의 범주값을 가지는 "price_cate" 컬럼을 생성하라.

# 구간화하여 범주형 데이터 만들기
price_cate = pd.qcut(dia['price'], 3, labels=["저가", "중가", "고가"])

# DF에 7번째 컬럼 price_cate 라는 이름으로 삽입
dia.insert(7, "price_cate", price_cate)

 

'AI_STUDY > Pandas' 카테고리의 다른 글

pandas _ nlargest, nsmallest  (0) 2022.06.22
Pandas _ 06 DataFrame_재구조화  (0) 2022.06.15
데이터셋 read시 루틴  (0) 2022.06.10
Pandas _ 03-2 집계  (0) 2022.06.09
Pandas _ 03-1 정렬  (0) 2022.06.09