Source code for qbaf_ctrbs.shapley
import math
from qbaf_ctrbs.utils import restrict, determine_powerset
[docs]
def determine_shapley_ctrb(topic, contributors, qbaf):
"""Determines the shapley contribution of a contributor
or a set of contributors to a topic argument.
.. note::
If a set of contributors is provided, its elements are treated
as a single player in the calculation. For example, given three
arguments {a}, {b}, and {c}, specifying {b, c} as a contributor
results in two players: {a} and {b, c}.
Args:
topic (string): The topic argument
contributors (string or set): The contributing argument(s)
qbaf (QBAFramework): The QBAF that contains topic and contributor
Returns:
float: The contribution of the contributor to the topic
This is a wrapper around determine_partitioned_shapley_ctrb.
It partitions qbaf.arguments into singletons,
except for contributors which form one set, then calls determine_partitioned_shapley_ctrb.
"""
partition = {frozenset([a]) for a in qbaf.arguments if a not in contributors and a != topic} | {frozenset([topic]), frozenset(contributors)}
return determine_partitioned_shapley_ctrb(topic, contributors, partition, qbaf)
[docs]
def determine_partitioned_shapley_ctrb(topic, contributors, partition, qbaf):
"""Determines the shapley contribution of a contributor
or a set of contributors to a topic argument for a given argument partition.
Args:
topic (string): The topic argument
contributors (string or set): The contributing argument(s)
partition (set): The argument partitioning
qbaf (QBAFramework): The QBAF that contains topic and contributor
Returns:
float: The contribution of the contributor to the topic
"""
if not isinstance(contributors, set):
contributors = {contributors}
if topic in contributors:
raise Exception ('An argument\'s shapley contribution to itself cannot be determined.')
if not all(item in qbaf.arguments for item in [topic, *contributors]):
raise Exception ('Topic and contributor must be in the QBAF.')
if ({topic} not in partition) or (contributors not in partition):
raise Exception ('Topic and contributor must be part of the given partition.')
partitioned_args = set().union(*partition) # Flatten list of sets into one set.
if partitioned_args != set(qbaf.arguments):
raise Exception('The partition is incomplete.')
total_len = sum(len(s) for s in partition)
if total_len != len(partitioned_args):
raise Exception('Too many arguments in the partition (might not be disjoint).')
sub_ctrbs = []
reduced_partition = [frozenset(part) for part in partition if part not in [{topic}, contributors]]
subsets = determine_powerset(reduced_partition)
for subset in subsets:
targets = {topic} | set().union(*subset)
qbaf_without = restrict(qbaf, list(targets))
targets |= contributors
qbaf_with = restrict(qbaf, list(targets))
weight = (math.factorial(len(subset)) * math.factorial((len(partition) - 1) - len(subset) - 1)
) / math.factorial(len(partition) - 1)
sub_ctrb = weight * (qbaf_with.final_strengths[topic] - qbaf_without.final_strengths[topic])
sub_ctrbs.append(sub_ctrb)
return sum(sub_ctrbs)