mlnlib 0.2.39__tar.gz → 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {mlnlib-0.2.39 → mlnlib-0.3.0}/LICENSE.md +1 -20
- mlnlib-0.3.0/PKG-INFO +282 -0
- mlnlib-0.3.0/README.md +260 -0
- {mlnlib-0.2.39 → mlnlib-0.3.0}/pyproject.toml +10 -6
- mlnlib-0.3.0/src/mlnlib/__init__.py +21 -0
- {mlnlib-0.2.39 → mlnlib-0.3.0}/src/mlnlib/mln.py +457 -260
- {mlnlib-0.2.39 → mlnlib-0.3.0}/src/mlnlib/preparation.py +132 -50
- mlnlib-0.3.0/src/mlnlib.egg-info/PKG-INFO +282 -0
- {mlnlib-0.2.39 → mlnlib-0.3.0}/src/mlnlib.egg-info/SOURCES.txt +1 -0
- {mlnlib-0.2.39 → mlnlib-0.3.0}/src/mlnlib.egg-info/dependency_links.txt +0 -0
- {mlnlib-0.2.39 → mlnlib-0.3.0}/src/mlnlib.egg-info/requires.txt +0 -0
- {mlnlib-0.2.39 → mlnlib-0.3.0}/src/mlnlib.egg-info/top_level.txt +0 -0
- mlnlib-0.2.39/PKG-INFO +0 -127
- mlnlib-0.2.39/README.md +0 -106
- mlnlib-0.2.39/src/mlnlib.egg-info/PKG-INFO +0 -127
- {mlnlib-0.2.39 → mlnlib-0.3.0}/setup.cfg +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
MIT License
|
|
2
2
|
|
|
3
3
|
Copyright (c) 2024 Eszter Bokányi
|
|
4
4
|
|
|
@@ -12,25 +12,6 @@ furnished to do so, subject to the following conditions:
|
|
|
12
12
|
The above copyright notice and this permission notice shall be included in all
|
|
13
13
|
copies or substantial portions of the Software.
|
|
14
14
|
|
|
15
|
-
In any academic work, the package should be cited as follows:
|
|
16
|
-
|
|
17
|
-
Bokányi, E., Kazmina, Y., & de Jong, R. (2023). POPNET MultiLayerNetwork Python Class (Version v2.0) [Computer software]. Zenodo. https://doi.org/10.5281/zenodo.10838865
|
|
18
|
-
|
|
19
|
-
The BibTeX entry for the citation is:
|
|
20
|
-
```
|
|
21
|
-
@software{bokanyi2023popnet,
|
|
22
|
-
title = {{{POPNET MultiLayerNetwork Python Class}}},
|
|
23
|
-
author = {Bok\'anyi, Eszter and Kazmina, Yuliia and family=Jong, given=Rachel, prefix=de, useprefix=true},
|
|
24
|
-
date = {2023},
|
|
25
|
-
doi = {10.5281/zenodo.10838865},
|
|
26
|
-
url = {https://zenodo.org/doi/10.5281/zenodo.10838865},
|
|
27
|
-
urldate = {2024-11-11},
|
|
28
|
-
abstract = {Library for working with large multilayer networks by the POPNET group.},
|
|
29
|
-
organization = {Zenodo},
|
|
30
|
-
version = {v2.0}
|
|
31
|
-
}
|
|
32
|
-
```
|
|
33
|
-
|
|
34
15
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
35
16
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
36
17
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
mlnlib-0.3.0/PKG-INFO
ADDED
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mlnlib
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Memory-efficient library for loading and working with large unweighted multilayer networks.
|
|
5
|
+
Author: Rachel G. de Jong, Yuliia Kazmina, Frank W. Takes
|
|
6
|
+
Author-email: Eszter Bokányi <e.bokanyi@liacs.leidenuniv.nl>
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
Project-URL: Homepage, https://github.com/planet-nl/mlnlib
|
|
9
|
+
Project-URL: Issues, https://github.com/planet-nl/mlnlib/issues
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Requires-Python: >=3.8
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE.md
|
|
15
|
+
Requires-Dist: pandas>=1.4.4
|
|
16
|
+
Requires-Dist: polars>=0.20.6
|
|
17
|
+
Requires-Dist: numpy>=1.20.3
|
|
18
|
+
Requires-Dist: python-igraph>=0.9.9
|
|
19
|
+
Requires-Dist: networkx>=3.0
|
|
20
|
+
Requires-Dist: scipy>=1.10.1
|
|
21
|
+
Dynamic: license-file
|
|
22
|
+
|
|
23
|
+
# mlnlib
|
|
24
|
+
|
|
25
|
+
Memory-efficient Python tools by the [PLANET-NL](https://planetnl.org) team (formerly POPNET) for working with large multilayer networks on machines with limited memory/cores.
|
|
26
|
+
|
|
27
|
+
Authors:
|
|
28
|
+
* Eszter Bokányi
|
|
29
|
+
* Rachel de Jong
|
|
30
|
+
* Yuliia Kazmina
|
|
31
|
+
* Frank W. Takes
|
|
32
|
+
|
|
33
|
+
Contact: `e.bokanyi@liacs.leidenuniv.nl`
|
|
34
|
+
|
|
35
|
+
License: MIT (see `LICENSE.md`). Please attribute appropriately when reusing.
|
|
36
|
+
|
|
37
|
+
## Overview
|
|
38
|
+
|
|
39
|
+
`mlnlib` provides the `MultiLayerNetwork` class for building, loading, manipulating, and exporting multilayer networks where edges can exist on multiple layers. Layers are encoded efficiently in a single sparse adjacency matrix using a binary encoding (powers of two).
|
|
40
|
+
|
|
41
|
+
Key components stored in a `MultiLayerNetwork` instance:
|
|
42
|
+
- `nodes`: pandas DataFrame (or polars when enabled) with at least columns `label` and `id`
|
|
43
|
+
- `A`: scipy.sparse.csr_matrix with binary layer encoding in its data
|
|
44
|
+
- `layers`: pandas DataFrame with at least columns `layer`, `label`, and `binary`; optionally `group`
|
|
45
|
+
|
|
46
|
+
## Install
|
|
47
|
+
|
|
48
|
+
From PyPI:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
pip install mlnlib
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Loading data
|
|
55
|
+
|
|
56
|
+
There are three supported ways to initialize a `MultiLayerNetwork` (class defined in `src/mlnlib/mln.py`).
|
|
57
|
+
|
|
58
|
+
1) Load a prepared library from disk
|
|
59
|
+
|
|
60
|
+
Folder layout must contain at least:
|
|
61
|
+
- `edges.npz` — NxN csr_matrix with binary encoded layers
|
|
62
|
+
- `nodes.csv.gz` or `nodes_{pd.__version__}.pkl` — nodes DataFrame with `label` and `id`
|
|
63
|
+
- `layers.csv` — layer definitions with `layer`, `label`, `binary` (and optional `group`)
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
from mlnlib import MultiLayerNetwork
|
|
67
|
+
|
|
68
|
+
mln = MultiLayerNetwork(
|
|
69
|
+
load_from_library=True,
|
|
70
|
+
library_path="/path/to/library"
|
|
71
|
+
)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
2) Build from raw CSVs via a config
|
|
75
|
+
|
|
76
|
+
Uses `RawCSVtoMLN` behind the scenes (see `src/mlnlib/preparation.py` and `test/config.json` for an example schema).
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
from mlnlib import MultiLayerNetwork
|
|
80
|
+
|
|
81
|
+
mln = MultiLayerNetwork(
|
|
82
|
+
load_from_config=True,
|
|
83
|
+
config_path="config.json"
|
|
84
|
+
)
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
3) Provide in-memory objects
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
from mlnlib import MultiLayerNetwork
|
|
91
|
+
from scipy.sparse import csr_matrix
|
|
92
|
+
import pandas as pd
|
|
93
|
+
|
|
94
|
+
# nodes must have columns: label, id (IDs are 0..N-1)
|
|
95
|
+
nodes = pd.DataFrame({
|
|
96
|
+
"label": ["a","b","c"],
|
|
97
|
+
"id": [0,1,2]
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
# layers dataframe: at least layer, label, binary
|
|
101
|
+
layers = pd.DataFrame({
|
|
102
|
+
"layer": [0,1],
|
|
103
|
+
"label": ["friendship","kinship"],
|
|
104
|
+
"binary": [1,2]
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
# A is NxN csr_matrix whose data encodes layers as sums of powers of two
|
|
108
|
+
A = csr_matrix((3,3), dtype="int64")
|
|
109
|
+
|
|
110
|
+
mln = MultiLayerNetwork(nodes=nodes, edges=A, layers=layers)
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Binary layer encoding
|
|
114
|
+
|
|
115
|
+
Each layer is assigned a power-of-two value: 1, 2, 4, 8, ... When an edge exists on multiple layers, the corresponding entry in `A` stores the sum, enabling fast bitwise checks. For example, if `A[i,j] = 7`, then layers 0, 1, and 2 are present because `7 = 1 + 2 + 4`. Checking for layer 1 is a cheap mask: `7 & 2 == 2`.
|
|
116
|
+
|
|
117
|
+
Binary encoding is stored in `self.layers`.
|
|
118
|
+
|
|
119
|
+
## Common tasks
|
|
120
|
+
|
|
121
|
+
- Convert node identifiers
|
|
122
|
+
- `mln.to_id("person_123")` or `mln.to_label(42)`
|
|
123
|
+
- Filter by nodes/layers/groups
|
|
124
|
+
- `mln.get_filtered_network(nodes_selected=[...], layers_selected=[...], groups_selected=[...], node_type="label", layer_type="label")`
|
|
125
|
+
- Per-layer adjacency
|
|
126
|
+
- `mln.get_layer_adjacency_matrix(layer="friendship", layer_type="label", store=True, dtype="int8")`
|
|
127
|
+
- Supra-adjacency matrix
|
|
128
|
+
- `mln.get_supra_adjacency_matrix(dtype="int8")`
|
|
129
|
+
- Edgelist export
|
|
130
|
+
- `mln.get_edgelist(edge_attribute="label")` # options: "binary", "layer", "label", "weight", or None
|
|
131
|
+
- Degrees and clustering
|
|
132
|
+
- `mln.get_degrees([...])`, `mln.get_clustering_coefficient([...])`
|
|
133
|
+
- Excess closure
|
|
134
|
+
- `mln.get_excess_closure(selected_nodes=[...], selected_layers=[...], layer_type="label")`
|
|
135
|
+
- Convert to graph libraries
|
|
136
|
+
- `mln.to_igraph(directed=True, edge_attributes=True, node_attributes=False, edge_attribute_type="binary")`
|
|
137
|
+
- `mln.to_networkx(directed=True, edge_attributes=True, node_attributes=False, edge_attribute_type="binary")`
|
|
138
|
+
|
|
139
|
+
## Saving and exporting
|
|
140
|
+
|
|
141
|
+
Save a prepared library for fast future loads:
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
mln.save(path="/path/to/library", overwrite=False)
|
|
145
|
+
# creates edges.npz, nodes.csv.gz (or pickle), layers.csv
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Export individual pieces:
|
|
149
|
+
- Edges: `mln.export_edges("edges.npz" | "edges.csv" | "edges.csv.gz")`
|
|
150
|
+
- Nodes: `mln.export_nodes("nodes.csv" | "nodes.csv.gz")`
|
|
151
|
+
- Layers: `mln.export_layers("layers.csv")`
|
|
152
|
+
- GraphML: `mln.save_to_graphml("network.graphml", edge_attribute_type="binary")`
|
|
153
|
+
|
|
154
|
+
## Data structures
|
|
155
|
+
|
|
156
|
+
- Nodes (`mln.nodes`): DataFrame with at least `label` and `id`. You may add arbitrary node attributes. Preferred mapping helpers are `to_id()` and `to_label()`.
|
|
157
|
+
- Layers (`mln.layers`): DataFrame with columns:
|
|
158
|
+
- `layer`: arbitrary ID, can be of any type
|
|
159
|
+
- `label`: human-readable name
|
|
160
|
+
- `label_long` : longer description
|
|
161
|
+
- `binary`: power-of-two encoding
|
|
162
|
+
- `group` (optional): used for grouped aggregations
|
|
163
|
+
- Adjacency (`mln.A`): csr_matrix whose data contains the sum of layer powers-of-two. Use `mln.get_binary_adjacency()` to collapse to 0/1.
|
|
164
|
+
|
|
165
|
+
## API Reference
|
|
166
|
+
|
|
167
|
+
### Core Methods
|
|
168
|
+
|
|
169
|
+
#### Node ID mapping (preferred interface)
|
|
170
|
+
- `to_id(labels: Any | List[Any]) -> int | List[int]`
|
|
171
|
+
Convert node label(s) to integer ID(s).
|
|
172
|
+
|
|
173
|
+
- `to_label(ids: int | List[int]) -> Any | List[Any]`
|
|
174
|
+
Convert integer node ID(s) to label(s).
|
|
175
|
+
|
|
176
|
+
#### Filtering and subgraphs
|
|
177
|
+
- `get_filtered_network(nodes_selected=None, layers_selected=[], groups_selected=[], node_type="label", layer_type="label", keep_node_alignment=False) -> MultiLayerNetwork`
|
|
178
|
+
Return a filtered subnetwork based on node and/or layer selection.
|
|
179
|
+
|
|
180
|
+
- `get_egonetwork(ego_label, depth=1, return_list=False, ignore_limit=False) -> MultiLayerNetwork | List`
|
|
181
|
+
Extract ego network centered on a node at specified depth.
|
|
182
|
+
|
|
183
|
+
#### Layer-specific adjacency
|
|
184
|
+
- `get_layer_adjacency_matrix(layer, layer_type="layer", store=False, dtype="int64") -> csr_matrix`
|
|
185
|
+
Create binary adjacency matrix for a single layer or group.
|
|
186
|
+
|
|
187
|
+
- `get_binary_adjacency(dtype="int64") -> csr_matrix`
|
|
188
|
+
Convert multilayer adjacency to binary (0/1) format, collapsing all layer information.
|
|
189
|
+
|
|
190
|
+
- `get_supra_adjacency_matrix(dtype="int64") -> csr_matrix`
|
|
191
|
+
Build supra-adjacency matrix (L*N × L*N) with inter-layer couplings.
|
|
192
|
+
|
|
193
|
+
- `get_grouped_mln() -> MultiLayerNetwork`
|
|
194
|
+
Return network with layers aggregated by group (requires `group` column in `layers`).
|
|
195
|
+
|
|
196
|
+
- `clear_layer_adjacency_matrices()`, `clear_group_adjacency_matrices()`, `clear_all_adjacency_matrices()`
|
|
197
|
+
Clear cached adjacency matrices.
|
|
198
|
+
|
|
199
|
+
#### Edge lists and conversions
|
|
200
|
+
- `get_edgelist(edge_attribute="binary") -> DataFrame`
|
|
201
|
+
Convert sparse adjacency matrix to edgelist. Options: "binary", "layer", "label", "weight", or None.
|
|
202
|
+
|
|
203
|
+
- `convert_layer_representation(layer, input_type="layer", output_type="binary") -> Any | List[Any]`
|
|
204
|
+
Convert between layer representations: "layer", "label", "binary".
|
|
205
|
+
|
|
206
|
+
- `convert_layer_binary_to_list(num, output_type="layer") -> List`
|
|
207
|
+
Decode binary integer to list of active layers.
|
|
208
|
+
|
|
209
|
+
#### Graph library exports
|
|
210
|
+
- `to_igraph(directed=True, edge_attributes=True, node_attributes=False, replace_igraph=False, edge_attribute_type="binary") -> ig.Graph`
|
|
211
|
+
Create igraph object. Options for `edge_attribute_type`: "binary", "weight".
|
|
212
|
+
|
|
213
|
+
- `to_networkx(directed=True, edge_attributes=True, node_attributes=False, edge_attribute_type="binary", layer_type="layer", ignore_limit=False) -> nx.Graph | nx.DiGraph | None`
|
|
214
|
+
Create NetworkX object (limited by `nx_node_limit` unless `ignore_limit=True`).
|
|
215
|
+
|
|
216
|
+
- `save_to_graphml(file_name, directed=True, edge_attributes=True, node_attributes=False, overwrite=False, edge_attribute_type="binary")`
|
|
217
|
+
Save as GraphML for external tools (Gephi, etc.).
|
|
218
|
+
|
|
219
|
+
#### Metrics and analysis
|
|
220
|
+
- `get_degrees(selected_nodes=[]) -> Dict[Any, int]`
|
|
221
|
+
Calculate degree for selected nodes across all layers.
|
|
222
|
+
|
|
223
|
+
- `get_clustering_coefficient(selected_nodes=[], batchsize=100000) -> Dict[Any, float]`
|
|
224
|
+
Compute clustering coefficient for selected nodes.
|
|
225
|
+
|
|
226
|
+
- `get_excess_closure(selected_nodes=[], node_type="label", selected_layers=[], layer_type="layer", batchsize=100000) -> Dict[str, Dict]`
|
|
227
|
+
Calculate excess closure metric (returns both clustering coefficient and excess closure).
|
|
228
|
+
|
|
229
|
+
#### Node operations
|
|
230
|
+
- `add_nodes(nodes_df: pd.DataFrame)`
|
|
231
|
+
Add new nodes without connections (expands adjacency matrix).
|
|
232
|
+
|
|
233
|
+
- `remove_nodes(nodes: List[Any])`
|
|
234
|
+
Remove nodes and their edges; reindexes remaining nodes.
|
|
235
|
+
|
|
236
|
+
#### Aggregation
|
|
237
|
+
- `create_affiliation_matrix(key, affil_edgelist: List[Tuple])`
|
|
238
|
+
Create bipartite affiliation matrix for node-to-entity relationships.
|
|
239
|
+
|
|
240
|
+
- `get_aggregated_network(aggregation_column=None, keep_layers=False, convert_A="none") -> MultiLayerNetwork`
|
|
241
|
+
Aggregate network by node attribute (e.g., region, age group).
|
|
242
|
+
|
|
243
|
+
#### Persistence
|
|
244
|
+
- `save(path="", overwrite=False, **kwargs)`
|
|
245
|
+
Save MLN library (edges.npz, nodes.csv.gz, layers.csv, optional codebook).
|
|
246
|
+
|
|
247
|
+
- `export_edges(file_name)`, `export_nodes(file_name)`, `export_layers(file_name)`, `export_codebook(file_name)`
|
|
248
|
+
Export individual components.
|
|
249
|
+
|
|
250
|
+
- `load(path) -> Tuple[DataFrame, csr_matrix, DataFrame]`
|
|
251
|
+
Load MLN components from disk.
|
|
252
|
+
|
|
253
|
+
#### Utilities
|
|
254
|
+
- `report_time(message="", init=False)`
|
|
255
|
+
Helper for timing measurements (requires `verbose=True`).
|
|
256
|
+
|
|
257
|
+
### RawCSVtoMLN (preparation.py)
|
|
258
|
+
|
|
259
|
+
Utility class for converting raw CSV node/edge files into MLN library format.
|
|
260
|
+
|
|
261
|
+
- `__init__(node_conf, edge_conf, layer_conf, output_folder, **kwargs)`
|
|
262
|
+
Configure input/output paths and column mappings.
|
|
263
|
+
|
|
264
|
+
- `init_all()`
|
|
265
|
+
Run full pipeline: init_layers() → init_nodes() → init_edges() → read_all_edges() → save_all() (if configured).
|
|
266
|
+
|
|
267
|
+
- `save_all(overwrite=False)`
|
|
268
|
+
Save nodes.csv.gz, edges.npz, and layers.csv to output folder.
|
|
269
|
+
|
|
270
|
+
See `test/config.json` for configuration schema.
|
|
271
|
+
|
|
272
|
+
## Notes
|
|
273
|
+
|
|
274
|
+
- NetworkX conversion limits very large graphs (`nx_node_limit` in code). Use igraph for larger networks.
|
|
275
|
+
- The package supports pandas by default and optional polars interop for CSV loading and some operations.
|
|
276
|
+
- The network can be directed; layer information is encoded in the adjacency matrix when `adjacency_element="binary"`.
|
|
277
|
+
|
|
278
|
+
## Citation
|
|
279
|
+
|
|
280
|
+
If you use this library in academic work, please cite this repository and acknowledge the PLANET-NL project (https://planetnl.org). Citation metadata is available in `CITATION.cff`, and GitHub shows it under "Cite this repository" in the sidebar.
|
|
281
|
+
|
|
282
|
+
A software paper describing `mlnlib` is in preparation (see [`paper/`](paper/)). We will update the citation information once it is published.
|
mlnlib-0.3.0/README.md
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
# mlnlib
|
|
2
|
+
|
|
3
|
+
Memory-efficient Python tools by the [PLANET-NL](https://planetnl.org) team (formerly POPNET) for working with large multilayer networks on machines with limited memory/cores.
|
|
4
|
+
|
|
5
|
+
Authors:
|
|
6
|
+
* Eszter Bokányi
|
|
7
|
+
* Rachel de Jong
|
|
8
|
+
* Yuliia Kazmina
|
|
9
|
+
* Frank W. Takes
|
|
10
|
+
|
|
11
|
+
Contact: `e.bokanyi@liacs.leidenuniv.nl`
|
|
12
|
+
|
|
13
|
+
License: MIT (see `LICENSE.md`). Please attribute appropriately when reusing.
|
|
14
|
+
|
|
15
|
+
## Overview
|
|
16
|
+
|
|
17
|
+
`mlnlib` provides the `MultiLayerNetwork` class for building, loading, manipulating, and exporting multilayer networks where edges can exist on multiple layers. Layers are encoded efficiently in a single sparse adjacency matrix using a binary encoding (powers of two).
|
|
18
|
+
|
|
19
|
+
Key components stored in a `MultiLayerNetwork` instance:
|
|
20
|
+
- `nodes`: pandas DataFrame (or polars when enabled) with at least columns `label` and `id`
|
|
21
|
+
- `A`: scipy.sparse.csr_matrix with binary layer encoding in its data
|
|
22
|
+
- `layers`: pandas DataFrame with at least columns `layer`, `label`, and `binary`; optionally `group`
|
|
23
|
+
|
|
24
|
+
## Install
|
|
25
|
+
|
|
26
|
+
From PyPI:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pip install mlnlib
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Loading data
|
|
33
|
+
|
|
34
|
+
There are three supported ways to initialize a `MultiLayerNetwork` (class defined in `src/mlnlib/mln.py`).
|
|
35
|
+
|
|
36
|
+
1) Load a prepared library from disk
|
|
37
|
+
|
|
38
|
+
Folder layout must contain at least:
|
|
39
|
+
- `edges.npz` — NxN csr_matrix with binary encoded layers
|
|
40
|
+
- `nodes.csv.gz` or `nodes_{pd.__version__}.pkl` — nodes DataFrame with `label` and `id`
|
|
41
|
+
- `layers.csv` — layer definitions with `layer`, `label`, `binary` (and optional `group`)
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
from mlnlib import MultiLayerNetwork
|
|
45
|
+
|
|
46
|
+
mln = MultiLayerNetwork(
|
|
47
|
+
load_from_library=True,
|
|
48
|
+
library_path="/path/to/library"
|
|
49
|
+
)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
2) Build from raw CSVs via a config
|
|
53
|
+
|
|
54
|
+
Uses `RawCSVtoMLN` behind the scenes (see `src/mlnlib/preparation.py` and `test/config.json` for an example schema).
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
from mlnlib import MultiLayerNetwork
|
|
58
|
+
|
|
59
|
+
mln = MultiLayerNetwork(
|
|
60
|
+
load_from_config=True,
|
|
61
|
+
config_path="config.json"
|
|
62
|
+
)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
3) Provide in-memory objects
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
from mlnlib import MultiLayerNetwork
|
|
69
|
+
from scipy.sparse import csr_matrix
|
|
70
|
+
import pandas as pd
|
|
71
|
+
|
|
72
|
+
# nodes must have columns: label, id (IDs are 0..N-1)
|
|
73
|
+
nodes = pd.DataFrame({
|
|
74
|
+
"label": ["a","b","c"],
|
|
75
|
+
"id": [0,1,2]
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
# layers dataframe: at least layer, label, binary
|
|
79
|
+
layers = pd.DataFrame({
|
|
80
|
+
"layer": [0,1],
|
|
81
|
+
"label": ["friendship","kinship"],
|
|
82
|
+
"binary": [1,2]
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
# A is NxN csr_matrix whose data encodes layers as sums of powers of two
|
|
86
|
+
A = csr_matrix((3,3), dtype="int64")
|
|
87
|
+
|
|
88
|
+
mln = MultiLayerNetwork(nodes=nodes, edges=A, layers=layers)
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Binary layer encoding
|
|
92
|
+
|
|
93
|
+
Each layer is assigned a power-of-two value: 1, 2, 4, 8, ... When an edge exists on multiple layers, the corresponding entry in `A` stores the sum, enabling fast bitwise checks. For example, if `A[i,j] = 7`, then layers 0, 1, and 2 are present because `7 = 1 + 2 + 4`. Checking for layer 1 is a cheap mask: `7 & 2 == 2`.
|
|
94
|
+
|
|
95
|
+
Binary encoding is stored in `self.layers`.
|
|
96
|
+
|
|
97
|
+
## Common tasks
|
|
98
|
+
|
|
99
|
+
- Convert node identifiers
|
|
100
|
+
- `mln.to_id("person_123")` or `mln.to_label(42)`
|
|
101
|
+
- Filter by nodes/layers/groups
|
|
102
|
+
- `mln.get_filtered_network(nodes_selected=[...], layers_selected=[...], groups_selected=[...], node_type="label", layer_type="label")`
|
|
103
|
+
- Per-layer adjacency
|
|
104
|
+
- `mln.get_layer_adjacency_matrix(layer="friendship", layer_type="label", store=True, dtype="int8")`
|
|
105
|
+
- Supra-adjacency matrix
|
|
106
|
+
- `mln.get_supra_adjacency_matrix(dtype="int8")`
|
|
107
|
+
- Edgelist export
|
|
108
|
+
- `mln.get_edgelist(edge_attribute="label")` # options: "binary", "layer", "label", "weight", or None
|
|
109
|
+
- Degrees and clustering
|
|
110
|
+
- `mln.get_degrees([...])`, `mln.get_clustering_coefficient([...])`
|
|
111
|
+
- Excess closure
|
|
112
|
+
- `mln.get_excess_closure(selected_nodes=[...], selected_layers=[...], layer_type="label")`
|
|
113
|
+
- Convert to graph libraries
|
|
114
|
+
- `mln.to_igraph(directed=True, edge_attributes=True, node_attributes=False, edge_attribute_type="binary")`
|
|
115
|
+
- `mln.to_networkx(directed=True, edge_attributes=True, node_attributes=False, edge_attribute_type="binary")`
|
|
116
|
+
|
|
117
|
+
## Saving and exporting
|
|
118
|
+
|
|
119
|
+
Save a prepared library for fast future loads:
|
|
120
|
+
|
|
121
|
+
```python
|
|
122
|
+
mln.save(path="/path/to/library", overwrite=False)
|
|
123
|
+
# creates edges.npz, nodes.csv.gz (or pickle), layers.csv
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Export individual pieces:
|
|
127
|
+
- Edges: `mln.export_edges("edges.npz" | "edges.csv" | "edges.csv.gz")`
|
|
128
|
+
- Nodes: `mln.export_nodes("nodes.csv" | "nodes.csv.gz")`
|
|
129
|
+
- Layers: `mln.export_layers("layers.csv")`
|
|
130
|
+
- GraphML: `mln.save_to_graphml("network.graphml", edge_attribute_type="binary")`
|
|
131
|
+
|
|
132
|
+
## Data structures
|
|
133
|
+
|
|
134
|
+
- Nodes (`mln.nodes`): DataFrame with at least `label` and `id`. You may add arbitrary node attributes. Preferred mapping helpers are `to_id()` and `to_label()`.
|
|
135
|
+
- Layers (`mln.layers`): DataFrame with columns:
|
|
136
|
+
- `layer`: arbitrary ID, can be of any type
|
|
137
|
+
- `label`: human-readable name
|
|
138
|
+
- `label_long` : longer description
|
|
139
|
+
- `binary`: power-of-two encoding
|
|
140
|
+
- `group` (optional): used for grouped aggregations
|
|
141
|
+
- Adjacency (`mln.A`): csr_matrix whose data contains the sum of layer powers-of-two. Use `mln.get_binary_adjacency()` to collapse to 0/1.
|
|
142
|
+
|
|
143
|
+
## API Reference
|
|
144
|
+
|
|
145
|
+
### Core Methods
|
|
146
|
+
|
|
147
|
+
#### Node ID mapping (preferred interface)
|
|
148
|
+
- `to_id(labels: Any | List[Any]) -> int | List[int]`
|
|
149
|
+
Convert node label(s) to integer ID(s).
|
|
150
|
+
|
|
151
|
+
- `to_label(ids: int | List[int]) -> Any | List[Any]`
|
|
152
|
+
Convert integer node ID(s) to label(s).
|
|
153
|
+
|
|
154
|
+
#### Filtering and subgraphs
|
|
155
|
+
- `get_filtered_network(nodes_selected=None, layers_selected=[], groups_selected=[], node_type="label", layer_type="label", keep_node_alignment=False) -> MultiLayerNetwork`
|
|
156
|
+
Return a filtered subnetwork based on node and/or layer selection.
|
|
157
|
+
|
|
158
|
+
- `get_egonetwork(ego_label, depth=1, return_list=False, ignore_limit=False) -> MultiLayerNetwork | List`
|
|
159
|
+
Extract ego network centered on a node at specified depth.
|
|
160
|
+
|
|
161
|
+
#### Layer-specific adjacency
|
|
162
|
+
- `get_layer_adjacency_matrix(layer, layer_type="layer", store=False, dtype="int64") -> csr_matrix`
|
|
163
|
+
Create binary adjacency matrix for a single layer or group.
|
|
164
|
+
|
|
165
|
+
- `get_binary_adjacency(dtype="int64") -> csr_matrix`
|
|
166
|
+
Convert multilayer adjacency to binary (0/1) format, collapsing all layer information.
|
|
167
|
+
|
|
168
|
+
- `get_supra_adjacency_matrix(dtype="int64") -> csr_matrix`
|
|
169
|
+
Build supra-adjacency matrix (L*N × L*N) with inter-layer couplings.
|
|
170
|
+
|
|
171
|
+
- `get_grouped_mln() -> MultiLayerNetwork`
|
|
172
|
+
Return network with layers aggregated by group (requires `group` column in `layers`).
|
|
173
|
+
|
|
174
|
+
- `clear_layer_adjacency_matrices()`, `clear_group_adjacency_matrices()`, `clear_all_adjacency_matrices()`
|
|
175
|
+
Clear cached adjacency matrices.
|
|
176
|
+
|
|
177
|
+
#### Edge lists and conversions
|
|
178
|
+
- `get_edgelist(edge_attribute="binary") -> DataFrame`
|
|
179
|
+
Convert sparse adjacency matrix to edgelist. Options: "binary", "layer", "label", "weight", or None.
|
|
180
|
+
|
|
181
|
+
- `convert_layer_representation(layer, input_type="layer", output_type="binary") -> Any | List[Any]`
|
|
182
|
+
Convert between layer representations: "layer", "label", "binary".
|
|
183
|
+
|
|
184
|
+
- `convert_layer_binary_to_list(num, output_type="layer") -> List`
|
|
185
|
+
Decode binary integer to list of active layers.
|
|
186
|
+
|
|
187
|
+
#### Graph library exports
|
|
188
|
+
- `to_igraph(directed=True, edge_attributes=True, node_attributes=False, replace_igraph=False, edge_attribute_type="binary") -> ig.Graph`
|
|
189
|
+
Create igraph object. Options for `edge_attribute_type`: "binary", "weight".
|
|
190
|
+
|
|
191
|
+
- `to_networkx(directed=True, edge_attributes=True, node_attributes=False, edge_attribute_type="binary", layer_type="layer", ignore_limit=False) -> nx.Graph | nx.DiGraph | None`
|
|
192
|
+
Create NetworkX object (limited by `nx_node_limit` unless `ignore_limit=True`).
|
|
193
|
+
|
|
194
|
+
- `save_to_graphml(file_name, directed=True, edge_attributes=True, node_attributes=False, overwrite=False, edge_attribute_type="binary")`
|
|
195
|
+
Save as GraphML for external tools (Gephi, etc.).
|
|
196
|
+
|
|
197
|
+
#### Metrics and analysis
|
|
198
|
+
- `get_degrees(selected_nodes=[]) -> Dict[Any, int]`
|
|
199
|
+
Calculate degree for selected nodes across all layers.
|
|
200
|
+
|
|
201
|
+
- `get_clustering_coefficient(selected_nodes=[], batchsize=100000) -> Dict[Any, float]`
|
|
202
|
+
Compute clustering coefficient for selected nodes.
|
|
203
|
+
|
|
204
|
+
- `get_excess_closure(selected_nodes=[], node_type="label", selected_layers=[], layer_type="layer", batchsize=100000) -> Dict[str, Dict]`
|
|
205
|
+
Calculate excess closure metric (returns both clustering coefficient and excess closure).
|
|
206
|
+
|
|
207
|
+
#### Node operations
|
|
208
|
+
- `add_nodes(nodes_df: pd.DataFrame)`
|
|
209
|
+
Add new nodes without connections (expands adjacency matrix).
|
|
210
|
+
|
|
211
|
+
- `remove_nodes(nodes: List[Any])`
|
|
212
|
+
Remove nodes and their edges; reindexes remaining nodes.
|
|
213
|
+
|
|
214
|
+
#### Aggregation
|
|
215
|
+
- `create_affiliation_matrix(key, affil_edgelist: List[Tuple])`
|
|
216
|
+
Create bipartite affiliation matrix for node-to-entity relationships.
|
|
217
|
+
|
|
218
|
+
- `get_aggregated_network(aggregation_column=None, keep_layers=False, convert_A="none") -> MultiLayerNetwork`
|
|
219
|
+
Aggregate network by node attribute (e.g., region, age group).
|
|
220
|
+
|
|
221
|
+
#### Persistence
|
|
222
|
+
- `save(path="", overwrite=False, **kwargs)`
|
|
223
|
+
Save MLN library (edges.npz, nodes.csv.gz, layers.csv, optional codebook).
|
|
224
|
+
|
|
225
|
+
- `export_edges(file_name)`, `export_nodes(file_name)`, `export_layers(file_name)`, `export_codebook(file_name)`
|
|
226
|
+
Export individual components.
|
|
227
|
+
|
|
228
|
+
- `load(path) -> Tuple[DataFrame, csr_matrix, DataFrame]`
|
|
229
|
+
Load MLN components from disk.
|
|
230
|
+
|
|
231
|
+
#### Utilities
|
|
232
|
+
- `report_time(message="", init=False)`
|
|
233
|
+
Helper for timing measurements (requires `verbose=True`).
|
|
234
|
+
|
|
235
|
+
### RawCSVtoMLN (preparation.py)
|
|
236
|
+
|
|
237
|
+
Utility class for converting raw CSV node/edge files into MLN library format.
|
|
238
|
+
|
|
239
|
+
- `__init__(node_conf, edge_conf, layer_conf, output_folder, **kwargs)`
|
|
240
|
+
Configure input/output paths and column mappings.
|
|
241
|
+
|
|
242
|
+
- `init_all()`
|
|
243
|
+
Run full pipeline: init_layers() → init_nodes() → init_edges() → read_all_edges() → save_all() (if configured).
|
|
244
|
+
|
|
245
|
+
- `save_all(overwrite=False)`
|
|
246
|
+
Save nodes.csv.gz, edges.npz, and layers.csv to output folder.
|
|
247
|
+
|
|
248
|
+
See `test/config.json` for configuration schema.
|
|
249
|
+
|
|
250
|
+
## Notes
|
|
251
|
+
|
|
252
|
+
- NetworkX conversion limits very large graphs (`nx_node_limit` in code). Use igraph for larger networks.
|
|
253
|
+
- The package supports pandas by default and optional polars interop for CSV loading and some operations.
|
|
254
|
+
- The network can be directed; layer information is encoded in the adjacency matrix when `adjacency_element="binary"`.
|
|
255
|
+
|
|
256
|
+
## Citation
|
|
257
|
+
|
|
258
|
+
If you use this library in academic work, please cite this repository and acknowledge the PLANET-NL project (https://planetnl.org). Citation metadata is available in `CITATION.cff`, and GitHub shows it under "Cite this repository" in the sidebar.
|
|
259
|
+
|
|
260
|
+
A software paper describing `mlnlib` is in preparation (see [`paper/`](paper/)). We will update the citation information once it is published.
|
|
@@ -1,19 +1,23 @@
|
|
|
1
1
|
[build-system]
|
|
2
|
-
requires = ["setuptools>=
|
|
2
|
+
requires = ["setuptools>=77.0"]
|
|
3
3
|
build-backend = "setuptools.build_meta"
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "mlnlib"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.3.0"
|
|
8
8
|
authors = [
|
|
9
|
-
{ name="Eszter Bokányi", email="e.bokanyi@
|
|
9
|
+
{ name="Eszter Bokányi", email="e.bokanyi@liacs.leidenuniv.nl" },
|
|
10
|
+
{ name="Rachel G. de Jong" },
|
|
11
|
+
{ name="Yuliia Kazmina" },
|
|
12
|
+
{ name="Frank W. Takes" },
|
|
10
13
|
]
|
|
11
14
|
description = "Memory-efficient library for loading and working with large unweighted multilayer networks."
|
|
12
15
|
readme = "README.md"
|
|
16
|
+
license = "MIT"
|
|
17
|
+
license-files = ["LICENSE.md"]
|
|
13
18
|
requires-python = ">=3.8"
|
|
14
19
|
classifiers = [
|
|
15
20
|
"Programming Language :: Python :: 3",
|
|
16
|
-
"License :: OSI Approved :: MIT License",
|
|
17
21
|
"Operating System :: OS Independent",
|
|
18
22
|
]
|
|
19
23
|
dependencies = [
|
|
@@ -26,5 +30,5 @@ dependencies = [
|
|
|
26
30
|
]
|
|
27
31
|
|
|
28
32
|
[project.urls]
|
|
29
|
-
Homepage = "https://github.com/
|
|
30
|
-
Issues = "https://github.com/
|
|
33
|
+
Homepage = "https://github.com/planet-nl/mlnlib"
|
|
34
|
+
Issues = "https://github.com/planet-nl/mlnlib/issues"
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""
|
|
2
|
+
mlnlib - Memory-efficient Python library for multilayer networks
|
|
3
|
+
|
|
4
|
+
This package provides tools for working with large multilayer networks
|
|
5
|
+
efficiently on computers with limited resources.
|
|
6
|
+
|
|
7
|
+
Main Classes:
|
|
8
|
+
-------------
|
|
9
|
+
- MultiLayerNetwork: Main class for multilayer network analysis
|
|
10
|
+
- RawCSVtoMLN: Utility class for converting raw CSV files to MLN format
|
|
11
|
+
|
|
12
|
+
Authors: Eszter Bokányi, Rachel G. de Jong, Yuliia Kazmina, Frank W. Takes
|
|
13
|
+
Contact: e.bokanyi@liacs.leidenuniv.nl
|
|
14
|
+
License: MIT
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from .mln import MultiLayerNetwork
|
|
18
|
+
from .preparation import RawCSVtoMLN
|
|
19
|
+
|
|
20
|
+
__version__ = "0.3.0"
|
|
21
|
+
__all__ = ["MultiLayerNetwork", "RawCSVtoMLN"]
|