Skip to content

Commit

Permalink
Add functionality and tests for adding moderator variables to the gra…
Browse files Browse the repository at this point in the history
…ph even if they are not also declared as IVs in the query. This change enables correct inference of random effects for interaction effects
  • Loading branch information
emjun committed Aug 16, 2021
1 parent fdabce6 commit a541ba5
Show file tree
Hide file tree
Showing 3 changed files with 53 additions and 6 deletions.
23 changes: 22 additions & 1 deletion tests/test_graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,28 @@ def test_add_interaction_effects(self):

# Check that the interaction effect inherits from Student unit
self.assertTrue(gr.has_edge(student, lhs, "has"))


def test_interaction_effect_variables_not_as_ivs(self):
u = ts.Unit("Unit")
a = u.nominal("Measure A", cardinality=2) # A is between-subjects
b = u.nominal("Measure B", cardinality=2) # B is between-subjects
c = u.nominal("Measure C", cardinality=2, number_of_instances=2) # B is within-subjects
dv = u.numeric("Dependent variable")

a.moderates(moderator=[b, c], on=dv)

design = ts.Design(dv=dv, ivs=[a, b])
gr = design.graph

variables = [u, a, b, c, dv]
self.assertEqual(len(gr.get_variables()), len(variables) + 1) # + 1 for interaction node that is created
for v in variables:
self.assertIn(v, gr.get_variables())
self.assertTrue(gr.has_edge(u, a, "has"))
self.assertTrue(gr.has_edge(u, b, "has"))
self.assertTrue(gr.has_edge(u, c, "has"))
self.assertTrue(gr.has_edge(u, dv, "has"))

def test_specify_non_nesting_relationship(self):
participant = ts.Unit("participant id", cardinality=12) # 12 participants
condition = participant.nominal("condition", cardinality=2, number_of_instances=2)
Expand Down
14 changes: 11 additions & 3 deletions tisane/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,6 @@ def update_edge(
def add_identifier(self, identifier: AbstractVariable):
self._add_variable(variable=identifier, is_identifier=True)

# TODO: Add repetitions for between/within; I think make repetitions an int not a variable
def add_relationship(
self, relationship: Union[Has, Nests, Associates, Causes, Moderates]
):
Expand Down Expand Up @@ -469,6 +468,17 @@ def moderates(
on: AbstractVariable,
moderates_obj: Moderates,
):
# First make sure that all the moderators are in the graph itself
for m in moderator:
if isinstance(m, Measure):
# Get identifier for each moderator
identifier = m.get_unit()
assert(identifier is not None)
# If the moderator is not in the graph, add it first
if not self.has_edge(start=identifier, end=m, edge_type="has"):
relationship = m.get_unit_relationsihp()
self.has(identifier, m, relationship, repetitions=relationship.repetitions)

# Create new interaction variable
m_names = [m.name for m in moderator]
name = "*".join(m_names)
Expand Down Expand Up @@ -496,8 +506,6 @@ def moderates(
variable=identifier, measure=var, repetitions=m_cardinality
)
self.has(identifier, var, relationship, repetitions=m_cardinality)
# else:
# import pdb; pdb.set_trace()

# Add an ambiguous/contribute edge to the graph
def contribute(self, lhs: AbstractVariable, rhs: AbstractVariable):
Expand Down
22 changes: 20 additions & 2 deletions tisane/variable.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,24 @@ def has(self, measure: AbstractVariable, number_of_instances: typing.Union[int,
self.relationships.append(has_relat)
measure.relationships.append(has_relat)

# @returns the unit this measure is an attribute of
def get_unit(self) -> Unit:
for r in self.relationships:
if isinstance(r, Has):
if r.measure is self and isinstance(r.variable, Unit):
return r.variable

return None

# @returns the unit relationship, None otherwise
def get_unit_relationsihp(self) -> "Has":
for r in self.relationships:
if isinstance(r, Has):
if r.measure is self and isinstance(r.variable, Unit):
return r

return None



"""
Expand Down Expand Up @@ -403,14 +421,14 @@ def __init__(self, moderator: List[AbstractVariable], on: AbstractVariable):
class Has:
variable: AbstractVariable
measure: AbstractVariable
repetitions: int
repetitions: "NumberValue"
according_to: AbstractVariable

def __init__(
self,
variable: AbstractVariable,
measure: AbstractVariable,
repetitions: int,
repetitions: "NumberValue",
**kwargs,
):
self.variable = variable
Expand Down

0 comments on commit a541ba5

Please sign in to comment.