在 oTree 中随机化治疗的有效方法
Efficient ways of randomizing treatments in oTree
我正在为我将在硕士论文中进行的实验编写代码 运行。我基本上完成了,但我仍然坚持最后一个方面,我找不到解决的方法。我有一个 public 很好的游戏,有 16 名参与者,分为 8 个两人组。我有 4 次治疗,我平衡了游戏,s.t。每种治疗每轮由 4 名玩家进行(他们是 12 人)。我现在缺少的部分是我希望每个玩家每轮都玩 3 次。这种随机化是在下面的代码中执行的,理论上是可行的,但实际上我从来没有设法完成它。在 20 分钟内,我设法在第 10 轮结束时,但无法让程序找到满足上述两个条件的第 11 轮和第 12 轮的组合。我知道这有点棘手,如果你更容易理解很喜欢,但是……你有什么建议吗?
非常感谢!
class Subsession(BaseSubsession):
def before_session_starts(self):
info_condition = ['Bel', 'Bel', 'Act', 'Act', 'Ctrl', 'Ctrl', 'No', 'No']
i = 0
condition = True
while condition:
i+= 1
print('I am in the loop: {} th time. Round{}.'.format(i, self.round_number))
self.group_randomly()
for gr_index, g in enumerate(self.get_groups()):
g.info = info_condition[gr_index]
for p in g.get_players():
p.info_player = g.info
condition = any(not p.can_go_on(p.info_player) for p in self.get_players())
if condition == False:
break
p.count_treat()
print('I am out of the loop. Round{}'.format(self.round_number))
class Player(BasePlayer):
runs = models.CharField()
def count_treat(self):
ctrl_count = 0
no_count = 0
bel_count = 0
act_count = 0
for p in self.in_all_rounds():
if p.info_player == "Ctrl":
ctrl_count += 1
elif p.info_player == "No":
no_count += 1
elif p.info_player == "Bel":
bel_count += 1
elif p.info_player == "Act":
act_count += 1
p.runs = dict()
p.runs['Ctrl'] = ctrl_count
p.runs['Bel'] = bel_count
p.runs['No'] = no_count
p.runs['Act'] = act_count
def can_go_on(self, activity):
self.count_treat()
print(self.id_in_subsession, self.runs[activity] < 4, activity, self.runs[activity])
return self.runs[activity] < 4
我不是 100% 确定我遇到了问题,但是如果我建议任务是随机化对象内设计中的治疗顺序,那么每个玩家将每个治疗进行 3 次,但是这个顺序应该在玩家之间随机分配。那我觉得可以这样搞:
在 models.py
中:
import random
class Constants(BaseConstants):
treatments = [['Bel', 'Bel','Bel'],
['Act', 'Act', 'Act'],
['Ctrl', 'Ctrl', 'Ctrl'],
['No', 'No', 'No']]
class Subsession(BaseSubsession):
def before_session_starts(self):
if not self.session.vars.get('treatments'):
treatments = []
for g in self.get_groups():
group_treatments = Constants.treatments
random.shuffle(group_treatments)
flat_treatments = [item for sublist in group_treatments for item in sublist]
if g.id % 2 != 0:
treatments.append(flat_treatments)
else:
treatments.append(treatments[-1])
self.session.vars['treatments'] = treatments
for g in self.get_groups():
g.treatment = self.session.vars['treatments'][g.id - 1][g.round_number - 1]
我们在这里做什么?
在 Constants
中,我们创建了一个包含治疗组的列表列表(不是最优雅的方式,因此如果您需要改变轮次的长度,请以更多 python 方式进行,但为了懒惰有效)。
然后当每个 Subsession
启动时,你会得到这个列表列表,对于每个组,我们将它们打乱(然后每个子列表中的项目不打乱),然后使它们平坦,所以它只是list.After 我们将这个随机处理的治疗列表添加到会话级别的列表中。
因此,在 treatments
会话变量中的第一轮之后,您将获得每组所有治疗的完整数组。
然后您只需将 Group
模型中的 treatment
模型字段设置为等于个性化治疗列表中的项目,对应于当前轮次和组 ID。
更新:使两组的治疗顺序相同
我决定添加另一个答案而不是编辑之前的答案,因为如果您不需要重新排列组,第一个答案仍然有用。
所以,假设您有四种治疗方法 - [A、B、C、D]
满足三个条件:
条件 1:玩家使用该组中的随机治疗进行三轮,然后进行下一次(随机)治疗进行三轮,依此类推。
我们可以将特定玩家的序列描述为列表:AAABBBCCCDDD,其中一个字母对应于一种治疗,它在该字符串中的位置对应于一个回合数。
条件2:每一轮正好有4名玩家打出相同的待遇
- 条件3:重新洗牌。这意味着根本没有球员在所有回合中都具有完全相同的治疗顺序。所以如果有人玩 AAABBBCCCCDDDD,没有人能做到同样的事情,并且要在前三轮成为 A 的那四个玩家之一,另一个玩家需要有类似 AAABBBDDDCCC 的东西。
由于人们总是连续玩三轮(如 AAA 或 BBB),让我们将三重 AAA 描述为 A,等等 - 只是为了简洁起见。所以 AAABBBCCCDDD 将是 ABCD.
下面的代码生成一个矩阵 12X16,其中每个玩家连续进行 3 次每种治疗,并且满足所有 3 个条件。
代码远非最优,我相信更有经验的python程序员会很容易做到一种方式更高效。但它有效。您需要做的就是将这个矩阵分配给任何 session.vars
变量,然后每个玩家都可以根据他的 id 和回合数获得 his/her 待遇。
函数by_letters
比较两个序列,returnTrue
如果两个序列的相同位置至少有一个字母。所以它 returns True
对于 abcd
和 bdca
因为 c
,但是它 returns False
对于 abcd
和 badc
.
我们在开始时生成四种治疗的所有 (24) 种可能排列。这是我们可以使用的序列池。
函数 filtered
return 是可用序列的一个子集,其中字母不重合(基于 by_letters
函数)。
函数triple
只是在找到处理矩阵时将每个处理重复三次。
所以我们通过从我们的一组治疗中产生排列来获得初始数据。
我们从中选择一个随机序列,然后根据第一次抽取过滤掉剩余的数据。那是我们的第二个序列。
我们找到两个集合的交集,这两个集合都没有与序列 1 和序列 2 重合的字母。然后从剩余的子集中我们绘制了一条随机线。那是我们的第三个序列。
我们对三个集合进行相同的交集以选取第四个序列。
这是我们的第一个 4X4 矩阵。
我们从初始数据中过滤掉所有序列(通过使用 itertools.filterfalse
),我们使用初始数据循环四次但没有我们刚刚找到的序列。
由于不是每次都能找到4个4X4的矩阵满足所有条件,所以我们用try.. except..
再画一个随机起始序列
import random
import itertools
initial_data = list(itertools.permutations('abcd'))
def by_letters(a, b):
u = zip(a, b)
for i, j in u:
if i == j:
return True
return False
def filtered(what):
x = [i for i in a if not by_letters(i, what)]
return x
def triple(str):
return ''.join([c+c+c for c in str])
result = None
while result is None:
try:
matrices = []
a = initial_data
for i in range(4):
print('cycle:: ', i)
line1 = random.choice(a)
line2 = random.choice(filtered(line1))
s1 = set(filtered(line1))
s2 = set(filtered(line2))
sets = [s1, s2]
line3 = random.choice(list(set.intersection(*sets)))
s3 = set(filtered(line3))
sets = [s1, s2, s3]
line4 = random.choice(list(set.intersection(*sets)))
matrix1 = [line1, line2, line3, line4]
matrices.append(matrix1)
a = list(itertools.filterfalse(lambda x: x in matrix1, a))
result = matrices
except IndexError:
pass
final = [triple(j) for i in matrices for j in i]
print(final)
输出类似于:
...
['aaabbbdddccc', 'dddcccaaabbb', 'cccdddbbbaaa', 'bbbaaacccddd',
'cccdddaaabbb', 'aaabbbcccddd', 'bbbaaadddccc', 'dddcccbbbaaa',
'aaacccbbbddd', 'dddaaacccbbb', 'cccbbbdddaaa', 'bbbdddaaaccc',
'dddaaabbbccc', 'bbbdddcccaaa', 'aaacccdddbbb', 'cccbbbaaaddd']
我正在为我将在硕士论文中进行的实验编写代码 运行。我基本上完成了,但我仍然坚持最后一个方面,我找不到解决的方法。我有一个 public 很好的游戏,有 16 名参与者,分为 8 个两人组。我有 4 次治疗,我平衡了游戏,s.t。每种治疗每轮由 4 名玩家进行(他们是 12 人)。我现在缺少的部分是我希望每个玩家每轮都玩 3 次。这种随机化是在下面的代码中执行的,理论上是可行的,但实际上我从来没有设法完成它。在 20 分钟内,我设法在第 10 轮结束时,但无法让程序找到满足上述两个条件的第 11 轮和第 12 轮的组合。我知道这有点棘手,如果你更容易理解很喜欢,但是……你有什么建议吗? 非常感谢!
class Subsession(BaseSubsession):
def before_session_starts(self):
info_condition = ['Bel', 'Bel', 'Act', 'Act', 'Ctrl', 'Ctrl', 'No', 'No']
i = 0
condition = True
while condition:
i+= 1
print('I am in the loop: {} th time. Round{}.'.format(i, self.round_number))
self.group_randomly()
for gr_index, g in enumerate(self.get_groups()):
g.info = info_condition[gr_index]
for p in g.get_players():
p.info_player = g.info
condition = any(not p.can_go_on(p.info_player) for p in self.get_players())
if condition == False:
break
p.count_treat()
print('I am out of the loop. Round{}'.format(self.round_number))
class Player(BasePlayer):
runs = models.CharField()
def count_treat(self):
ctrl_count = 0
no_count = 0
bel_count = 0
act_count = 0
for p in self.in_all_rounds():
if p.info_player == "Ctrl":
ctrl_count += 1
elif p.info_player == "No":
no_count += 1
elif p.info_player == "Bel":
bel_count += 1
elif p.info_player == "Act":
act_count += 1
p.runs = dict()
p.runs['Ctrl'] = ctrl_count
p.runs['Bel'] = bel_count
p.runs['No'] = no_count
p.runs['Act'] = act_count
def can_go_on(self, activity):
self.count_treat()
print(self.id_in_subsession, self.runs[activity] < 4, activity, self.runs[activity])
return self.runs[activity] < 4
我不是 100% 确定我遇到了问题,但是如果我建议任务是随机化对象内设计中的治疗顺序,那么每个玩家将每个治疗进行 3 次,但是这个顺序应该在玩家之间随机分配。那我觉得可以这样搞:
在 models.py
中:
import random
class Constants(BaseConstants):
treatments = [['Bel', 'Bel','Bel'],
['Act', 'Act', 'Act'],
['Ctrl', 'Ctrl', 'Ctrl'],
['No', 'No', 'No']]
class Subsession(BaseSubsession):
def before_session_starts(self):
if not self.session.vars.get('treatments'):
treatments = []
for g in self.get_groups():
group_treatments = Constants.treatments
random.shuffle(group_treatments)
flat_treatments = [item for sublist in group_treatments for item in sublist]
if g.id % 2 != 0:
treatments.append(flat_treatments)
else:
treatments.append(treatments[-1])
self.session.vars['treatments'] = treatments
for g in self.get_groups():
g.treatment = self.session.vars['treatments'][g.id - 1][g.round_number - 1]
我们在这里做什么?
在 Constants
中,我们创建了一个包含治疗组的列表列表(不是最优雅的方式,因此如果您需要改变轮次的长度,请以更多 python 方式进行,但为了懒惰有效)。
然后当每个 Subsession
启动时,你会得到这个列表列表,对于每个组,我们将它们打乱(然后每个子列表中的项目不打乱),然后使它们平坦,所以它只是list.After 我们将这个随机处理的治疗列表添加到会话级别的列表中。
因此,在 treatments
会话变量中的第一轮之后,您将获得每组所有治疗的完整数组。
然后您只需将 Group
模型中的 treatment
模型字段设置为等于个性化治疗列表中的项目,对应于当前轮次和组 ID。
更新:使两组的治疗顺序相同
我决定添加另一个答案而不是编辑之前的答案,因为如果您不需要重新排列组,第一个答案仍然有用。
所以,假设您有四种治疗方法 - [A、B、C、D]
满足三个条件:
条件 1:玩家使用该组中的随机治疗进行三轮,然后进行下一次(随机)治疗进行三轮,依此类推。
我们可以将特定玩家的序列描述为列表:AAABBBCCCDDD,其中一个字母对应于一种治疗,它在该字符串中的位置对应于一个回合数。
条件2:每一轮正好有4名玩家打出相同的待遇
- 条件3:重新洗牌。这意味着根本没有球员在所有回合中都具有完全相同的治疗顺序。所以如果有人玩 AAABBBCCCCDDDD,没有人能做到同样的事情,并且要在前三轮成为 A 的那四个玩家之一,另一个玩家需要有类似 AAABBBDDDCCC 的东西。
由于人们总是连续玩三轮(如 AAA 或 BBB),让我们将三重 AAA 描述为 A,等等 - 只是为了简洁起见。所以 AAABBBCCCDDD 将是 ABCD.
下面的代码生成一个矩阵 12X16,其中每个玩家连续进行 3 次每种治疗,并且满足所有 3 个条件。
代码远非最优,我相信更有经验的python程序员会很容易做到一种方式更高效。但它有效。您需要做的就是将这个矩阵分配给任何 session.vars
变量,然后每个玩家都可以根据他的 id 和回合数获得 his/her 待遇。
函数by_letters
比较两个序列,returnTrue
如果两个序列的相同位置至少有一个字母。所以它 returns True
对于 abcd
和 bdca
因为 c
,但是它 returns False
对于 abcd
和 badc
.
我们在开始时生成四种治疗的所有 (24) 种可能排列。这是我们可以使用的序列池。
函数 filtered
return 是可用序列的一个子集,其中字母不重合(基于 by_letters
函数)。
函数triple
只是在找到处理矩阵时将每个处理重复三次。
所以我们通过从我们的一组治疗中产生排列来获得初始数据。
我们从中选择一个随机序列,然后根据第一次抽取过滤掉剩余的数据。那是我们的第二个序列。
我们找到两个集合的交集,这两个集合都没有与序列 1 和序列 2 重合的字母。然后从剩余的子集中我们绘制了一条随机线。那是我们的第三个序列。
我们对三个集合进行相同的交集以选取第四个序列。
这是我们的第一个 4X4 矩阵。
我们从初始数据中过滤掉所有序列(通过使用 itertools.filterfalse
),我们使用初始数据循环四次但没有我们刚刚找到的序列。
由于不是每次都能找到4个4X4的矩阵满足所有条件,所以我们用try.. except..
再画一个随机起始序列
import random
import itertools
initial_data = list(itertools.permutations('abcd'))
def by_letters(a, b):
u = zip(a, b)
for i, j in u:
if i == j:
return True
return False
def filtered(what):
x = [i for i in a if not by_letters(i, what)]
return x
def triple(str):
return ''.join([c+c+c for c in str])
result = None
while result is None:
try:
matrices = []
a = initial_data
for i in range(4):
print('cycle:: ', i)
line1 = random.choice(a)
line2 = random.choice(filtered(line1))
s1 = set(filtered(line1))
s2 = set(filtered(line2))
sets = [s1, s2]
line3 = random.choice(list(set.intersection(*sets)))
s3 = set(filtered(line3))
sets = [s1, s2, s3]
line4 = random.choice(list(set.intersection(*sets)))
matrix1 = [line1, line2, line3, line4]
matrices.append(matrix1)
a = list(itertools.filterfalse(lambda x: x in matrix1, a))
result = matrices
except IndexError:
pass
final = [triple(j) for i in matrices for j in i]
print(final)
输出类似于:
...
['aaabbbdddccc', 'dddcccaaabbb', 'cccdddbbbaaa', 'bbbaaacccddd',
'cccdddaaabbb', 'aaabbbcccddd', 'bbbaaadddccc', 'dddcccbbbaaa',
'aaacccbbbddd', 'dddaaacccbbb', 'cccbbbdddaaa', 'bbbdddaaaccc',
'dddaaabbbccc', 'bbbdddcccaaa', 'aaacccdddbbb', 'cccbbbaaaddd']