Python中的投资组合选择与固定集合的约束

时间:2017-05-17 01:45:43

标签: python optimization scipy

我正在开展一个项目,我试图从一组125名玩家中选择最佳玩家子集(如下所示)

约束是:

a)球员数量= 3

b)价格总和< = 30

优化函数是Max(投票总和)

        Player  Vote  Price
  William Smith  0.67    8.6
Robert Thompson  0.31    6.7
Joseph Robinson  0.61    6.2
Richard Johnson  0.88    4.3
   Richard Hall  0.28    9.7

我查看了scipy优化包,但我无法找到将宇宙约束到此子集的任何方法。有没有人能指出我是否有一个可以做到这一点的图书馆? 感谢

3 个答案:

答案 0 :(得分:3)

这个问题很适合用数学程序表达,可以用不同的优化库来解决。

它被称为精确的k项背包问题

例如,您可以使用Package PuLP。它具有与不同优化软件包的接口,但捆绑了一个免费的解算器。

easy_install pulp

免费解算器通常比商业解算器慢,但我认为PuLP应该能够用标准求解器解决问题的合理大型版本。

您的问题可以通过PuLP解决,如下所示:

from pulp import *

# Data input
players = ["William Smith", "Robert Thompson", "Joseph Robinson", "Richard Johnson", "Richard Hall"]
vote = [0.67, 0.31, 0.61, 0.88, 0.28]
price = [8.6, 6.7, 6.2, 4.3, 9.7]

P = range(len(players))

# Declare problem instance, maximization problem
prob = LpProblem("Portfolio", LpMaximize)

# Declare decision variable x, which is 1 if a
# player is part of the portfolio and 0 else
x = LpVariable.matrix("x", list(P), 0, 1, LpInteger)

# Objective function -> Maximize votes
prob += sum(vote[p] * x[p] for p in P)

# Constraint definition
prob += sum(x[p] for p in P) == 3
prob += sum(price[p] * x[p] for p in P) <= 30

# Start solving the problem instance
prob.solve()

# Extract solution
portfolio = [players[p] for p in P if x[p].varValue]
print(portfolio)

使用与Brad Solomon相同的随机数据从125中抽取3名玩家的运行时间为0.5秒。

答案 1 :(得分:1)

由于 a)约束,您的问题是离散优化任务。你应该引入离散变量来表示采取/不采取玩家。请考虑以下Minizinc伪代码:

array[players_num] of var bool: taken_players;
array[players_num] of float: votes;
array[players_num] of float: prices;

constraint sum (taken_players * prices) <= 30;
constraint sum (taken_players) = 3;

solve maximize sum (taken_players * votes);

据我所知,你不能用scipy来解决这些问题(例如this)。

您可以通过以下方式解决问题:

  1. 您可以在Python中生成Minizinc问题并通过调用外部解算器来解决它。它看起来更具可扩展性和稳健性。
  2. 您可以使用simulated annealing
  3. Mixed integer approach
  4. 第二种选择似乎对您来说更简单。但是,就个人而言,我更喜欢第一种:它允许您引入各种各样的约束,问题表达感觉更自然和清晰。

答案 2 :(得分:1)

@CaptainTrunky是正确的,scipy.minimize在这里不起作用。

这是一个使用itertools的非常糟糕的解决方法,请忽略其他方法之一是否有效。考虑从125创建3个玩家创建317,750个组合,n!/((n - k)!* k!)。主循环运行时~6m。

from itertools import combinations

df = DataFrame({'Player' : np.arange(0, 125),
                'Vote' : 10 * np.random.random(125),
                'Price' : np.random.randint(1, 10, 125)
                })

df
Out[109]: 
     Player  Price     Vote
0         0      4  7.52425
1         1      6  3.62207
2         2      9  4.69236
3         3      4  5.24461
4         4      4  5.41303
..      ...    ...      ...
120     120      9  8.48551
121     121      8  9.95126
122     122      8  6.29137
123     123      8  1.07988
124     124      4  2.02374

players = df.Player.values
idx = pd.MultiIndex.from_tuples([i for i in combinations(players, 3)])

votes = []
prices = []

for i in combinations(players, 3):
    vote = df[df.Player.isin(i)].sum()['Vote']
    price = df[df.Player.isin(i)].sum()['Price']
    votes.append(vote); prices.append(price)

result = DataFrame({'Price' : prices, 'Vote' : votes}, index=idx)

# The index below is (first player, second player, third player)

result[result.Price <= 30].sort_values('Vote', ascending=False)
Out[128]: 
           Price      Vote
63 87 121   25.0  29.75051
   64 121   20.0  29.62626
64 87 121   19.0  29.61032
63 64 87    20.0  29.56665
   65 121   24.0  29.54248
         ...       ...
18 22 78    12.0   1.06352
   23 103   20.0   1.02450
22 23 103   20.0   1.00835
18 22 103   15.0   0.98461
      23    14.0   0.98372