import pandas as pd
import numpy as npTidydata 심화 | groupby()
groupby()메소드의 심화
해당 포스트는 전북대학교 통계학과 최규빈 교수님의 강의내용을 토대로 재구성되었음을 알립니다.
1. 라이브러리 imports
2. groupby
A. df.groupby
df = pd.DataFrame({'department':['A','A','B','B'], 'gender':['male','female','male','female'],'count':[1,2,3,1]})
df| department | gender | count | |
|---|---|---|---|
| 0 | A | male | 1 |
| 1 | A | female | 2 |
| 2 | B | male | 3 |
| 3 | B | female | 1 |
g = df.groupby(by = 'department')
g<pandas.core.groupby.generic.DataFrameGroupBy object at 0x0000029243C3B250>
set(dir(g)) & {'__iter__'} # g는 반복문을 돌리기 유리하게 설계되어 있음{'__iter__'}
g에 적용할 수 있는 메소드(dir(g))중에
'__iter__'라는 게 있다. 즉, g는 for문을 돌리려고 만든 오브젝트이다.
[i for i in g] ## list(g)[('A',
department gender count
0 A male 1
1 A female 2),
('B',
department gender count
2 B male 3
3 B female 1)]
두 개의 튜플이 나온다. 튜플의 원소는 그룹화하는 열과 sub_dataframe과 같은 형식이다.
dct = {k:df for k, df in g} ## 딕셔너리 컴프리헨션
dct{'A': department gender count
0 A male 1
1 A female 2,
'B': department gender count
2 B male 3
3 B female 1}
display(dct['A'])
display(dct['B'])| department | gender | count | |
|---|---|---|---|
| 0 | A | male | 1 |
| 1 | A | female | 2 |
| department | gender | count | |
|---|---|---|---|
| 2 | B | male | 3 |
| 3 | B | female | 1 |
딕셔너리의 원소가 sub-dataframe
### B. g의 이용법
- g를 이용하여 원래의 df를 복원하라.
df = pd.DataFrame({'department':['A','A','B','B'], 'gender':['male','female','male','female'],'count':[1,2,3,1]})
g = df.groupby('department')pd.concat([df for _,df in g])| department | gender | count | |
|---|---|---|---|
| 0 | A | male | 1 |
| 1 | A | female | 2 |
| 2 | B | male | 3 |
| 3 | B | female | 1 |
단순히 묶어주기만 해도 이렇게 나온다.
- g를 이용하여 아래와 동일한 기능을 하는 코드를 작성하라. (agg함수 사용 금지)
df = pd.DataFrame({'department':['A','A','B','B'], 'gender':['male','female','male','female'],'count':[1,2,3,1]})
df.groupby('department').agg({'count':'sum'})| count | |
|---|---|
| department | |
| A | 3 |
| B | 4 |
list(g)[('A',
department gender count
0 A male 1
1 A female 2),
('B',
department gender count
2 B male 3
3 B female 1)]
pd.DataFrame(pd.Series({i:sum(j['count']) for i, j in g})) ## 리스트로 먼저 묶어줘야 함.
##pd.DataFrame(pd.Series({k:df['count'].sum() for k,df in g})) 이게 더 직관적| 0 | |
|---|---|
| A | 3 |
| B | 4 |
- 이 데이터프레임을 class를 기준으로 그룹핑하여 sub-dataframe을 만들고, score가 높은 순서로 정렬하는 코드를 작성하라.
df = pd.DataFrame({'class':['A']*5+['B']*5, 'id':[0,1,2,3,4]*2, 'score':[60,20,40,60,90,20,30,90,95,95]})
df| class | id | score | |
|---|---|---|---|
| 0 | A | 0 | 60 |
| 1 | A | 1 | 20 |
| 2 | A | 2 | 40 |
| 3 | A | 3 | 60 |
| 4 | A | 4 | 90 |
| 5 | B | 0 | 20 |
| 6 | B | 1 | 30 |
| 7 | B | 2 | 90 |
| 8 | B | 3 | 95 |
| 9 | B | 4 | 95 |
g = df.groupby('class')
pd.concat([df.sort_values('score', ascending = False) for i, df in g], axis = 0).reset_index(drop = True)| class | id | score | |
|---|---|---|---|
| 0 | A | 4 | 90 |
| 1 | A | 0 | 60 |
| 2 | A | 3 | 60 |
| 3 | A | 2 | 40 |
| 4 | A | 1 | 20 |
| 5 | B | 3 | 95 |
| 6 | B | 4 | 95 |
| 7 | B | 2 | 90 |
| 8 | B | 1 | 30 |
| 9 | B | 0 | 20 |
3. merge
A. 가장 빈번하게 사용되는 상황
- 예시 : big 데이터프레임에 groupby+agg를 사용하여 small 데이터프레임이 생긴 경우
big = pd.DataFrame({'department':['A','A','B','B'], 'gender':['male','female','male','female'],'count':[1,2,3,1]})
small = pd.DataFrame({'department':['A','B'], 'count_sum':[3,4]})
## big.groupby('department').aggregate({'count':'sum'}).rename({'count' : 'count_sum'}, axis = 1)display("big",big)
display("small",small)'big'
| department | gender | count | |
|---|---|---|---|
| 0 | A | male | 1 |
| 1 | A | female | 2 |
| 2 | B | male | 3 |
| 3 | B | female | 1 |
'small'
| department | count_sum | |
|---|---|---|
| 0 | A | 3 |
| 1 | B | 4 |
department | gender | count –> big
department | count_sum –> small
department | gender | count | count_sum
이러한 작업을 하고 싶을 때, pd.merge()를 사용하면 된다.
pd.merge(big, small)
## big.merge(small)
## small.merge(big)| department | gender | count | count_sum | |
|---|---|---|---|---|
| 0 | A | male | 1 | 3 |
| 1 | A | female | 2 | 3 |
| 2 | B | male | 3 | 4 |
| 3 | B | female | 1 | 4 |
사실 아래가 정확한 코드이다.
pd.merge(big, small, on = 'department')| department | gender | count | count_sum | |
|---|---|---|---|---|
| 0 | A | male | 1 | 3 |
| 1 | A | female | 2 | 3 |
| 2 | B | male | 3 | 4 |
| 3 | B | female | 1 | 4 |
공통부분인 department에 따라 데이터프레임이 병합됨
- 두 데이터프레임은 IndexLabel에 대하여 서로 다른 정보를 각각 정리한 상황
- 두 데티어프레임에서 공통인 열(IndexLabel(을 찾고, 이것을 기준으로 데이터의 정보를 병합한다.
B. 여러가지 파라메터
# on
big = pd.DataFrame({'department':['A','A','B','B'], 'gender':['male','female','male','female'],'count':[1,2,3,1]})
small = big.groupby('department').agg({'count':'sum'}).reset_index()
display("big",big)
display("small",small)'big'
| department | gender | count | |
|---|---|---|---|
| 0 | A | male | 1 |
| 1 | A | female | 2 |
| 2 | B | male | 3 |
| 3 | B | female | 1 |
'small'
| department | count | |
|---|---|---|
| 0 | A | 3 |
| 1 | B | 4 |
- 잘못된 코드
pd.merge(big, small) ## department와 count가 겹친다. 이름은 겹치지만 count는 의미가 다름| department | gender | count |
|---|
연결고리로 이해
‘A’, 1
‘A’, 2
‘B’, 3
‘B’, 1
‘A’, 3
‘B’, 4
두 데이터프레임의 연결고리가 없다. 따라서 아무것도 산출할 수 없다…
- 제대로 쓴 코드
pd.merge(big, small, on = 'department')| department | gender | count_x | count_y | |
|---|---|---|---|---|
| 0 | A | male | 1 | 3 |
| 1 | A | female | 2 | 3 |
| 2 | B | male | 3 | 4 |
| 3 | B | female | 1 | 4 |
- 열의 이름을 살리면서…
pd.merge(big, small.rename({'count' : 'count_sum'}, axis = 1)) ## 공통부분을 없애줌| department | gender | count | count_sum | |
|---|---|---|---|---|
| 0 | A | male | 1 | 3 |
| 1 | A | female | 2 | 3 |
| 2 | B | male | 3 | 4 |
| 3 | B | female | 1 | 4 |
어차피 이름을 바꿔야 하니, 처음부터 양식에 맞게 해주는 게 더 좋을 수 있음…
사실 아래 둘은 같은 코드이다.
pd.merge(big,small,on='department')
pd.merge(big,small,left_on='department', right_on='department')| department | gender | count_x | count_y | |
|---|---|---|---|---|
| 0 | A | male | 1 | 3 |
| 1 | A | female | 2 | 3 |
| 2 | B | male | 3 | 4 |
| 3 | B | female | 1 | 4 |
# left_on, right_on
big = pd.DataFrame({'department':['A','A','B','B'], 'gender':['male','female','male','female'],'count':[1,2,3,1]})
small = pd.DataFrame({'dept':['A','B'], 'count':[3,4]})
display("big",big)
display("small",small)'big'
| department | gender | count | |
|---|---|---|---|
| 0 | A | male | 1 |
| 1 | A | female | 2 |
| 2 | B | male | 3 |
| 3 | B | female | 1 |
'small'
| dept | count | |
|---|---|---|
| 0 | A | 3 |
| 1 | B | 4 |
공통부분은
count이고, 오히려 다른 부분은 모두 공통되지 않은 상황
pd.merge(big, small)| department | gender | count | dept | |
|---|---|---|---|---|
| 0 | B | male | 3 | A |
count로 합치면서 지랄이 난다.
- department, dept를 기준으로 병합…
pd.merge(big, small, left_on = 'department', right_on = 'dept')
## 왼쪽 데이터프레임에선 department, 오른쪽 데이터프레임에선 dept를 기준으로 삼음...| department | gender | count_x | dept | count_y | |
|---|---|---|---|---|---|
| 0 | A | male | 1 | A | 3 |
| 1 | A | female | 2 | A | 3 |
| 2 | B | male | 3 | B | 4 |
| 3 | B | female | 1 | B | 4 |
- 더 직관적이고 편하게…
pd.merge(big, small.rename({'dept' : 'department', 'count' : 'count_sum'}, axis = 1))| department | gender | count | count_sum | |
|---|---|---|---|---|
| 0 | A | male | 1 | 3 |
| 1 | A | female | 2 | 3 |
| 2 | B | male | 3 | 4 |
| 3 | B | female | 1 | 4 |
강제로 한 열만 이름이 같도록 해서 공통부분을 지정해줬다.(훨씬 편하지요옹?)
# how
df1 = pd.DataFrame({
'dept':['통계','수학','과학','IAB'],
'count':[20,30,25,50]
})
df2 = pd.DataFrame({
'dept':['통계','수학','과학','신설학과'],
'desc':['통계학과는...','수학과는...','과학학과는...','이 학과는 내년에 신설될 예정이고...']
})
display("df1",df1)
display("df2",df2)'df1'
| dept | count | |
|---|---|---|
| 0 | 통계 | 20 |
| 1 | 수학 | 30 |
| 2 | 과학 | 25 |
| 3 | IAB | 50 |
'df2'
| dept | desc | |
|---|---|---|
| 0 | 통계 | 통계학과는... |
| 1 | 수학 | 수학과는... |
| 2 | 과학 | 과학학과는... |
| 3 | 신설학과 | 이 학과는 내년에 신설될 예정이고... |
공통의 열인 dept, 서로 다른 정보인 count와 desc
on, left_on, right_on을 사용할 필요가 없다.
pd.merge(df1, df2) ## IAB, 신설학과가 미아됨| dept | count | desc | |
|---|---|---|---|
| 0 | 통계 | 20 | 통계학과는... |
| 1 | 수학 | 30 | 수학과는... |
| 2 | 과학 | 25 | 과학학과는... |
근데 겹치지 않는 것이 없어졌음…
겹치지 않는 자료를 처리하는 방식은 4가지 경우로 나누어진다.
#pd.merge(df1, df2, how = 'inner') ## 보수적으로, 자료가 없으면 없앰, default
#pd.merge(df1, df2, how = 'left') ## 왼쪽 거 기준으로(첫 번째)
#pd.merge(df1, df2, how = 'right') ## 오른쪽 거 기준으로(두 번째)
pd.merge(df1, df2, how = 'outer') ## 개방적으로, 자료를 전부 보전| dept | count | desc | |
|---|---|---|---|
| 0 | 통계 | 20.0 | 통계학과는... |
| 1 | 수학 | 30.0 | 수학과는... |
| 2 | 과학 | 25.0 | 과학학과는... |
| 3 | IAB | 50.0 | NaN |
| 4 | 신설학과 | NaN | 이 학과는 내년에 신설될 예정이고... |
4. concat, merge를 이용한 데이터 병합
df_course2023 = pd.DataFrame({
'name':['최규빈']*3+['최혜미']*2+['이영미']+['양성준'],
'year':[2023]*7,
'course':['파이썬프로그래밍', '데이터시각화', '기계학습활용','수리통계1', '수리통계2','회귀분석1','통계수학']})
df_course2023| name | year | course | |
|---|---|---|---|
| 0 | 최규빈 | 2023 | 파이썬프로그래밍 |
| 1 | 최규빈 | 2023 | 데이터시각화 |
| 2 | 최규빈 | 2023 | 기계학습활용 |
| 3 | 최혜미 | 2023 | 수리통계1 |
| 4 | 최혜미 | 2023 | 수리통계2 |
| 5 | 이영미 | 2023 | 회귀분석1 |
| 6 | 양성준 | 2023 | 통계수학 |
df_course2024 = pd.DataFrame({
'name':['최규빈','이영미','이영미','양성준','최혜미'],
'year':[2024]*5,
'course':['기계학습활용','수리통계1', '수리통계2','회귀분석1','통계수학']})
df_course2024| name | year | course | |
|---|---|---|---|
| 0 | 최규빈 | 2024 | 기계학습활용 |
| 1 | 이영미 | 2024 | 수리통계1 |
| 2 | 이영미 | 2024 | 수리통계2 |
| 3 | 양성준 | 2024 | 회귀분석1 |
| 4 | 최혜미 | 2024 | 통계수학 |
df_score = pd.DataFrame({
'name':['최규빈','최규빈','이영미','이영미','양성준','양성준','최혜미','최혜미'],
'year':[2023,2024]*4,
'score':[1, 1.2, 5,5,5,5,5,5]})
df_score| name | year | score | |
|---|---|---|---|
| 0 | 최규빈 | 2023 | 1.0 |
| 1 | 최규빈 | 2024 | 1.2 |
| 2 | 이영미 | 2023 | 5.0 |
| 3 | 이영미 | 2024 | 5.0 |
| 4 | 양성준 | 2023 | 5.0 |
| 5 | 양성준 | 2024 | 5.0 |
| 6 | 최혜미 | 2023 | 5.0 |
| 7 | 최혜미 | 2024 | 5.0 |
df_sex = pd.DataFrame({'name':['최규빈','이영미','양성준','최혜미'],
'sex':['male','female','male','female']})
df_sex| name | sex | |
|---|---|---|
| 0 | 최규빈 | male |
| 1 | 이영미 | female |
| 2 | 양성준 | male |
| 3 | 최혜미 | female |
주어진 정보를 바탕으로, 4개의 데이터프레임을 결합하라.
- 풀이
df_course2023 ## 7개 강의목록
df_course2024 ## 5개 강의목록
df_score ## 점수
df_sex ## 교수님 성별
위 두개는 열이 똑같다. (concat)
아래 두 개는 다른 정보이다. (merge)
pd.concat([df_course2023, df_course2024], axis = 0).reset_index(drop = True)| name | year | course | |
|---|---|---|---|
| 0 | 최규빈 | 2023 | 파이썬프로그래밍 |
| 1 | 최규빈 | 2023 | 데이터시각화 |
| 2 | 최규빈 | 2023 | 기계학습활용 |
| 3 | 최혜미 | 2023 | 수리통계1 |
| 4 | 최혜미 | 2023 | 수리통계2 |
| 5 | 이영미 | 2023 | 회귀분석1 |
| 6 | 양성준 | 2023 | 통계수학 |
| 7 | 최규빈 | 2024 | 기계학습활용 |
| 8 | 이영미 | 2024 | 수리통계1 |
| 9 | 이영미 | 2024 | 수리통계2 |
| 10 | 양성준 | 2024 | 회귀분석1 |
| 11 | 최혜미 | 2024 | 통계수학 |
단순히 두 개의 데이터프레임을 합쳤다.
pd.concat([df_course2023, df_course2024], axis = 0).reset_index(drop = True).merge(df_score)| name | year | course | score | |
|---|---|---|---|---|
| 0 | 최규빈 | 2023 | 파이썬프로그래밍 | 1.0 |
| 1 | 최규빈 | 2023 | 데이터시각화 | 1.0 |
| 2 | 최규빈 | 2023 | 기계학습활용 | 1.0 |
| 3 | 최혜미 | 2023 | 수리통계1 | 5.0 |
| 4 | 최혜미 | 2023 | 수리통계2 | 5.0 |
| 5 | 이영미 | 2023 | 회귀분석1 | 5.0 |
| 6 | 양성준 | 2023 | 통계수학 | 5.0 |
| 7 | 최규빈 | 2024 | 기계학습활용 | 1.2 |
| 8 | 이영미 | 2024 | 수리통계1 | 5.0 |
| 9 | 이영미 | 2024 | 수리통계2 | 5.0 |
| 10 | 양성준 | 2024 | 회귀분석1 | 5.0 |
| 11 | 최혜미 | 2024 | 통계수학 | 5.0 |
연도별로 점수를 넣어줬다. name과 year만 겹치므로 알아서 합쳐진다.
pd.concat([df_course2023, df_course2024], axis = 0).reset_index(drop = True).merge(df_score).merge(df_sex)| name | year | course | score | sex | |
|---|---|---|---|---|---|
| 0 | 최규빈 | 2023 | 파이썬프로그래밍 | 1.0 | male |
| 1 | 최규빈 | 2023 | 데이터시각화 | 1.0 | male |
| 2 | 최규빈 | 2023 | 기계학습활용 | 1.0 | male |
| 3 | 최규빈 | 2024 | 기계학습활용 | 1.2 | male |
| 4 | 최혜미 | 2023 | 수리통계1 | 5.0 | female |
| 5 | 최혜미 | 2023 | 수리통계2 | 5.0 | female |
| 6 | 최혜미 | 2024 | 통계수학 | 5.0 | female |
| 7 | 이영미 | 2023 | 회귀분석1 | 5.0 | female |
| 8 | 이영미 | 2024 | 수리통계1 | 5.0 | female |
| 9 | 이영미 | 2024 | 수리통계2 | 5.0 | female |
| 10 | 양성준 | 2023 | 통계수학 | 5.0 | male |
| 11 | 양성준 | 2024 | 회귀분석1 | 5.0 | male |
name이 겹치므로 알아서 합쳐진다.