用于合并笛卡尔积的Python自联接,以生成所有组合和和和

用于合并笛卡尔积的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

我是Python的新手,看起来它有很多灵活性,比传统的RDBMS系统更快

在一个非常简单的过程中创建随机幻想团队。我来自RDBMS背景(OracleSQL),对于这种数据处理来说,这似乎不是最佳选择

我使用pandas从csv文件中读取数据框,现在有了一个简单的数据框,其中包含两列——Player、Salary:

`                    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)})