Python Library/Pandas

[Pandas - Python] Apply : General split-apply-combine

바보1 2022. 6. 16. 01:22
tips[:6]

	total_bill	tip	smoker	day	time	size	tip_pct
0	16.99	1.01	No	Sun	Dinner	2	0.059447
1	10.34	1.66	No	Sun	Dinner	3	0.160542
2	21.01	3.50	No	Sun	Dinner	3	0.166587
3	23.68	3.31	No	Sun	Dinner	2	0.139780
4	24.59	3.61	No	Sun	Dinner	4	0.146808
5	25.29	4.71	No	Sun	Dinner	4	0.186240
def top(df, n=5, column='tip_pct'):
    return df.sort_values(by=column)[-n:]
top(tips, n=6)


    total_bill	tip	smoker	day	time	size	tip_pct
109	14.31	4.00	Yes	Sat	Dinner	2	0.279525
183	23.17	6.50	Yes	Sun	Dinner	4	0.280535
232	11.61	3.39	No	Sat	Dinner	2	0.291990
67	3.07	1.00	Yes	Sat	Dinner	1	0.325733
178	9.60	4.00	Yes	Sun	Dinner	2	0.416667
172	7.25	5.15	Yes	Sun	Dinner	2	0.710345

 

tip_pct 기준으로 정렬을 한 후, 가장 큰 6개의 열에 대해서 가져왔습니다.

 

이를 이용하여 apply 함수를 적용할 수 있습니다.

tips.groupby('smoker').apply(top)


		total_bill	tip	smoker	day	time	size	tip_pct
smoker								
No	88	24.71		5.85	No	Thur	Lunch	2	0.236746
        185	20.69		5.00	No	Sun	Dinner	5	0.241663
        51	10.29		2.60	No	Sun	Dinner	2	0.252672
        149	7.51		2.00	No	Thur	Lunch	2	0.266312
        232	11.61		3.39	No	Sat	Dinner	2	0.291990
Yes	109	14.31		4.00	Yes	Sat	Dinner	2	0.279525
        183	23.17		6.50	Yes	Sun	Dinner	4	0.280535
        67	3.07		1.00	Yes	Sat	Dinner	1	0.325733
        178	9.60		4.00	Yes	Sun	Dinner	2	0.416667
        172	7.25		5.15	Yes	Sun	Dinner	2	0.710345

우선 df를 smoker를 기준으로 그룹화 했고, 위의 top 함수를 적용했습니다.

이때 smoker 인덱스에 따라 각각 다섯 개씩 가져왔습니다.

 

tips.groupby(['smoker', 'day']).apply(top, n=1, column='total_bill')


			total_bill	tip	smoker	day	time	size	tip_pct
smoker	day								
No	Fri	94	22.75	3.25	No	Fri	Dinner		2	0.142857
        Sat	212	48.33	9.00	No	Sat	Dinner		4	0.186220
        Sun	156	48.17	5.00	No	Sun	Dinner		6	0.103799
        Thur	142	41.19	5.00	No	Thur	Lunch		5	0.121389
Yes	Fri	95	40.17	4.73	Yes	Fri	Dinner		4	0.117750
        Sat	170	50.81	10.00	Yes	Sat	Dinner		3	0.196812
        Sun	182	45.35	3.50	Yes	Sun	Dinner		3	0.077178
        Thur	197	43.11	5.00	Yes	Thur	Lunch		4	0.115982

이번에는 smoker, day를 기준으로 그룹화, top 함수에 파라미터를 넣어서 가져왔습니다.

이때 n = 1이라는 것은 smoker 인덱스 안의 day 인덱스에 해당하는 개수입니다.

smoker 인덱스를 하나씩 가져왔고, 그 안에 있는 day 인덱스를 각각 하나씩 가져왔습니다.

 

result = tips.groupby('smoker')['tip_pct'].describe()
result

	count	mean	std	min	25%	50%	75%	max
smoker								
No	151.0	0.159328	0.039910	0.056797	0.136906	0.155625	0.185014	0.291990
Yes	93.0	0.163196	0.085119	0.035638	0.106771	0.153846	0.195059	0.710345
result.unstack('smoker')


       smoker
count  No        151.000000
       Yes        93.000000
mean   No          0.159328
       Yes         0.163196
std    No          0.039910
       Yes         0.085119
min    No          0.056797
       Yes         0.035638
25%    No          0.136906
       Yes         0.106771
50%    No          0.155625
       Yes         0.153846
75%    No          0.185014
       Yes         0.195059
max    No          0.291990
       Yes         0.710345
dtype: float64

이때 result를 smoker를 기준으로 unstack한 결과입니다.


Suppressing the Group Keys

 

 

tips.groupby('smoker', group_keys=False).apply(top)



	total_bill	tip	smoker	day	time	size	tip_pct
88	24.71		5.85	No	Thur	Lunch	2	0.236746
185	20.69		5.00	No	Sun	Dinner	5	0.241663
51	10.29		2.60	No	Sun	Dinner	2	0.252672
149	7.51		2.00	No	Thur	Lunch	2	0.266312
232	11.61		3.39	No	Sat	Dinner	2	0.291990
109	14.31		4.00	Yes	Sat	Dinner	2	0.279525
183	23.17		6.50	Yes	Sun	Dinner	4	0.280535
67	3.07		1.00	Yes	Sat	Dinner	1	0.325733
178	9.60		4.00	Yes	Sun	Dinner	2	0.416667
172	7.25		5.15	Yes	Sun	Dinner	2	0.710345

smoker를 기준으로 그룹화 했지만, group key = False로 함으로써, 인덱스에 나타지 않게 되었습니다.

즉 원본 데이터를 유지하며, 그룹화했습니다.


Quantile and Bucket Analysis

 

 

frame = pd.DataFrame({'data1': np.random.randn(1000),
                      'data2': np.random.randn(1000)})
quartiles = pd.cut(frame.data1, 4)
quartiles[:10]



0     (-1.23, 0.489]
1    (-2.956, -1.23]
2     (-1.23, 0.489]
3     (0.489, 2.208]
4     (-1.23, 0.489]
5     (0.489, 2.208]
6     (-1.23, 0.489]
7     (-1.23, 0.489]
8     (0.489, 2.208]
9     (0.489, 2.208]
Name: data1, dtype: category
Categories (4, interval[float64, right]): [(-2.956, -1.23] < (-1.23, 0.489] < (0.489, 2.208] < (2.208, 3.928]]

데이터를 생성 후, 4개로 쪼갰습니다.

 

def get_stats(group):
    return {'min': group.min(), 'max': group.max(),
            'count': group.count(), 'mean': group.mean()}
grouped = frame.data2.groupby(quartiles)
grouped.apply(get_stats).unstack()


		min		max		count	mean
	data1				
(-2.956, -1.23]	-3.399312	1.670835	95.0	-0.039521
(-1.23, 0.489]	-2.989741	3.260383	598.0	-0.002051
(0.489, 2.208]	-3.745356	2.954439	297.0	0.081822
(2.208, 3.928]	-1.929776	1.765640	10.0	0.024750

grouped는 범위에 따라 나눠진 quartiles를 대상으로 다시 data2를 그룹화한 df입니다.

이후 get_stats 함수를 통해 적용한 결과입니다. 각각의 그룹에 함수를 적용하는 모습입니다.

만약 unstack()하지 않았다면, 계층적 인덱싱의 결과로 나왔을 것입니다.

 

또 다른 예시입니다.

이번엔 qcut을 한 모습입니다.

# Return quantile numbers
grouping = pd.qcut(frame.data1, 10, labels=False)
grouped = frame.data2.groupby(grouping)
grouped.apply(get_stats).unstack()



	min		max		count	mean
data1				
0	-3.399312	1.670835	100.0	-0.049902
1	-1.950098	2.628441	100.0	0.030989
2	-2.925113	2.527939	100.0	-0.067179
3	-2.315555	3.260383	100.0	0.065713
4	-2.047939	2.074345	100.0	-0.111653
5	-2.989741	2.184810	100.0	0.052130
6	-2.223506	2.458842	100.0	-0.021489
7	-3.056990	2.954439	100.0	-0.026459
8	-3.745356	2.735527	100.0	0.103406
9	-2.064111	2.377020	100.0	0.220122