데이터 분석/Pandas

10 minutes to pandas - Group by (split - apply - combine)

haloaround 2022. 3. 29. 23:00

Group By 개요


Splitting the data into groups based on some criteria

Applying a function to each group independently 

- Aggregation: 각 그룹에 대하여 요약통계를 처리할 수 있다.
- Transformation: 그룹 특화 연산을 수행하고, 전체 데이터에 대해 그 값을 적용할 수 있다. 
- Filtration: 그룹 단위 연산으로 T/F 를 평가한다. True 인 대상을 추출할 수 있다. 

Combining the results into a data structure.

split, apply, combine

 

 

Splitting an object into groups


Groupby 를 독립적으로 이해하는 것이 중요하다.

 

주요기능

# 컬럼명의 값 기준으로 그룹핑하기 
grouped = df.groupby("A")
grouped = df.groupby(["A", "B"])
grouped = df.groupby("first")
grouped = df.groupby(["first", "second"])
grouped = df.groupby(["first", "A"])

# 인덱스 명/위치를 통해 인덱스의 값을 기준으로 그룹핑하기
grouped = df.groupby(level=0)
grouped = df.groupby(level=["first", "second"])

# 컬럼 및 인덱스의 값 기준으로 그룹핑하기 
grouped = df.groupby([pd.Grouper(level=0), "A"])
grouped = df.groupby([pd.Grouper(freq="1M", key="Date"), "Buyer"]).sum()

# Date 컬럼으로 그룹핑할 경우, index 로 옮긴후 그룹핑하기 
grouped = df.groupby([df.index.year, df.index.month])

 

주의. 만약 그룹의 기준에 NaN 또는 NaT 가 있다면, 자동적으로 배제된다. 즉 NA 그룹이나 NaT 그룹은 없다!
만약 NA 그룹이나 NaT 그룹이 필요하면 dropna = False 를 사용하자!

참고. pd.Grouper 는 특히 datetime-like object 에 대한 다양한 기능을 제공한다. 필요할 경우 더 공부해보자. 

 

부가기능

# group by 속성 
grouped = df.groupby("A", dropna=False)
grouped = df.groupby("A", as_index=False)


# Groupby Object 상세 확인
grouped.groups #그룹 이름과 행의 인덱스 배열 딕셔너리
grouped.ngroups # 몇 개의 그룹
grouped.get_group("bar") # 'bar 라는 이름을 가지는 그룹 

for name, group in grouped: #그룹 이름과 그룹의 DataFrame Chunk 
    print(name)
    print(group)

 

 

Applying


Aggregation

각 그룹에 대해 연산 (주로 통계치 계산) 을 수행할 수 있다. 스프레드시트의 Pivot Table 기능과 흡사하다!
주의. 그룹의 연산 대상에 NaN 값이 있을 경우 연산대상에서 제외된다.

grouped = df.groupby("A")

# 연산이 가능한 모든 컬럼에 대해서 다음 연산을 수행한다. 
grouped.sum()
grouped.mean()
grouped.size()
grouped.describe()
grouped.nunique()
grouped.nlargest(3)

# 연산이 가능한 모든 컬럼에 대해서 연산을 1개 또는 N개 수행한다.
grouped.aggregate(np.sum)
grouped.aggregate([np.sum, np.mean, np.std])

# 사용자정의 함수를 수행한다. 
grouped.agg(lambda x: x.max() - x.min())
grouped.agg([lambda x: x.max() - x.min(), lambda x: x.median() - x.mean()])

# df 의 특정 컬럼에 대해서만 연산을 수애한다. 
grouped["C"].agg([np.sum, np.mean, np.std])
grouped[["C", "D"]].agg([np.sum, np.mean, np.std])

# df 의 컬럼마다 다른 연산을 수행한다. 
grouped.agg({"C": np.sum, "D": lambda x: np.std()})

 

 

Transformation

전체 데이터에 그룹 단위의 연산값을 활용하여 특정 값을 변환할 수 있다. 

# z score : 그룹의 평균과 표준편차 값을 가지고 z score 를 구한다. 
index = pd.date_range("10/1/1999", periods=1100)
ts = pd.Series(np.random.normal(0.5, 2, 1100), index)
grouped = ts.groupby(lambda x: x.year)
grouped.transform(lambda x: (x - x.mean()) / x.std())

# fillna : NA 를 각 그룹의 평균값으로 채운다. 
grouped = df.groupby('A')
grouped.transform(lambda x: x.fillna(x.mean()))
grouped.ffill()

 

 

Filteration

특정 조건을 만족하는 subset 만 추출할 수 있다. 

# 그룹에 속한 값의 합이 4 초과인 그룹만 남긴다. 
sf = pd.Series([1, 2, 2, 3, 3, 3, 4, 4, 4, 4])
sf.groupby(sf).filter(lambda x : x.sum() > 4)

# 그룹 크기가 2 초과인 그룹만 남긴다.
dff = pd.DataFrame({"A": np.arange(8), "B": list("aabbbbcc")})
dff.groupby('B').filter(lambda x : len(x) > 2)

 

응용


Flexible Apply

하나의 컬럼에 대해 값을 다양하게 변환하고 싶은 경우 다음과 같이 적용할 수 있다. 

# A 컬럼 값을 기준으로 그룹핑한다. 그 중 C 컬럼에 대해 관심이 있다.
grouped = df.groupby('A')['C']

# group 을 인자로 받고, 새로운 DataFrame 을 제공하는 함수이다. 
def f(group):
    return pd.DataFrame({'original': group,      # 원래값
                         'demeaned': group - group.mean()})   # 평균과의 거리(편차)
                         
                         
# GroupBy Object 에 대해 f 함수를 호출한다.  
grouped.apply(f)

'''
   original  demeaned
0 -0.575247 -0.215962
1  0.254161  0.123181
2 -1.143704 -0.784420
3  0.215897  0.084917
4  1.193555  1.552839
'''

 

Piping Function Calls

Functions that take GroupBy objects can be chained together using a pipe method to allow for a cleaner, more readable syntax. 

GroupBy Object 를 인자로 받아서 함수를 수행할 수 있다. 

n = 1000
df = pd.DataFrame(
    {
        "Store": np.random.choice(["Store_1", "Store_2"], n),
        "Product": np.random.choice(["Product_1", "Product_2"], n),
        "Revenue": (np.random.random(n) * 50 + 10).round(2),
        "Quantity": np.random.randint(1, 10, size=n),
    }
)
'''
     Store    Product  Revenue  Quantity
0  Store_2  Product_1    26.12         1
1  Store_2  Product_1    28.86         1
'''

# 여러 컬럼의 값에 대한 연산을 활용하여 결과를 제공한다. 
df.groupby(["Store", "Product"]).pipe(
    lambda grp: grp.Revenue.sum() / grp.Quantity.sum()
    ).unstack().round(2)
    
'''
Product  Product_1  Product_2
Store                        
Store_1       6.82       7.05
Store_2       6.30       6.64
'''

# GroupBy Object 에 대해 사용자 정의 함수를 처리할 수 있다. 
def mean(groupby):
    return groupby.mean()
    
df.groupby(["Store", "Product"]).pipe(mean)

'''
                     Revenue  Quantity
Store   Product                       
Store_1 Product_1  34.622727  5.075758
        Product_2  35.482815  5.029630
Store_2 Product_1  32.972837  5.237589
        Product_2  34.684360  5.224000
'''