그룹 연산은 데이터를 집계하거나 변환하는 등의 작업을 한 번에 처리할 수 있는 기능¶
groupby 메서드로 평균값 구하기¶
In [1]:
import warnings
warnings.simplefilter(action="ignore", category = FutureWarning)
import pandas as pd
df = pd.read_csv('../../data/gapminder.tsv', sep='\t')
df
Out[1]:
country | continent | year | lifeExp | pop | gdpPercap | |
---|---|---|---|---|---|---|
0 | Afghanistan | Asia | 1952 | 28.801 | 8425333 | 779.445314 |
1 | Afghanistan | Asia | 1957 | 30.332 | 9240934 | 820.853030 |
2 | Afghanistan | Asia | 1962 | 31.997 | 10267083 | 853.100710 |
3 | Afghanistan | Asia | 1967 | 34.020 | 11537966 | 836.197138 |
4 | Afghanistan | Asia | 1972 | 36.088 | 13079460 | 739.981106 |
... | ... | ... | ... | ... | ... | ... |
1699 | Zimbabwe | Africa | 1987 | 62.351 | 9216418 | 706.157306 |
1700 | Zimbabwe | Africa | 1992 | 60.377 | 10704340 | 693.420786 |
1701 | Zimbabwe | Africa | 1997 | 46.809 | 11404948 | 792.449960 |
1702 | Zimbabwe | Africa | 2002 | 39.989 | 11926563 | 672.038623 |
1703 | Zimbabwe | Africa | 2007 | 43.487 | 12311143 | 469.709298 |
1704 rows × 6 columns
In [2]:
# groupby 메서드 사용 - year 열 기준, lifeExp 열의 평균값을 구함
avg_life_exp_by_year = df.groupby('year')['lifeExp'].mean()
avg_life_exp_by_year
Out[2]:
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 메서드¶
- groubpy 메서드 자체를 분해하는 것은 불가능하므로 비슷한 연산 수행
In [3]:
# groupby 메서드에 열 이름을 전달하면 '분할' 작업이 일어남
years = df['year'].unique()
years
Out[3]:
array([1952, 1957, 1962, 1967, 1972, 1977, 1982, 1987, 1992, 1997, 2002,
2007], dtype=int64)
In [4]:
# 연도별로 데이터 추출 : 1952년을 추출한 예. '반영'
y1952 = df.loc[df.year == 1952, :]
y1952.head()
Out[4]:
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 |
In [5]:
# 추출한 데이터의 평균을 구함 '반영-2'
y1952_mean = y1952.lifeExp.mean()
y1952.mean
Out[5]:
<bound method NDFrame._add_numeric_operations.<locals>.mean of 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
... ... ... ... ... ... ...
1644 Vietnam Asia 1952 40.412 26246839 605.066492
1656 West Bank and Gaza Asia 1952 43.160 1030585 1515.592329
1668 Yemen, Rep. Asia 1952 32.548 4963829 781.717576
1680 Zambia Africa 1952 42.038 2672000 1147.388831
1692 Zimbabwe Africa 1952 48.451 3080907 406.884115
[142 rows x 6 columns]>
In [6]:
# 연도별 평균값을 구함 '반영-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
In [7]:
# 연도별 평균값을 취합 -'결합'
df2 = pd.DataFrame({'year':[1952,1957,1962, 2007],
'':[y1952_mean,y1957_mean,y1962_mean,y2007_mean]})
df2
# line 2 = 3,4,5,6,7
Out[7]:
year | ||
---|---|---|
0 | 1952 | 49.057620 |
1 | 1957 | 51.507401 |
2 | 1962 | 53.609249 |
3 | 2007 | 67.007423 |
In [8]:
def my_mean(values):
n = len(values)
sum = 0
for value in values:
sum += value
return sum/n
In [9]:
my_mean([1,2,4,7])
Out[9]:
3.5
In [10]:
my_mean([1,2,4,7,88])
Out[10]:
20.4
In [11]:
# 사용자 정의 함수(my_mean)를 groupby 메서드와 조합하기 위해 agg 메서드를 사용
agg_my_mean = df.groupby('year')['lifeExp'].agg(my_mean)
agg_my_mean
Out[11]:
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 메서드¶
In [12]:
# 첫 번째 인자로 받은 열의 평균값을 구하여 두 번째 인자로 받은 값과의 차이를 계산하여 반환하는 함수 : my_mean_diff
def my_mean_diff(values, diff_value):
n = len(values)
sum = 0
for value in values:
sum += value
mean = sum / n # mean = [1,2,3,4] 합의 평균 -> 2.5
return mean - diff_value
In [13]:
my_mean_diff([1,2,3,4], 10)
Out[13]:
-7.5
In [14]:
global_mean = df['lifeExp'].mean()
global_mean
Out[14]:
59.474439366197174
In [15]:
agg_mean_diff = df.groupby('year')['lifeExp'].agg(my_mean_diff,
diff_value=global_mean)
agg_mean_diff
Out[15]:
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
In [16]:
total_agg_mean_diff = pd.DataFrame({
'year':years,
'연도별 평균 수명': agg_my_mean.values,
'전체 평균 수명':global_mean,
'결과':agg_mean_diff.values})
total_agg_mean_diff
Out[16]:
year | 연도별 평균 수명 | 전체 평균 수명 | 결과 | |
---|---|---|---|---|
0 | 1952 | 49.057620 | 59.474439 | -10.416820 |
1 | 1957 | 51.507401 | 59.474439 | -7.967038 |
2 | 1962 | 53.609249 | 59.474439 | -5.865190 |
3 | 1967 | 55.678290 | 59.474439 | -3.796150 |
4 | 1972 | 57.647386 | 59.474439 | -1.827053 |
5 | 1977 | 59.570157 | 59.474439 | 0.095718 |
6 | 1982 | 61.533197 | 59.474439 | 2.058758 |
7 | 1987 | 63.212613 | 59.474439 | 3.738173 |
8 | 1992 | 64.160338 | 59.474439 | 4.685899 |
9 | 1997 | 65.014676 | 59.474439 | 5.540237 |
10 | 2002 | 65.694923 | 59.474439 | 6.220483 |
11 | 2007 | 67.007423 | 59.474439 | 7.532983 |
집계 메서드를 리스트, 딕셔너리에 담아 전달¶
집계 메서드를 리스트, 딕셔너리에 담아 전달¶
- 모든 열에 여러 함수를 매핑 : group 객체.agg([함수1, 함수2, 함수3, ...])
- 각 열마다 다른 함수를 매핑 : group 객체.agg({'열1':함수1, '열2':함수2, ...})
- agg 함수는 집계가 목적이므로 데이터 타입이 숫자 타입인 행/열만 함수를 적용
집계 기능을 내장하고 있는 판다스 기본 함수에는¶
- mean(), max(), min(), sum(), count(), size(), var(), std(), describe(), info(), first(), last() 등이 있다.
- agg() 메소드 데이터 집계 : group 객체.agg(매핑함수)
In [17]:
# 집계 메서드를 리스트에 담아 전달하기
# 연도별로 그룹화한 llifeExp 열의 0이 아닌 값의 개수, 평균, 표준편차를 리스트에 담아 전달
import numpy as np
gdf = df.groupby('year')['lifeExp'].agg([np.count_nonzero, np.mean, np.std])
gdf.reset_index()
Out[17]:
year | count_nonzero | mean | std | |
---|---|---|---|---|
0 | 1952 | 142 | 49.057620 | 12.225956 |
1 | 1957 | 142 | 51.507401 | 12.231286 |
2 | 1962 | 142 | 53.609249 | 12.097245 |
3 | 1967 | 142 | 55.678290 | 11.718858 |
4 | 1972 | 142 | 57.647386 | 11.381953 |
5 | 1977 | 142 | 59.570157 | 11.227229 |
6 | 1982 | 142 | 61.533197 | 10.770618 |
7 | 1987 | 142 | 63.212613 | 10.556285 |
8 | 1992 | 142 | 64.160338 | 11.227380 |
9 | 1997 | 142 | 65.014676 | 11.559439 |
10 | 2002 | 142 | 65.694923 | 12.279823 |
11 | 2007 | 142 | 67.007423 | 12.073021 |
In [18]:
# 집계 메서드를 딕셔너리에 담아 agg 메서드에 전달 평균, 중간값(median)
gdf_dict = df.groupby('year').agg({
'lifeExp':'mean', 'pop':'median', 'gdpPercap':'median'
})
gdf_dict
Out[18]:
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 |
In [19]:
# 표준점수: 통계에서 데이터의 평균과 표준편차의 차이를 표준점수라고 함
def my_zscore(x):
return (x-x.mean()) / x.std()
In [20]:
# transform 데이터 변환 메서드 - 데이터와 메서드를 일대일로 대응시켜 계산
# 데이터의 양이 줄어들지 않음, 데이터 변환하는데 사용
transform_z = df.groupby('year')['lifeExp'].transform(my_zscore)
transform_z.head()
Out[20]:
0 -1.656854
1 -1.731249
2 -1.786543
3 -1.848157
4 -1.894173
Name: lifeExp, dtype: float64
In [21]:
df.shape
Out[21]:
(1704, 6)
In [22]:
# 데이터의 양이 줄어들지 않음을 확인(1704로 원본 데이터와 동일)
transform_z.shape
Out[22]:
(1704,)
누락값을 평균값으로 처리하기¶
In [23]:
import seaborn as sns
import numpy as np
In [24]:
# sample()을 활용한 무작위 샘플 데이터 만들기
# 무작위 표본 추출을 하는데 p..random.permutation() 함수를 사용하여 순열을
# 무작위로 뒤섞은 후에 n개 만큼 인덱싱 해오는 방법을 사용
# 'total_bill' 열의 값 4개를 임의로 선택해서 누락값으로 변경
np.random.seed(42)
tips_10 = sns.load_dataset('tips').sample(10)
tips_10.loc[np.random.permutation(tips_10.index)[:4], 'total_bill'] = np.NaN
tips_10
Out[24]:
total_bill | tip | sex | smoker | day | time | size | |
---|---|---|---|---|---|---|---|
24 | 19.82 | 3.18 | Male | No | Sat | Dinner | 2 |
6 | 8.77 | 2.00 | Male | No | Sun | Dinner | 2 |
153 | NaN | 2.00 | Male | No | Sun | Dinner | 4 |
211 | NaN | 5.16 | Male | Yes | Sat | Dinner | 4 |
198 | NaN | 2.00 | Female | Yes | Thur | Lunch | 2 |
176 | NaN | 2.00 | Male | Yes | Sun | Dinner | 2 |
192 | 28.44 | 2.56 | Male | Yes | Thur | Lunch | 2 |
124 | 12.48 | 2.52 | Female | No | Thur | Lunch | 2 |
9 | 14.78 | 3.23 | Male | No | Sun | Dinner | 2 |
101 | 15.38 | 3.00 | Female | Yes | Fri | Dinner | 2 |
In [25]:
# total_bill 누락값이 4개 이므로 6개만 나옴, 다른 값은 10개
# total_bill 누락값-여성1명, 남성3명
count_sex = tips_10.groupby('sex').count()
count_sex
Out[25]:
total_bill | tip | smoker | day | time | size | |
---|---|---|---|---|---|---|
sex | ||||||
Male | 4 | 7 | 7 | 7 | 7 | 7 |
Female | 2 | 3 | 3 | 3 | 3 | 3 |
In [26]:
def fill_na_mean(x):
avg = x.mean()
return x.fillna(avg)
In [27]:
# 성별을 구분하여 total_bill 열의 평균을 구하는 함수를 적용
# tips_10에 새로운 열 ('fill_total_bill')
# 남성과 여성의 누락값을 고려하여 계산한 평균값으로 채움
total_bill_group_mean = \
tips_10.groupby('sex')['total_bill'].transform(fill_na_mean)
tips_10['fill_total_bill'] = total_bill_group_mean
tips_10
Out[27]:
total_bill | tip | sex | smoker | day | time | size | fill_total_bill | |
---|---|---|---|---|---|---|---|---|
24 | 19.82 | 3.18 | Male | No | Sat | Dinner | 2 | 19.8200 |
6 | 8.77 | 2.00 | Male | No | Sun | Dinner | 2 | 8.7700 |
153 | NaN | 2.00 | Male | No | Sun | Dinner | 4 | 17.9525 |
211 | NaN | 5.16 | Male | Yes | Sat | Dinner | 4 | 17.9525 |
198 | NaN | 2.00 | Female | Yes | Thur | Lunch | 2 | 13.9300 |
176 | NaN | 2.00 | Male | Yes | Sun | Dinner | 2 | 17.9525 |
192 | 28.44 | 2.56 | Male | Yes | Thur | Lunch | 2 | 28.4400 |
124 | 12.48 | 2.52 | Female | No | Thur | Lunch | 2 | 12.4800 |
9 | 14.78 | 3.23 | Male | No | Sun | Dinner | 2 | 14.7800 |
101 | 15.38 | 3.00 | Female | Yes | Fri | Dinner | 2 | 15.3800 |
데이터 필터링 사용하기 - filter 메서드¶
- 그룹 객체 필터링 : group 객체.filter(조건식 함수)
In [28]:
tips = sns.load_dataset('tips')
tips.shape
Out[28]:
(244, 7)
In [29]:
# size 열의 데이터 수를 확인
tips['size'].value_counts()
Out[29]:
2 156
3 38
4 37
5 5
1 4
6 4
Name: size, dtype: int64
In [30]:
# # size 222: 1 , 216: 5
# tips.tail(30)
In [31]:
# 주문이 30번 이상 있는 테이블만 필터링
tips_filtered = tips.\
groupby('size').filter(lambda x: x['size'].count() >=30)
In [32]:
tips_filtered.shape
Out[32]:
(231, 7)
In [33]:
# 222-1, 216-5 주문이 적은 1, 5번 테이블을 제외하고 출력
tips_filtered.tail(30)
Out[33]:
total_bill | tip | sex | smoker | day | time | size | |
---|---|---|---|---|---|---|---|
212 | 48.33 | 9.00 | Male | No | Sat | Dinner | 4 |
213 | 13.27 | 2.50 | Female | Yes | Sat | Dinner | 2 |
214 | 28.17 | 6.50 | Female | Yes | Sat | Dinner | 3 |
215 | 12.90 | 1.10 | Female | Yes | Sat | Dinner | 2 |
217 | 11.59 | 1.50 | Male | Yes | Sat | Dinner | 2 |
218 | 7.74 | 1.44 | Male | Yes | Sat | Dinner | 2 |
219 | 30.14 | 3.09 | Female | Yes | Sat | Dinner | 4 |
220 | 12.16 | 2.20 | Male | Yes | Fri | Lunch | 2 |
221 | 13.42 | 3.48 | Female | Yes | Fri | Lunch | 2 |
223 | 15.98 | 3.00 | Female | No | Fri | Lunch | 3 |
224 | 13.42 | 1.58 | Male | Yes | Fri | Lunch | 2 |
225 | 16.27 | 2.50 | Female | Yes | Fri | Lunch | 2 |
226 | 10.09 | 2.00 | Female | Yes | Fri | Lunch | 2 |
227 | 20.45 | 3.00 | Male | No | Sat | Dinner | 4 |
228 | 13.28 | 2.72 | Male | No | Sat | Dinner | 2 |
229 | 22.12 | 2.88 | Female | Yes | Sat | Dinner | 2 |
230 | 24.01 | 2.00 | Male | Yes | Sat | Dinner | 4 |
231 | 15.69 | 3.00 | Male | Yes | Sat | Dinner | 3 |
232 | 11.61 | 3.39 | Male | No | Sat | Dinner | 2 |
233 | 10.77 | 1.47 | Male | No | Sat | Dinner | 2 |
234 | 15.53 | 3.00 | Male | Yes | Sat | Dinner | 2 |
235 | 10.07 | 1.25 | Male | No | Sat | Dinner | 2 |
236 | 12.60 | 1.00 | Male | Yes | Sat | Dinner | 2 |
237 | 32.83 | 1.17 | Male | Yes | Sat | Dinner | 2 |
238 | 35.83 | 4.67 | Female | No | Sat | Dinner | 3 |
239 | 29.03 | 5.92 | Male | No | Sat | Dinner | 3 |
240 | 27.18 | 2.00 | Female | Yes | Sat | Dinner | 2 |
241 | 22.67 | 2.00 | Male | Yes | Sat | Dinner | 2 |
242 | 17.82 | 1.75 | Male | No | Sat | Dinner | 2 |
243 | 18.78 | 3.00 | Female | No | Thur | Dinner | 2 |
In [34]:
tips_filtered['size'].value_counts()
Out[34]:
2 156
3 38
4 37
Name: size, dtype: int64
그룹 오브젝트 저장하여 살펴보기¶
In [35]:
tips_10 = sns.load_dataset('tips').sample(10, random_state=42)
tips_10
Out[35]:
total_bill | tip | sex | smoker | day | time | size | |
---|---|---|---|---|---|---|---|
24 | 19.82 | 3.18 | Male | No | Sat | Dinner | 2 |
6 | 8.77 | 2.00 | Male | No | Sun | Dinner | 2 |
153 | 24.55 | 2.00 | Male | No | Sun | Dinner | 4 |
211 | 25.89 | 5.16 | Male | Yes | Sat | Dinner | 4 |
198 | 13.00 | 2.00 | Female | Yes | Thur | Lunch | 2 |
176 | 17.89 | 2.00 | Male | Yes | Sun | Dinner | 2 |
192 | 28.44 | 2.56 | Male | Yes | Thur | Lunch | 2 |
124 | 12.48 | 2.52 | Female | No | Thur | Lunch | 2 |
9 | 14.78 | 3.23 | Male | No | Sun | Dinner | 2 |
101 | 15.38 | 3.00 | Female | Yes | Fri | Dinner | 2 |
In [36]:
# 자료형이 그룹 오브젝트임을 확인
grouped = tips_10.groupby('sex')
grouped
Out[36]:
<pandas.core.groupby.generic.DataFrameGroupBy object at 0x00000225BF363070>
In [37]:
# 그룹 오브젝트에 포함된 그룹을 보려면 groups 속성을 출력
grouped.groups
Out[37]:
{'Male': [24, 6, 153, 211, 176, 192, 9], 'Female': [198, 124, 101]}
그룹 오브젝트 평균 구하기¶
In [38]:
tips_10.head(2)
Out[38]:
total_bill | tip | sex | smoker | day | time | size | |
---|---|---|---|---|---|---|---|
24 | 19.82 | 3.18 | Male | No | Sat | Dinner | 2 |
6 | 8.77 | 2.00 | Male | No | Sun | Dinner | 2 |
In [39]:
# sex, smoker, day, time 열은 그룹 연산에서 제외
# python은 그룹 연산에 적합한 열을 알아서 골라 줌
avgs = grouped.mean()
avgs
Out[39]:
total_bill | tip | size | |
---|---|---|---|
sex | |||
Male | 20.02 | 2.875714 | 2.571429 |
Female | 13.62 | 2.506667 | 2.000000 |
그룹 오브젝트에서 데이터 추출하고 반복하기¶
In [40]:
female = grouped.get_group('Female')
female
Out[40]:
total_bill | tip | sex | smoker | day | time | size | |
---|---|---|---|---|---|---|---|
198 | 13.00 | 2.00 | Female | Yes | Thur | Lunch | 2 |
124 | 12.48 | 2.52 | Female | No | Thur | Lunch | 2 |
101 | 15.38 | 3.00 | Female | Yes | Fri | Dinner | 2 |
그룹 오브젝트 계산하고 살펴보기¶
In [41]:
bill_sex_time = tips_10.groupby(['sex', 'time'])
group_avg = bill_sex_time.mean()
group_avg
Out[41]:
total_bill | tip | size | ||
---|---|---|---|---|
sex | time | |||
Male | Lunch | 28.440000 | 2.560000 | 2.000000 |
Dinner | 18.616667 | 2.928333 | 2.666667 | |
Female | Lunch | 12.740000 | 2.260000 | 2.000000 |
Dinner | 15.380000 | 3.000000 | 2.000000 |
In [42]:
group_method = tips_10.groupby(['sex', 'time']).mean().reset_index()
group_method
Out[42]:
sex | time | total_bill | tip | size | |
---|---|---|---|---|---|
0 | Male | Lunch | 28.440000 | 2.560000 | 2.000000 |
1 | Male | Dinner | 18.616667 | 2.928333 | 2.666667 |
2 | Female | Lunch | 12.740000 | 2.260000 | 2.000000 |
3 | Female | Dinner | 15.380000 | 3.000000 | 2.000000 |
In [43]:
# reset_index 메서드 대신 as_index=False 를 설정해도 같은 결과
group_param = tips_10.groupby(['sex', 'time'], as_index=False).mean()
group_param
Out[43]:
sex | time | total_bill | tip | size | |
---|---|---|---|---|---|
0 | Male | Lunch | 28.440000 | 2.560000 | 2.000000 |
1 | Male | Dinner | 18.616667 | 2.928333 | 2.666667 |
2 | Female | Lunch | 12.740000 | 2.260000 | 2.000000 |
3 | Female | Dinner | 15.380000 | 3.000000 | 2.000000 |
반응형
'데이터분석' 카테고리의 다른 글
[23.06.19] Python time series - 12(3) (0) | 2023.06.19 |
---|---|
[23.06.19] Python pandas titanic problem - 12(2) (0) | 2023.06.19 |
[23.06.16] Python apply, 문제 - 11(4) (0) | 2023.06.16 |
[23.06.16] Python pivot_table - 11(2) (0) | 2023.06.16 |
[23.06.16] Python melt - 11(1) (0) | 2023.06.16 |