Каков самый быстрый способ сделать жадное покрытие с помощью Pandas?

Этот вопрос не полностью совпадает с проблемой покрытия жадного набора, но они имеют одну и ту же идею.

Учитывая кадр данных Pandas df1 с одним столбцом df['s'], состоящим из набора ключей df2:

import numpy as np
import pandas as pd
>>> df = pd.DataFrame(np.array([set([1,3,5]), set([1,3,5,6]), set([2,3,4,12]), set([1,3,7]), set([1,15,11]), set([1,16]), set([16])]),columns=['s'])
>>> df
                    s
0      set([1, 3, 5])
1   set([1, 3, 5, 6])
2  set([12, 2, 3, 4])
3      set([1, 3, 7])
4    set([1, 11, 15])
5        set([1, 16])
6           set([16])
        ...

>>> df2 = pd.DataFrame(np.array([[1,2,3,3,3,6,4,8,9,10,11,12,13,14,15,16,5,7],[2.,1.,3.,2.,1.,2.,3.,1.,1.,1.,1.,1.,1.,1.,1.,16.,1.,1.]]).T,columns=['key', 'value'])
>>> df2
    key  value
0     1      2
1     2      1
2     3      3
3     3      2
4     3      1
5     6      2
6     4      3
7     8      1
8     9      1
9    10      1
10   11      1
11   12      1
12   13      1
13   14      1
14   15      1
15   16     16
16    5      1
17    7      1

    ...

Фрейм данных df2 выше может содержать повторяющиеся ключи. Мы выбираем последний. Например, выберите значение «1,0» для ключа «3» выше.

Я хочу найти шесть верхних строк df['s'], которые могут максимально суммировать значения соответствующих им ключей, и отсортировать строки нового фрейма данных по их вкладу значения. Каков самый быстрый способ сделать это?

Для данного набора данных выше первые две строки результирующего кадра данных должны быть

df3:
    set([1,16])
    set([12,2,3,4])
    ...

Второй выше не установлен ([16]), потому что «16» уже содержится в наборе ([1,16]), а добавленное значение равно нулю из набора ([16]).

сортируются суммированием соответствующих значений ключей множества.

Обновить:

Чтобы упростить эту проблему, давайте рассмотрим, что df2 содержит только уникальные ключи. И это можно легко исправить на основе трюка Эндрю.


person Rex    schedule 13.09.2015    source источник
comment
Есть ли у вас разумная граница ключевых значений, например. 1..н? С тех пор это, казалось бы, сводится к некоторой базовой линейной алгебре, и знание pandas/numpy может быть самым быстрым способом сделать это. У вас может быть матрица len(df1['s']) x n для представления наборов в df1['s'], а затем вектор длины n для представления df2. Построение матрицы наборов может быть раздражающим, но для вектора «весов» df2 вам нужно что-то вроде df2.drop_duplicates('key', take_last=True).   -  person Andrew Rosenfeld    schedule 14.09.2015
comment
Ключами являются какие-то неизвестные цифры. Он должен обрабатывать их как строку, так как ключ может быть 0001.   -  person Rex    schedule 14.09.2015
comment
Хорошо, у вас есть ограничение на количество различных ключей? Каковы, по вашему мнению, приблизительные размеры df1 и df2?   -  person Andrew Rosenfeld    schedule 15.09.2015
comment
Грубый размер df1 и df2 составляет около 10 тыс. строк.   -  person Rex    schedule 15.09.2015


Ответы (1)


Предполагая, что у вас не слишком много ключей, вы можете представить свой список наборов в виде разреженной матрицы со столбцом для каждого ключа.

In [29]: df = pd.DataFrame([{1:1,3:1,5:1}, {1:1,3:1,5:1,6:1}, {2:1,3:1,4:1,12:1}, {1:1,3:1,7:1}, {1:1,15:1,11:1}, {9:1}, {16:1}]).fillna(0)

In [30]: df
Out[30]: 
   1   2   3   4   5   6   7   9   11  12  15  16
0   1   0   1   0   1   0   0   0   0   0   0   0
1   1   0   1   0   1   1   0   0   0   0   0   0
2   0   1   1   1   0   0   0   0   0   1   0   0
3   1   0   1   0   0   0   1   0   0   0   0   0
4   1   0   0   0   0   0   0   0   1   0   1   0
5   0   0   0   0   0   0   0   1   0   0   0   0
6   0   0   0   0   0   0   0   0   0   0   0   1

А затем представьте свои веса в виде серии, проиндексированной по ключу:

In [37]: weights = df2.drop_duplicates('key', keep='last').set_index('key')['value']

Затем взвесьте и просуммируйте ваши наборы:

In [40]: totals = (df * weights).sum(axis=1)

In [41]: totals
Out[41]: 
0     4
1     6
2     6
3     4
4     4
5     1
6    16
dtype: float64

А затем просто найдите верхние 6 строк:

In [55]: top6 = totals.order(ascending=False).head(6)

In [56]: top6
Out[56]: 
6    16
2     6
1     6
4     4
3     4
0     4
dtype: float64

Вы можете использовать индексы обратно в разреженную матрицу, чтобы восстановить, какие это были наборы:

In [58]: df.ix[top6.index]
Out[58]: 
   1   2   3   4   5   6   7   9   11  12  15  16
6   0   0   0   0   0   0   0   0   0   0   0   1
2   0   1   1   1   0   0   0   0   0   1   0   0
1   1   0   1   0   1   1   0   0   0   0   0   0
4   1   0   0   0   0   0   0   0   1   0   1   0
3   1   0   1   0   0   0   1   0   0   0   0   0
0   1   0   1   0   1   0   0   0   0   0   0   0

Вам может не понравиться этот подход, но я хотел бы указать на наличие фреймов структур данных, таких как наборы, а не примитивы, поскольку элементы не особенно похожи на панды, поэтому рекомендуется некоторый перевод проблемы.

person Andrew Rosenfeld    schedule 15.09.2015