用于合并笛卡尔积的Python自联接,以生成所有组合和和和
我是Python的新手,看起来它有很多灵活性,比传统的RDBMS系统更快 在一个非常简单的过程中创建随机幻想团队。我来自RDBMS背景(OracleSQL),对于这种数据处理来说,这似乎不是最佳选择 我使用pandas从csv文件中读取数据框,现在有了一个简单的数据框,其中包含两列——Player、Salary:用于合并笛卡尔积的Python自联接,以生成所有组合和和和,python,python-2.7,pandas,linear-programming,Python,Python 2.7,Pandas,Linear Programming,我是Python的新手,看起来它有很多灵活性,比传统的RDBMS系统更快 在一个非常简单的过程中创建随机幻想团队。我来自RDBMS背景(OracleSQL),对于这种数据处理来说,这似乎不是最佳选择 我使用pandas从csv文件中读取数据框,现在有了一个简单的数据框,其中包含两列——Player、Salary: ` Name Salary 0 Jason Day 11700 1 Dustin Johnson
` Name Salary
0 Jason Day 11700
1 Dustin Johnson 11600
2 Rory McIlroy 11400
3 Jordan Spieth 11100
4 Henrik Stenson 10500
5 Phil Mickelson 10200
6 Justin Rose 9800
7 Adam Scott 9600
8 Sergio Garcia 9400
9 Rickie Fowler 9200`
我试图通过python(pandas)制作6个玩家的所有组合,工资在45000-50000之间
在查找python选项时,我发现itertools组合很有趣,但它会产生大量的组合列表,而不会过滤工资总额
在传统的SQL中,我会进行大规模的合并笛卡尔连接w/SUM,但之后我会让玩家在不同的位置
比如A,B,C然后,C,B,A
我的传统SQL不能很好地工作,如下所示:
` SELECT distinct
ONE.name AS "1",
TWO.name AS "2",
THREE.name AS "3",
FOUR.name AS "4",
FIVE.name AS "5",
SIX.name AS "6",
sum(one.salary + two.salary + three.salary + four.salary + five.salary + six.salary) as salary
FROM
nl.pgachamp2 ONE,nl.pgachamp2 TWO,nl.pgachamp2 THREE, nl.pgachamp2 FOUR,nl.pgachamp2 FIVE,nl.pgachamp2 SIX
where ONE.name != TWO.name
and ONE.name != THREE.name
and one.name != four.name
and one.name != five.name
and TWO.name != THREE.name
and TWO.name != four.name
and two.name != five.name
and TWO.name != six.name
and THREE.name != four.name
and THREE.name != five.name
and three.name != six.name
and five.name != six.name
and four.name != six.name
and four.name != five.name
and one.name != six.name
group by ONE.name, TWO.name, THREE.name, FOUR.name, FIVE.name, SIX.name`
在Pandas/Python中有这样做的方法吗
任何可以指向的文档都将非常棒 我对6个组合进行了测试,没有发现任何团队对此感到满意。我用5代替了 这会让你达到目的:
from itertools import combinations
import pandas as pd
s = df.set_index('Name').squeeze()
combos = pd.DataFrame([c for c in combinations(s.index, 5)])
combo_salary = combos.apply(lambda x: s.ix[x].sum(), axis=1)
combos[(combo_salary >= 45000) & (combo_salary <= 50000)]
来自itertools导入组合的
作为pd进口熊猫
s=df.set_index('Name').squence()
combos=pd.DataFrame([c表示组合中的c(s.index,5)])
combo_salary=combos.apply(lambda x:s.ix[x].sum(),axis=1)
combos[(combo_salary>=45000)和(combo_salary这是一个使用简单算法的非熊猫解决方案。它从按薪资排序的玩家列表中递归生成组合。这允许它跳过生成超出薪资上限的组合
正如piRSquared提到的,没有6人的团队在问题中提到的工资限制范围内,所以我选择了限制来生成少量团队
#!/usr/bin/env python3
''' Limited combinations
Generate combinations of players whose combined salaries fall within given limits
See http://stackoverflow.com/q/38636460/4014959
Written by PM 2Ring 2016.07.28
'''
data = '''\
0 Jason Day 11700
1 Dustin Johnson 11600
2 Rory McIlroy 11400
3 Jordan Spieth 11100
4 Henrik Stenson 10500
5 Phil Mickelson 10200
6 Justin Rose 9800
7 Adam Scott 9600
8 Sergio Garcia 9400
9 Rickie Fowler 9200
'''
data = [s.split() for s in data.splitlines()]
all_players = [(' '.join(u[1:-1]), int(u[-1])) for u in data]
all_players.sort(key=lambda t: t[1])
for i, row in enumerate(all_players):
print(i, row)
print('- '*40)
def choose_teams(free, num, team=(), value=0):
num -= 1
for i, p in enumerate(free):
salary = all_players[p][1]
newvalue = value + salary
if newvalue <= hi:
newteam = team + (p,)
if num == 0:
if newvalue >= lo:
yield newteam, newvalue
else:
yield from choose_teams(free[i+1:], num, newteam, newvalue)
else:
break
#Salary limits
lo, hi = 55000, 60500
#Indices of players that can be chosen for a team
free = tuple(range(len(all_players)))
for i, (t, s) in enumerate(choose_teams(free, 6), 1):
team = [all_players[p] for p in t]
names, sals = zip(*team)
assert sum(sals) == s
print(i, t, names, s)
如果您使用的是不支持
语法的较旧版本的Python,则可以替换
yield from choose_teams(free[i+1:], num, newteam, newvalue)
与
正如评论中提到的,这是一个约束满足问题。它有一个组合部分,但由于您没有定义最小化或最大化的目标,因此它不是一个优化问题(目前)。你可以用很多方法来解决这个问题:你可以尝试像piRSquared这样的暴力,或者使用像PM 2Ring这样的启发式算法。我将用0-1线性规划给出一个解决方案,并使用库来建模和解决这个问题
from pulp import *
import pandas as pd
df = df.set_index('Name')
feasible_solutions = []
为了对问题进行建模,首先需要定义决策变量。这里,决策变量将是每个参与者的一个指标变量:如果选择了该参与者,则为1,否则为0。下面是您在纸浆中的操作方式:
players = LpVariable.dicts('player', df.index.tolist(), 0, 1, LpInteger)
接下来,您将创建一个问题:
prob = pulp.LpProblem('Team Selection', pulp.LpMinimize)
正如我前面提到的,您的问题没有说明任何目标。您只想创建所有可能的团队。因此,我们将定义任意目标函数(我将再次回到该任意函数)
您主要有两个约束:
1) 该队将有5名队员:
prob += lpSum([players[player] for player in players]) == 5
请记住玩家字典存储了我们的决策变量。players[player]
要么是1(如果该玩家在团队中)要么是0(否则)。因此,如果将所有这些变量相加,结果应该等于5
2) 总工资应在45k和50k之间
prob += lpSum([players[player] * df.at[player, 'Salary']
for player in players]) <= 50000
prob += lpSum([players[player] * df.at[player, 'Salary']
for player in players]) >= 45000
prob.solve()
是解决问题的方法。根据结果,它返回一个整数。如果它找到一个最优解,结果是1。对于不可行或无界解,有不同的代码。因此,只要我们能找到新的解,我们就继续循环
在循环中,我们首先将当前解决方案附加到我们的可行的\u解决方案列表中。然后,我们添加另一个约束:对于这5个参与者,变量之和不能超过4(最大值5,如果是5,我们知道这是同一个解决方案)
如果你运行这个,你将得到与piRSquared相同的结果
那么,这有什么好处呢
我们使用整数/二进制线性规划的主要原因是组合的数量增长非常快。这就是所谓的。看看可能的团队数量(没有任何限制):
几乎不可能评估所有组合
当然,当您想要列出满足这些约束的所有组合时,您仍然在冒风险。如果满足约束的组合数量很大,则计算将花费大量时间。您无法避免这种情况。但是,如果子集很小或仍然很大,但您正在对该集合上的函数求值,则情况会好得多
例如,考虑下面的数据文件:
import numpy as np
np.random.seed(0)
df = pd.DataFrame({'Name': ['player' + str(i).zfill(3) for i in range(100)],
'Salary': np.random.randint(0, 9600, 100)})
75287520个解决方案中有268个满足薪资限制。在我的计算机中列出它们需要44秒。使用暴力查找它们需要数小时(更新:需要8小时21分钟)
默认情况下,Palm使用开源解算器。您可以使用其他开源/商业替代解算器与Palm一起使用。商业解算器通常比预期的更快(尽管它们非常昂贵)
另一种选择是我在评论中提到的约束编程。对于这类问题,你可以找到许多聪明的方法来减少约束编程的搜索空间。我对整数编程很满意,所以我展示了一个基于此的模型,但我应该指出,约束编程可能更适合于此 Python是一种通用编程语言,而不是数据库系统。你的第一句话让它听起来好像你期望它是某种数据库。然而,你试图做的事情听起来像背包问题的一种变体;创建符合聚合条件的组合。解决这类问题的正确工具叫做d动态编程。同意——不要期望DB系统,但是JU
prob += lpSum([players[player] for player in players]) == 5
prob += lpSum([players[player] * df.at[player, 'Salary']
for player in players]) <= 50000
prob += lpSum([players[player] * df.at[player, 'Salary']
for player in players]) >= 45000
while prob.solve() == 1:
current_solution = [player for player in players if value(players[player])]
feasible_solutions.append(current_solution)
prob += lpSum([players[player] for player in current_solution]) <= 4
from scipy.misc import comb
comb(10, 5)
Out: 252.0
comb(20, 5)
Out: 15504.0
comb(50, 5)
Out: 2118760.0
comb(100, 5)
Out: 75287520.0
import numpy as np
np.random.seed(0)
df = pd.DataFrame({'Name': ['player' + str(i).zfill(3) for i in range(100)],
'Salary': np.random.randint(0, 9600, 100)})