Skip to content

Tripartite Graph

Please remember that this class is a subclass of NXBipartiteGraph, so it inherits all its methods. You can check their documentation as well!

NXTripartiteGraph(source_frame=None, item_exo_properties=None, item_contents_dir=None, link_label=None)

Bases: NXBipartiteGraph, TripartiteDiGraph

Class that implements a Tripartite graph through networkx library.

Info

A Tripartite Graph is a graph which supports User nodes, Item nodes and Property nodes, but the latter can only be linked to Item nodes. If you need maximum flexibility, consider using a Full Graph

It creates a graph from an initial Rating object.

Consider the following matrix representation of the Rating object

    +------+-----------+-------+
    | User |   Item    | Score |
    +------+-----------+-------+
    | u1   | Tenet     |     4 |
    | u2   | Inception |     5 |
    | ...  | ...       |   ... |
    +------+-----------+-------+

The graph will be created with the following interactions:

             4
        u1 -----> Tenet
             5
        u2 -----> Inception

where u1 and u2 become User nodes and Tenet and Inception become Item nodes, with the edge weighted depending on the score given

If the link_label parameter is specified, then each link between users and items will be labeled with the label specified (e.g. link_label='score'):

        (4, 'score')
    u1 -------------> Tenet
        (5, 'score')
    u2 -------------> Inception

Then the framework tries to load 'Tenet' and 'Inception' from the item_contents_dir if it is specified and if succeeds, adds in the graph their loaded properties as specified in the item_exo_properties parameter.

Load exogenous properties

In order to load properties in the graph, we must specify where items are serialized and which properties to add:

  • If item_exo_properties is specified as a set, then the graph will try to load all properties from said exogenous representation
{'my_exo_id'}
  • If item_exo_properties is specified as a dict, then the graph will try to load said properties from said exogenous representation
{'my_exo_id': ['my_prop1', 'my_prop2']]}
PARAMETER DESCRIPTION
source_frame

The initial Ratings object needed to create the graph

TYPE: Ratings DEFAULT: None

item_exo_properties

Set or Dict which contains representations to load from items. Use a Set if you want to load all properties from specific representations, or use a Dict if you want to choose which properties to load from specific representations

TYPE: Union[Dict, set] DEFAULT: None

item_contents_dir

The path containing items serialized with the Content Analyzer

TYPE: str DEFAULT: None

link_label

If specified, each link will be labeled with the given label. Default is None

TYPE: str DEFAULT: None

Source code in clayrs/recsys/graphs/nx_implementation/nx_tripartite_graphs.py
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
def __init__(self, source_frame: Ratings = None,
             item_exo_properties: Union[Dict, set] = None,
             item_contents_dir: str = None,
             link_label: str = None):

    NXBipartiteGraph.__init__(self, source_frame, link_label)

    if item_exo_properties and not item_contents_dir:
        logger.warning("`item_exo_properties` parameter set but `item_contents_dir` is None! "
                       "No property will be loaded")
    elif not item_exo_properties and item_contents_dir:
        logger.warning("`item_contents_dir` parameter set but `item_exo_properties` is None! "
                       "No property will be loaded")

    if source_frame is not None and item_contents_dir is not None and item_exo_properties is not None:
        self.add_node_with_prop([ItemNode(item_id) for item_id in source_frame.unique_item_id_column],
                                item_exo_properties,
                                item_contents_dir)

property_nodes: Set[PropertyNode] property

Returns a set of all Property nodes in the graph

Creates a link connecting the start_node to the final_node. If two lists are passed, then the node in position \(i\) in the start_node list will be linked to the node in position \(i\) in the final_node list.

If nodes to link do not exist, they will be added automatically to the graph. Please remember that since this is a Tripartite Graph, only User nodes, Item nodes and Property nodes can be added! And Property nodes can only be linked to Item nodes!

A link can be weighted with the weight parameter and labeled with the label parameter. A timestamp can also be specified via timestamp parameter. All three are optional parameters, so they are not required

PARAMETER DESCRIPTION
start_node

Single Node object or a list of Node objects. They will be the 'head' of the link, since it's a directed graph

TYPE: Union[Node, List[Node]]

final_node

Single Node object or a list Node objects. They will be the 'tail' of the link, since it's a directed graph

TYPE: object

weight

weight of the link, default is None (no weight)

TYPE: float DEFAULT: None

label

label of the link, default is None (no label)

TYPE: str DEFAULT: None

timestamp

timestamp of the link, default is None (no timestamp)

TYPE: str DEFAULT: None

RAISES DESCRIPTION
ValueError

Exception raised when Property nodes are tried to be linked with non-Item nodes

Source code in clayrs/recsys/graphs/nx_implementation/nx_tripartite_graphs.py
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
def add_link(self, start_node: Union[Node, List[Node]], final_node: Union[Node, List[Node]],
             weight: float = None, label: str = None, timestamp: str = None):
    """
    Creates a link connecting the `start_node` to the `final_node`. If two lists are passed, then the node in
    position $i$ in the `start_node` list will be linked to the node in position $i$ in the `final_node` list.

    If nodes to link do not exist, they will be added automatically to the graph. Please remember that since this is
    a Tripartite Graph, only *User nodes*, *Item nodes* and *Property nodes* can be added! And *Property nodes* can
    only be linked to *Item nodes*!

    A link can be weighted with the `weight` parameter and labeled with the `label` parameter.
    A timestamp can also be specified via `timestamp` parameter.
    All three are optional parameters, so they are not required

    Args:
        start_node: Single Node object or a list of Node objects. They will be the 'head' of the link, since it's a
            directed graph
        final_node (object): Single Node object or a list Node objects. They will be the 'tail' of the link,
            since it's a directed graph
        weight: weight of the link, default is None (no weight)
        label: label of the link, default is None (no label)
        timestamp: timestamp of the link, default is None (no timestamp)

    Raises:
        ValueError: Exception raised when Property nodes are tried to be linked with non-Item nodes
    """

    def is_not_valid_link(start_n: Node, final_n: Node):
        return (isinstance(final_n, PropertyNode) and not isinstance(start_n, ItemNode)) or \
               (isinstance(start_n, PropertyNode) and not isinstance(final_n, ItemNode))

    if not isinstance(start_node, list):
        start_node = [start_node]

    if not isinstance(final_node, list):
        final_node = [final_node]

    if any(is_not_valid_link(start_n, final_n) for start_n, final_n in zip(start_node, final_node)):
        raise ValueError("Only item nodes can be linked to property nodes in a Tripartite Graph!")

    self.add_node(start_node)
    self.add_node(final_node)

    not_none_dict = {}
    if label is not None:
        not_none_dict['label'] = label
    if weight is not None:
        not_none_dict['weight'] = weight
    if timestamp is not None:
        not_none_dict['timestamp'] = timestamp

    self._graph.add_edges_from(zip(start_node, final_node),
                               **not_none_dict)

add_node(node)

Adds one or multiple Node objects to the graph. Since this is a Tripartite Graph, only User Node, Item Node and Property Node can be added!

No duplicates are allowed, but different category nodes with same id are (e.g. ItemNode('1') and UserNode('1'))

PARAMETER DESCRIPTION
node

Node(s) object(s) that needs to be added to the graph

TYPE: Union[Node, List[Node]]

RAISES DESCRIPTION
ValueError

Exception raised when one of the node to add to the graph is not a User, Item or Property node

Source code in clayrs/recsys/graphs/nx_implementation/nx_tripartite_graphs.py
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
def add_node(self, node: Union[Node, List[Node]]):
    """
    Adds one or multiple Node objects to the graph.
    Since this is a Tripartite Graph, only `User Node`, `Item Node` and `Property Node` can be added!

    No duplicates are allowed, but different category nodes with same id are (e.g. `ItemNode('1')` and
    `UserNode('1')`)

    Args:
        node: Node(s) object(s) that needs to be added to the graph

    Raises:
        ValueError: Exception raised when one of the node to add to the graph is not a User, Item or Property node
    """
    if not isinstance(node, list):
        node = [node]

    if any(not isinstance(n, (UserNode, ItemNode, PropertyNode)) for n in node):
        raise ValueError("You can only add UserNodes or ItemNodes to a bipartite graph!")

    self._graph.add_nodes_from(node)

add_node_with_prop(node, item_exo_properties, item_contents_dir, item_filename=None)

Adds one or multiple Node objects and its/their properties to the graph. Since this is a Tripartite Graph, only Item Node are allowed to have properties!

In order to load properties in the graph, we must specify where items are serialized and which properties to add:

  • If item_exo_properties is specified as a set, then the graph will try to load all properties from said exogenous representation
{'my_exo_id'}
  • If item_exo_properties is specified as a dict, then the graph will try to load said properties from said exogenous representation
{'my_exo_id': ['my_prop1', 'my_prop2']]}

In case you want your node to have a different id from serialized contents, via the item_filename parameter you can specify what is the filename of the node that you are adding, e.g.

item_to_add = ItemNode('different_id')

# item_filename is 'item_serialized_1.xz'

graph.add_node_with_prop(item_to_add, ..., item_filename='item_serialized_1')

In case you are adding a list of nodes, you can specify the filename for each node in the list.

PARAMETER DESCRIPTION
node

Node(s) object(s) that needs to be added to the graph along with their properties

TYPE: Union[ItemNode, List[ItemNode]]

item_exo_properties

Set or Dict which contains representations to load from items. Use a Set if you want to load all properties from specific representations, or use a Dict if you want to choose which properties to load from specific representations

TYPE: Union[Dict, set]

item_contents_dir

The path containing items serialized with the Content Analyzer

TYPE: str

item_filename

Filename(s) of the node(s) to add

TYPE: Union[str, List[str]] DEFAULT: None

RAISES DESCRIPTION
ValueError

Exception raised when one of the node to add to the graph with their properties is not an ItemNode

Source code in clayrs/recsys/graphs/nx_implementation/nx_tripartite_graphs.py
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
def add_node_with_prop(self, node: Union[ItemNode, List[ItemNode]], item_exo_properties: Union[Dict, set],
                       item_contents_dir: str,
                       item_filename: Union[str, List[str]] = None):
    """
    Adds one or multiple Node objects and its/their properties to the graph.
    Since this is a Tripartite Graph, only `Item Node` are allowed to have properties!

    In order to load properties in the graph, we must specify where items are serialized and ***which
    properties to add***:

    *   If *item_exo_properties* is specified as a **set**, then the graph will try to load **all properties**
    from **said exogenous representation**

    ```python
    {'my_exo_id'}
    ```

    *   If *item_exo_properties* is specified as a **dict**, then the graph will try to load **said properties**
    from **said exogenous representation**

    ```python
    {'my_exo_id': ['my_prop1', 'my_prop2']]}
    ```

    In case you want your node to have a different id from serialized contents, via the `item_filename` parameter
    you can specify what is the filename of the node that you are adding, e.g.

    ```
    item_to_add = ItemNode('different_id')

    # item_filename is 'item_serialized_1.xz'

    graph.add_node_with_prop(item_to_add, ..., item_filename='item_serialized_1')
    ```

    In case you are adding a list of nodes, you can specify the filename for each node in the list.

    Args:
        node: Node(s) object(s) that needs to be added to the graph along with their properties
        item_exo_properties: Set or Dict which contains representations to load from items. Use a `Set` if you want
            to load all properties from specific representations, or use a `Dict` if you want to choose which
            properties to load from specific representations
        item_contents_dir: The path containing items serialized with the Content Analyzer
        item_filename: Filename(s) of the node(s) to add

    Raises:
        ValueError: Exception raised when one of the node to add to the graph with their properties is not
            an ItemNode
    """
    def node_prop_link_generator():
        for n, id in zip(progbar, item_filename):
            item: Content = loaded_items.get(id)

            if item is not None:
                exo_props = self._get_exo_props(item_exo_properties, item)

                single_item_prop_edges = [(n,
                                           PropertyNode(prop_dict[prop]),
                                           {'label': prop})
                                          for prop_dict in exo_props for prop in prop_dict]

            else:
                single_item_prop_edges = []

            yield from single_item_prop_edges

    if not isinstance(node, list):
        node = [node]

    if any(not isinstance(n, ItemNode) for n in node):
        raise ValueError("Only item nodes can be linked to property nodes in a Tripartite Graph!")

    if isinstance(item_exo_properties, set):
        item_exo_properties = dict.fromkeys(item_exo_properties, None)

    if item_filename is None:
        item_filename = [n.value for n in node]

    if not isinstance(item_filename, list):
        item_filename = [item_filename]

    loaded_items = LoadedContentsDict(item_contents_dir, contents_to_load=set(item_filename))
    with get_progbar(node) as progbar:

        progbar.set_description("Creating Item->Properties links")
        self._graph.add_edges_from((tuple_to_add for tuple_to_add in node_prop_link_generator()))