As your requirement has 2 parts:
- field goals %, and
- field goals in yard ranges,
let's solve it one by one.
Part 1: Field goals %
We can use df.groupby() together with .value_counts(normalize=True) to get it:
(df.groupby('kicker')['kick_result']
.value_counts(normalize=True).mul(100).round(2)
.sort_index()
.to_frame(name='Result_%')
).reset_index()
Test Run
Test Data Construction:
To have a complete testing of the various requirements, I have added test data as follows:
kick_result kick_yards kicker
49 MADE 18.0 X1
50 MADE 28.0 X1
51 MADE 38.0 X1
52 MISS 48.0 X1
53 MISS 58.0 X1
64 MADE 30.0 X2
75 MADE 27.0 X2
158 MADE 32.0 X2
159 MISS 32.0 X2
160 MISS 42.0 X2
259 MISS 46.0 X3
260 MISS 26.0 X3
261 MADE 56.0 X3
Run code:
(df.groupby('kicker')['kick_result']
.value_counts(normalize=True).mul(100).round(2)
.sort_index()
.to_frame(name='Result_%')
).reset_index()
Result:
kicker kick_result Result_%
0 X1 MADE 60.00
1 X1 MISS 40.00
2 X2 MADE 60.00
3 X2 MISS 40.00
4 X3 MADE 33.33
5 X3 MISS 66.67
Part 2: Field goals in yard ranges
We can use pd.crosstab() together with pd.cut() to build a table with the yard ranges.
Total attempts for all ranges are also included.
pd.crosstab(index=[df['kicker'], pd.cut(df['kick_yards'],[0, 20, 30, 40, 50, np.inf])],
columns=df['kick_result'],
margins=True, margins_name='Total_Attempts')
Result (using the enriched test data):
kick_result MADE MISS Total_Attempts
kicker kick_yards
X1 (0.0, 20.0] 1 0 1
(20.0, 30.0] 1 0 1
(30.0, 40.0] 1 0 1
(40.0, 50.0] 0 1 1
(50.0, inf] 0 1 1
X2 (20.0, 30.0] 2 0 2
(30.0, 40.0] 1 1 2
(40.0, 50.0] 0 1 1
X3 (20.0, 30.0] 0 1 1
(40.0, 50.0] 0 1 1
(50.0, inf] 1 0 1
Total_Attempts 7 6 13