Pandas groupby выбирает верхние N строк на основе значения столбца И доли размера группы

У меня есть следующие данные:

    group   cluster probabilityA    probabilityB
0   a   0   0.28    0.153013
1   a   0   0.28    0.133686
2   a   0   0.28    0.058366
3   a   0   0.28    0.091937
4   a   1   0.50    0.040095
5   a   1   0.50    0.150359
6   a   2   0.32    0.043512
7   a   2   0.32    0.088408
8   a   2   0.32    0.005158
9   a   2   0.32    0.107054
10  a   2   0.32    0.029050
11  a   2   0.32    0.099361
12  b   0   0.40    0.057752
13  b   0   0.40    0.177103
14  b   1   0.60    0.218634
15  b   1   0.60    0.098535
16  b   1   0.60    0.065746
17  b   1   0.60    0.190805
18  b   1   0.60    0.191425

Что я хочу сделать, это выбрать 5 лучших (произвольное число, может быть N) строк для каждой группы на основе probabilityB И на долю размеров каждого cluster . Если мы посмотрим только на группу А, то есть 3 кластера: 0, 1 и 2. Их соответствующие доли размера:

group  cluster
a      0          0.333333
       1          0.166667
       2          0.500000
Name: probabilityA, dtype: float64

И здесь, если я хочу топ-5 строк на основе этих акций, я бы взял

(round
      (df
            .groupby(["group", "cluster"])["probabilityA"]
            .count() / 
       df
            .groupby(["group", "cluster"])["probabilityA"]
            .count()
            .sum(level = 0) 
       * 5)

group  cluster
a      0          2.0
       1          1.0
       2          2.0

2 элемента из кластера 0 и 2 и только 1 элемент из кластера 1 на основе столбца probabilityB B. Итак, мой результат будет выглядеть так (индекс не имеет значения в приведенном ниже примере):

    group   cluster probabilityA    probabilityB
0   a   1   0.50    0.150359
1   a   2   0.32    0.107054
2   a   2   0.32    0.088408
3   a   0   0.28    0.153013
4   a   0   0.28    0.133686
5   b   0   0.40    0.177103
6   b   1   0.60    0.218634
7   b   1   0.60    0.191425
8   b   1   0.60    0.190805
9   b   1   0.60    0.098535

Есть ли способ, которым я могу этого достичь?

заранее спасибо!

Всего 2 ответа


Я думаю, что наиболее ясное решение состоит в том, чтобы разделить задачу на этапы:

  1. Генерация счетчиков для каждой группы верхнего уровня:

    c1 = df.groupby(["group"])["probabilityA"].count().rename('c1')
    

    Для ваших данных результат:

    group
    a    12
    b     7
    Name: c1, dtype: int64
    
  2. Установите количество строк для каждой группы верхнего уровня:

    N = 5
    
  3. Сгенерируйте количество строк для каждой группы второго уровня:

    cnt = df.groupby(["group", "cluster"])["probabilityA"].count().rename('c2')
        .reset_index(level=1).join(c1).set_index('cluster', append=True)
        .apply(lambda row: N * row.c2 / row.c1, axis=1).round().astype(int)
    

    Для ваших данных результат:

    group  cluster
    a      0          2
           1          1
           2          2
    b      0          1
           1          4
    dtype: int32
    
  4. Затем определите функцию, оттягивая соответствующее количество «верхних» строк:

    def takeFirst(grp):
        grpKey = tuple(grp.iloc[0, 0:2])
        grpCnt = cnt.loc[grpKey]
        return grp.nlargest(grpCnt, 'probabilityB')
    
  5. И последний шаг - вычислить результат:

    df.groupby(['group', 'cluster']).apply(takeFirst)
    

    Для ваших данных результат:

                     group  cluster  probabilityA  probabilityB
    group cluster                                              
    a     0       0      a        0          0.28      0.153013
                  1      a        0          0.28      0.133686
          1       5      a        1          0.50      0.150359
          2       9      a        2          0.32      0.107054
                  11     a        2          0.32      0.099361
    b     0       13     b        0          0.40      0.177103
          1       14     b        1          0.60      0.218634
                  18     b        1          0.60      0.191425
                  17     b        1          0.60      0.190805
                  15     b        1          0.60      0.098535
    

Я намеренно оставил группу и кластер в качестве столбцов индекса, чтобы упростить идентификацию, из какой группы они были взяты, но в окончательной версии вы можете добавить .reset_index(level=[0,1], drop=True) чтобы удалить их.


Я думаю, что если вы сгруппированы по вероятности A - вы могли бы достичь этого.

df.groupby(['group', 'cluster', 'probabilityA']).aggregate({
    'group': 'first',
    'cluster': 'first',
    'probabilityA': lambda x: round(len(x)/(sum(x)*(len(x))*n),
    'probabilityB': lambda x: sum(x)
})

Вышеупомянутое решение было ошибочным, поскольку count (). Sum () отличается для всей группы и отдельно для вероятности A, поэтому я сделал следующее:

ОБНОВЛЕНИЕ - Полное решение:

  1. Сортируйте ваш фрейм данных:
df.sort_values(by=['group', 'cluster','probabilityB'], ascending=False)
  1. Создайте количество объектов в отдельном сгруппированном фрейме данных:
cluster = pd.DataFrame(round(df.groupby(['group', 'cluster', 'probabilityA'])['probabilityA'].count() 
          / df.groupby(['group', 'cluster', 'probabilityA'])['probabilityB'].count().sum(level=0)*5))
cluster.reset_index(level=['group', 'cluster', 'probabilityA'], inplace=True)
cluster = cluster.rename(columns={0: 'counts'})
cluster['counts'] = pd.to_numeric(cluster['counts'], downcast='integer')
  1. Создать новый фрейм данных с вероятностью сортировкиB:
output = pd.concat(cluster.apply(lambda x: df.loc[(df['group'] == x['group']) & (df['cluster'] == x['cluster'])].groupby(
    ['group', 'cluster']).head(x['counts']), axis=1).tolist())

Вывод: см. Выходной фрейм данных


Есть идеи?

10000