Idea of solution is create helper column for number of sorted groups and filtering.
Solution working with same values of element_counts per each group by individual column, also there are always 5 values in lists.
First because sample data are in one column some preprocessing:
import ast
df = pd.read_excel('sample.xlsx')
c = df.columns[0].split(',')
df = df.iloc[:, 0].str.split(',', expand=True, n=3).applymap(lambda x: x.strip('"'))
df.columns = c
df['individual'] = df['individual'].astype(int)
c = ['cluster_choice_prob_k_fold','benchmark_probabilities']
df[c] = df[c].astype(float)
print (df.head())
individual cluster_choice_prob_k_fold benchmark_probabilities \
0 9710535 0.512776 0.163837
1 9710535 0.512776 0.098600
2 9710535 0.512776 0.085191
3 9710535 0.512776 0.067577
4 9710535 0.512776 0.065420
element_counts
0 [3, 2, 0, 0, 0]
1 [3, 2, 0, 0, 0]
2 [3, 2, 0, 0, 0]
3 [3, 2, 0, 0, 0]
4 [3, 2, 0, 0, 0]
Then sorting per both columns by DataFrame.sort_values and convert string repr of lists to lists of integers by ast.literal_eval:
df = df.sort_values(['cluster_choice_prob_k_fold','benchmark_probabilities'], ascending=False)
df['element_counts'] = df['element_counts'].apply(ast.literal_eval)
Then create counter of groups by GroupBy.transform and factorize:
df['g'] = df.groupby('individual', sort=False)['cluster_choice_prob_k_fold'].transform(lambda x: pd.factorize(x)[0]).astype(int)
Create new DataFrame with remove duplicates of individual by DataFrame.drop_duplicates, DataFrame.explode lists to new column and create column for groups with numpy.tile:
df1 = (df.drop_duplicates('individual')
.explode('element_counts')[['individual','element_counts']]
.assign(g = np.tile(np.arange(5), df['individual'].nunique())))
print (df1)
individual element_counts g
0 9710535 3 0
0 9710535 2 1
0 9710535 0 2
0 9710535 0 3
0 9710535 0 4
So is possible use DataFrame.merge with left join to original and remove non match groups with missing values by DataFrame.dropna:
df = (df.merge(df1, how='left', on=['individual','g'], suffixes=('','_'))
.dropna(subset=['element_counts_']))
print (df.head(10))
individual cluster_choice_prob_k_fold benchmark_probabilities \
0 9710535 0.512776 0.163837
1 9710535 0.512776 0.098600
2 9710535 0.512776 0.085191
3 9710535 0.512776 0.067577
4 9710535 0.512776 0.065420
5 9710535 0.512776 0.054764
6 9710535 0.512776 0.048068
7 9710535 0.512776 0.018973
8 9710535 0.294674 0.050787
9 9710535 0.294674 0.037609
element_counts g element_counts_
0 [3, 2, 0, 0, 0] 0 3
1 [3, 2, 0, 0, 0] 0 3
2 [3, 2, 0, 0, 0] 0 3
3 [3, 2, 0, 0, 0] 0 3
4 [3, 2, 0, 0, 0] 0 3
5 [3, 2, 0, 0, 0] 0 3
6 [3, 2, 0, 0, 0] 0 3
7 [3, 2, 0, 0, 0] 0 3
8 [3, 2, 0, 0, 0] 1 2
9 [3, 2, 0, 0, 0] 1 2
And last use GroupBy.apply with DataFrame.head for filter rows per groups by added column, last removed both helper columns:
df = (df.groupby(['individual', 'cluster_choice_prob_k_fold'],
group_keys=False,
sort=False)
.apply(lambda x: x.head(x['element_counts_'].iat[0]))
.drop(['g','element_counts_'], axis=1))
print (df)
individual cluster_choice_prob_k_fold benchmark_probabilities \
0 9710535 0.512776 0.163837
1 9710535 0.512776 0.098600
2 9710535 0.512776 0.085191
8 9710535 0.294674 0.050787
9 9710535 0.294674 0.037609
element_counts
0 [3, 2, 0, 0, 0]
1 [3, 2, 0, 0, 0]
2 [3, 2, 0, 0, 0]
8 [3, 2, 0, 0, 0]
9 [3, 2, 0, 0, 0]