mlnlib 0.2.37__tar.gz → 0.2.39__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.
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: mlnlib
3
- Version: 0.2.37
3
+ Version: 0.2.39
4
4
  Summary: Memory-efficient library for loading and working with large unweighted multilayer networks.
5
5
  Author-email: Eszter Bokányi <e.bokanyi@uva.nl>
6
6
  Project-URL: Homepage, https://github.com/popnet-io/popnet_mln
@@ -17,6 +17,7 @@ Requires-Dist: numpy>=1.20.3
17
17
  Requires-Dist: python-igraph>=0.9.9
18
18
  Requires-Dist: networkx>=3.0
19
19
  Requires-Dist: scipy>=1.10.1
20
+ Dynamic: license-file
20
21
 
21
22
  # MLN
22
23
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "mlnlib"
7
- version = "0.2.37"
7
+ version = "0.2.39"
8
8
  authors = [
9
9
  { name="Eszter Bokányi", email="e.bokanyi@uva.nl" },
10
10
  ]
@@ -543,7 +543,6 @@ class MultiLayerNetwork:
543
543
  idx[nodes_selected] = True
544
544
 
545
545
  if keep_node_alignment:
546
- print("Getting indices...")
547
546
  i,j = self.A.nonzero()
548
547
  test = np.isin(i,nodes_selected) & np.isin(j,nodes_selected)
549
548
  selected_i = i[test]
@@ -552,25 +551,24 @@ class MultiLayerNetwork:
552
551
  del j
553
552
  selected_data = self.A.data[test]
554
553
  del test
555
- print("Done.")
556
- print("Constructing larger matrix...")
557
554
  A_selected = csr_matrix((selected_data,(selected_i,selected_j)),shape=(self.N,self.N),dtype=self.A.dtype)
558
- print("Done.")
559
- print("Constructing nodes...")
555
+ # keep full node dataframe
560
556
  if self.use_polars:
561
557
  nodes_selected = self.nodes.clone()
562
558
  else:
563
559
  nodes_selected = self.nodes.copy()
564
- nodes_selected["active"] = idx
565
- print("Done.")
560
+ # filter down active nodes
561
+ nodes_selected["active"] &= idx
566
562
  else:
567
563
  # slicing the adjacency matrix
568
564
  A_selected = self.A[idx,:][:,idx]
569
565
  # slicing the attribute table
570
566
  if self.use_polars:
571
567
  nodes_selected = self.nodes.filter(idx)
568
+ nodes_selected["id"] = np.arange(sum(idx))
572
569
  else:
573
570
  nodes_selected = self.nodes.iloc[nodes_selected].reset_index(drop=True)
571
+ nodes_selected["id"] = nodes_selected.index
574
572
  else:
575
573
  A_selected = deepcopy(self.A)
576
574
  nodes_selected = self.nodes
@@ -1673,6 +1671,112 @@ class MultiLayerNetwork:
1673
1671
  self.N = mask.sum()
1674
1672
  self.A = self.A[mask,:][:,mask]
1675
1673
 
1674
+ def get_aggregated_network(self, aggregation_column=None, keep_layers = False, convert_A = "none"):
1675
+ """
1676
+ Return an aggregated network over a certain column in self.nodes.
1677
+
1678
+ It looks at unique values in the column, and aggregates the edges for
1679
+ each unique value. The resulting network has as many nodes as there
1680
+ are unique values in the column. The edges are aggregated by counting all
1681
+ edges from all layers that go between the groups.
1682
+
1683
+ The resulting network is a MultiLayerNetwork object, but instead of the binary
1684
+ adjacency matrix, it has a weighted adjacency matrix, where the weights are
1685
+ the edge counts between the groups.
1686
+
1687
+ Parameters:
1688
+ -------------
1689
+ aggregation_column : string, default None
1690
+ the column in self.nodes based on which the edges should be aggregated
1691
+ -------------
1692
+ """
1693
+
1694
+ if not aggregation_column in self.nodes.columns:
1695
+ raise ValueError(f"Column {aggregation_column} not found in self.nodes. Possible candidates are: ", self.nodes.columns)
1696
+
1697
+
1698
+ if self.use_polars:
1699
+ mask = self.nodes["active"] & self.nodes[aggregation_column].is_not_null()
1700
+ affil_edgelist = list(zip(self.nodes["label"].filter(mask), self.nodes[aggregation_column].filter(mask)))
1701
+ else:
1702
+ mask = self.nodes["active"] & (~pd.isnull(self.nodes[aggregation_column]))
1703
+ affil_edgelist = list(zip(self.nodes["label"][mask], self.nodes[aggregation_column][mask]))
1704
+
1705
+ self.create_affiliation_matrix(aggregation_column,affil_edgelist)
1706
+
1707
+ if convert_A == "boolean":
1708
+ G = self.affiliation_matrix[aggregation_column]["A"].T * self.A.sign() * self.affiliation_matrix[aggregation_column]["A"]
1709
+ elif convert_A == "multiplexity":
1710
+ A = deepcopy(self.A)
1711
+ # create lookup table for mapping binary values to number of layers / multiplexity
1712
+ lookup = {}
1713
+ for num in np.unique(A.data):
1714
+ lookup[num] = bin(num).count("1")
1715
+ # apply lookup table
1716
+ A.data = np.array([lookup[x] for x in A.data])
1717
+ G = self.affiliation_matrix[aggregation_column]["A"].T * A * self.affiliation_matrix[aggregation_column]["A"]
1718
+ elif convert_A == "none":
1719
+ G = self.affiliation_matrix[aggregation_column]["A"].T * self.A * self.affiliation_matrix[aggregation_column]["A"]
1720
+ else:
1721
+ ValueError("Invalid value for convert_A, please choose from none, booelan, or multiplexity.")
1722
+ uniques = [self.affiliation_matrix[aggregation_column]["column_id_to_label"][i] for i in range(self.affiliation_matrix[aggregation_column]["M"])]
1723
+ if 'weight' not in self.nodes.columns:
1724
+ # count occurrences in each aggregation group
1725
+ grp_counts = self.affiliation_matrix[aggregation_column]["A"].sum(axis=0).tolist()[0]
1726
+ else:
1727
+ # sum up previous weights
1728
+ grp_counts = self.nodes[mask].groupby(aggregation_column)['weight'].sum().loc[uniques]
1729
+
1730
+ if self.use_polars:
1731
+ nodes_selected = pl.DataFrame({
1732
+ 'label' : uniques,
1733
+ 'weight' : grp_counts,
1734
+ "id" : [i for i in range(self.affiliation_matrix[aggregation_column]["M"])]
1735
+ }
1736
+ )
1737
+ else:
1738
+ nodes_selected = pd.DataFrame(data={
1739
+ 'label' : uniques,
1740
+ 'weight' : grp_counts,
1741
+ "id" : [i for i in range(self.affiliation_matrix[aggregation_column]["M"])]
1742
+ }
1743
+ )
1744
+
1745
+ # add data related to particular aggregation level
1746
+ if self.use_polars:
1747
+ aggregation_nodes = self.nodes.select([c for c in self.nodes.columns if aggregation_column.split('_')[0] in c]).groupby([aggregation_column]).first()
1748
+ nodes_selected = nodes_selected.join(aggregation_nodes, left_on=['label'], right_on=[aggregation_column], how='left')
1749
+ else:
1750
+ aggregation_nodes = self.nodes.groupby([aggregation_column]).head(1)[[c for c in self.nodes.columns if aggregation_column.split('_')[0] in c]]
1751
+ nodes_selected = nodes_selected.merge(aggregation_nodes, left_on=['label'], right_on=[aggregation_column], how='left')
1752
+
1753
+ f = MultiLayerNetwork(
1754
+ edges = G.tocsr(),
1755
+ nodes = nodes_selected,
1756
+ **self._to_pass
1757
+ )
1758
+
1759
+ # this is only providing raw counts for all selected layers
1760
+ # TODO: it would be more elegant to keep layers!
1761
+ selection_layers = pd.DataFrame(data={'layer' : 1, 'label' : 'count', 'label_long' : 'count_'+convert_A, 'layer' : 'count', 'binary' : 1}, index=[0])
1762
+ selection_layers_dict = {
1763
+ 'binary_to_layer': {1 : 1},
1764
+ 'binary_to_layer' : {1 : 'count'},
1765
+ 'layer_to_layer' : {1 : 'count'},
1766
+ 'layer_to_binary' : {1 : 1},
1767
+ 'layer_to_binary' : {'count' : 1},
1768
+ 'layer_to_layer' : {'count' : 1}
1769
+ }
1770
+
1771
+ f.layers = selection_layers
1772
+ f._layer_conversion_dict = selection_layers_dict
1773
+ # The nodes have changed, therefore new mappings for to_nid and to_label are passed.
1774
+ f._map_label_to_id = dict(zip(f.nodes["label"],f.nodes["id"]))
1775
+ f._map_id_to_label = dict(zip(f.nodes["id"],f.nodes["label"]))
1776
+ f.adjacency_element = "weight"
1777
+
1778
+ return f
1779
+
1676
1780
 
1677
1781
 
1678
1782
 
@@ -1681,4 +1785,4 @@ class MultiLayerNetwork:
1681
1785
 
1682
1786
 
1683
1787
 
1684
-
1788
+
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: mlnlib
3
- Version: 0.2.37
3
+ Version: 0.2.39
4
4
  Summary: Memory-efficient library for loading and working with large unweighted multilayer networks.
5
5
  Author-email: Eszter Bokányi <e.bokanyi@uva.nl>
6
6
  Project-URL: Homepage, https://github.com/popnet-io/popnet_mln
@@ -17,6 +17,7 @@ Requires-Dist: numpy>=1.20.3
17
17
  Requires-Dist: python-igraph>=0.9.9
18
18
  Requires-Dist: networkx>=3.0
19
19
  Requires-Dist: scipy>=1.10.1
20
+ Dynamic: license-file
20
21
 
21
22
  # MLN
22
23
 
File without changes
File without changes
File without changes