d3graph 2.9.0__tar.gz → 2.9.2__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
1
  Metadata-Version: 2.4
2
2
  Name: d3graph
3
- Version: 2.9.0
3
+ Version: 2.9.2
4
4
  Summary: Python package to create interactive network based on d3js.
5
5
  Author-email: Erdogan Taskesen <erdogant@gmail.com>
6
6
  License-Expression: BSD-3-Clause
@@ -17,7 +17,7 @@ from d3graph.d3graph import (
17
17
 
18
18
  __author__ = 'Erdogan Tasksen'
19
19
  __email__ = 'erdogant@gmail.com'
20
- __version__ = '2.9.0'
20
+ __version__ = '2.9.2'
21
21
 
22
22
  # Setup root logger
23
23
  _logger = logging.getLogger('d3graph')
@@ -253,6 +253,7 @@ class d3graph:
253
253
  edge_style=0,
254
254
  edge_color: (str, list) = '#808080',
255
255
  edge_opacity: (float, str, list) = 'weight',
256
+ min_weight: float = 1.0,
256
257
  scaler: str = 'zscore',
257
258
  directed: bool = False,
258
259
  marker_start=None,
@@ -287,6 +288,8 @@ class d3graph:
287
288
  edge_opacity : (float, str, list), (default: 1.0)
288
289
  * 0.8 : Opacity of the edges [0-1] where 0=transparent and 1=fully opaque.
289
290
  * 'weight' : Set opacity based on the weight of the edge and the scaler
291
+ min_weight : float
292
+ edges are kept with >= weight.
290
293
  scaler : str, (default: 'zscore')
291
294
  Scale the edge-width using the following scaler:
292
295
  * 'zscore' : Scale values to Z-scores.
@@ -346,13 +349,14 @@ class d3graph:
346
349
  self.config['label'] = label
347
350
  self.config['label_color'] = label_color
348
351
  self.config['label_fontsize'] = label_fontsize
352
+ self.config['min_weight'] = min_weight
349
353
 
350
354
  if not hasattr(self, 'adjmat'):
351
355
  logger.error('adjmat is missing. Initialize first with d3 = d3graph(adjmat)')
352
356
  return
353
357
 
354
358
  if (not directed) and (marker_end is not None) or (marker_start is not None):
355
- logger.info('Set directed=True to see the markers!')
359
+ logger.info('Markers not shown. Set directed=True to see the markers.')
356
360
 
357
361
  # Get node color properties which is required to set the edge color
358
362
  node_color_map = None
@@ -364,9 +368,10 @@ class d3graph:
364
368
  self.config['edge_color'] = '#808080'
365
369
 
366
370
  # Set the edge properties
371
+ logger.info('Set the edge properties')
367
372
  self.edge_properties, self.adjmat = adjmat2dict(
368
373
  self.adjmat,
369
- filter_weight=0,
374
+ min_weight=self.config['min_weight'],
370
375
  minmax=self.config['minmax'],
371
376
  minmax_distance=self.config['minmax_distance'],
372
377
  scaler=self.config['edge_scaler'],
@@ -385,7 +390,7 @@ class d3graph:
385
390
  node_color_map=node_color_map,
386
391
  )
387
392
 
388
- logger.debug('Number of edges: %.0d', len(self.edge_properties.keys()))
393
+ # logger.info(f'Number of edges: {len(self.edge_properties.keys())}')
389
394
 
390
395
  def set_node_properties(self,
391
396
  label=None,
@@ -697,6 +702,7 @@ class d3graph:
697
702
 
698
703
  def graph(self,
699
704
  adjmat,
705
+ min_weight: float = 1.0,
700
706
  color: str = 'cluster',
701
707
  opacity: str = 'degree',
702
708
  size='degree',
@@ -710,7 +716,9 @@ class d3graph:
710
716
  Parameters
711
717
  ----------
712
718
  adjmat : pd.DataFrame()
713
- Adjacency matrix (symmetric). Values > 0 are edges.
719
+ Adjacency matrix (symmetric and Values > 0 are edges).
720
+ min_weight : float
721
+ edges are kept with >= weight.
714
722
  color : list of strings (default: 'cluster')
715
723
  Coloring of the nodes.
716
724
  * 'cluster' or None : Colours are based on the community distance clusters.
@@ -764,7 +772,7 @@ class d3graph:
764
772
  # Checks
765
773
  self.adjmat = data_checks(adjmat.copy())
766
774
  # Set default edge properties
767
- self.set_edge_properties(scaler=scaler)
775
+ self.set_edge_properties(scaler=scaler, min_weight=min_weight)
768
776
  # Set default node properties
769
777
  self.set_node_properties(color=color, opacity=opacity, size=size, scaler=scaler, cmap=cmap)
770
778
 
@@ -1059,7 +1067,7 @@ def json_create(G: nx.Graph) -> str:
1059
1067
 
1060
1068
  # %% Convert adjacency matrix to vector
1061
1069
  def adjmat2dict(adjmat: pd.DataFrame,
1062
- filter_weight: float = 0.0,
1070
+ min_weight: float = 1.0,
1063
1071
  scaler: str = 'zscore',
1064
1072
  marker_start=None,
1065
1073
  marker_end='arrow',
@@ -1083,8 +1091,8 @@ def adjmat2dict(adjmat: pd.DataFrame,
1083
1091
  ----------
1084
1092
  adjmat : pd.DataFrame()
1085
1093
  Adjacency matrix.
1086
- filter_weight : float
1087
- edges are returned with a minimum weight.
1094
+ min_weight : float
1095
+ edges are kept with >= weight.
1088
1096
  scaler : str, (default: 'zscore')
1089
1097
  Scale the edge-width using the following scaler:
1090
1098
  'zscore' : Scale values to Z-scores.
@@ -1153,21 +1161,23 @@ def adjmat2dict(adjmat: pd.DataFrame,
1153
1161
  'tooltip': Text that is shown when hovering over the edge.
1154
1162
 
1155
1163
  """
1156
- # Convert adjacency matrix into vector
1157
- df = adjmat.stack().reset_index()
1158
- # Set columns
1159
- df.columns = ['source', 'target', 'weight']
1160
- # Combine source-target values with weights
1161
- df = create_unique_dataframe(df)
1162
- # Remove self loops and no-connected edges
1163
- Iloc = df['source'] != df['target']
1164
- # Keep only edges with a minimum edge strength
1165
- if filter_weight is not None:
1166
- logger.info("Keep only edges with weight>%g" % filter_weight)
1167
- Iloc2 = df['weight'] > filter_weight
1168
- Iloc = Iloc & Iloc2
1169
- df = df.loc[Iloc, :]
1170
- df.reset_index(drop=True, inplace=True)
1164
+ # # Convert adjacency matrix into vector
1165
+ # df = adjmat.stack().reset_index()
1166
+ # # Set columns
1167
+ # df.columns = ['source', 'target', 'weight']
1168
+ # # Combine source-target values with weights
1169
+ # df = create_unique_dataframe(df)
1170
+ # # Remove self loops and no-connected edges
1171
+ # Iloc = df['source'] != df['target']
1172
+ # # Keep only edges with a minimum edge strength
1173
+ # if min_weight is not None:
1174
+ # logger.info(f"Keep only edges with weight>{min_weight}")
1175
+ # Iloc2 = df['weight'] > min_weight
1176
+ # Iloc = Iloc & Iloc2
1177
+ # df = df.loc[Iloc, :]
1178
+ # df.reset_index(drop=True, inplace=True)
1179
+
1180
+ df = adjmat2vec(adjmat, min_weight=min_weight)
1171
1181
 
1172
1182
  # Scale the weights for visualization purposes
1173
1183
  if minmax_distance is not None:
@@ -1225,6 +1235,7 @@ def adjmat2dict(adjmat: pd.DataFrame,
1225
1235
  source_target = list(zip(df['source'], df['target']))
1226
1236
  if len(source_target)==0:
1227
1237
  raise Exception('There are no links in the input data set that have unique source-target value with weight > 0')
1238
+
1228
1239
  # Return
1229
1240
  d = {edge: {'weight': df['weight'].iloc[i],
1230
1241
  'weight_scaled': df['weight_scaled'].iloc[i],
@@ -1249,6 +1260,36 @@ def adjmat2dict(adjmat: pd.DataFrame,
1249
1260
 
1250
1261
 
1251
1262
  # %% Create unique dataframe and update weights
1263
+ # def create_unique_dataframe(X, logger=None):
1264
+ # """Combine source-target into adjacency matrix with updated weights.
1265
+
1266
+ # Parameters
1267
+ # ----------
1268
+ # X : DataFrame
1269
+ # Data frame containing the columns [source, target, weight].
1270
+ # logger : Object, optional
1271
+ # Logger object. The default is None.
1272
+
1273
+ # Returns
1274
+ # -------
1275
+ # X : pd.DataFrame
1276
+ # Unique adjacency matrix containing with index as source and columns as target labels. Weights are in the matrix.
1277
+
1278
+ # References
1279
+ # ----------
1280
+ # * This function is similar to that of d3blocks.
1281
+
1282
+ # """
1283
+ # # Check whether labels are unique
1284
+ # if isinstance(X, pd.DataFrame):
1285
+ # Iloc = ismember(X.columns, ['source', 'target', 'weight'])[0]
1286
+ # X = X.loc[:, Iloc]
1287
+ # if 'weight' in X.columns: X['weight'] = X['weight'].astype(float)
1288
+ # # Groupby values and sum the weights
1289
+ # X = X.groupby(by=['source', 'target']).sum()
1290
+ # X.reset_index(drop=False, inplace=True)
1291
+ # return X
1292
+
1252
1293
  def create_unique_dataframe(X, logger=None):
1253
1294
  """Combine source-target into adjacency matrix with updated weights.
1254
1295
 
@@ -1269,15 +1310,12 @@ def create_unique_dataframe(X, logger=None):
1269
1310
  * This function is similar to that of d3blocks.
1270
1311
 
1271
1312
  """
1272
- # Check whether labels are unique
1273
- if isinstance(X, pd.DataFrame):
1274
- Iloc = ismember(X.columns, ['source', 'target', 'weight'])[0]
1275
- X = X.loc[:, Iloc]
1276
- if 'weight' in X.columns: X['weight'] = X['weight'].astype(float)
1277
- # Groupby values and sum the weights
1278
- X = X.groupby(by=['source', 'target']).sum()
1279
- X.reset_index(drop=False, inplace=True)
1280
- return X
1313
+ X = X[['source', 'target', 'weight']]
1314
+ return (
1315
+ X.groupby(['source', 'target'], sort=False, observed=True)
1316
+ .agg({'weight': 'sum'})
1317
+ .reset_index()
1318
+ )
1281
1319
 
1282
1320
 
1283
1321
  # %% Convert dict with edges to graph (G) (also works with lower versions of networkx)
@@ -1580,61 +1618,80 @@ def vec2adjmat(source, target, weight=None, symmetric: bool = True, aggfunc='sum
1580
1618
 
1581
1619
 
1582
1620
  # %% Convert adjacency matrix to vector
1621
+ # def adjmat2vec(adjmat, min_weight: float = 1.0) -> pd.DataFrame:
1622
+ # """Convert adjacency matrix into vector with source and target.
1623
+
1624
+ # Parameters
1625
+ # ----------
1626
+ # adjmat : pd.DataFrame()
1627
+ # Adjacency matrix.
1628
+
1629
+ # min_weight : float
1630
+ # edges are returned with a minimum weight.
1631
+
1632
+ # Returns
1633
+ # -------
1634
+ # pd.DataFrame()
1635
+ # nodes that are connected based on source and target
1636
+
1637
+ # Examples
1638
+ # --------
1639
+ # >>> source = ['Cloudy', 'Cloudy', 'Sprinkler', 'Rain']
1640
+ # >>> target = ['Sprinkler', 'Rain', 'Wet_Grass', 'Wet_Grass']
1641
+ # >>> adjmat = vec2adjmat(source, target, weight=[1, 2, 1, 3])
1642
+ # >>> vector = adjmat2vec(adjmat)
1643
+
1644
+ # """
1645
+ # # Convert adjacency matrix into vector
1646
+ # logger.info('Converting adjacency matrix into source-target..')
1647
+ # adjmat = adjmat.stack().reset_index()
1648
+ # # Set columns
1649
+ # adjmat.columns = ['source', 'target', 'weight']
1650
+ # # Remove self loops and no-connected edges
1651
+ # Iloc1 = adjmat['source'] != adjmat['target']
1652
+ # Iloc2 = adjmat['weight'] >= min_weight
1653
+ # Iloc = Iloc1 & Iloc2
1654
+ # # Take only connected nodes
1655
+ # adjmat = adjmat.loc[Iloc, :]
1656
+ # adjmat.reset_index(drop=True, inplace=True)
1657
+ # return adjmat
1658
+
1659
+
1583
1660
  def adjmat2vec(adjmat, min_weight: float = 1.0) -> pd.DataFrame:
1584
- """Convert adjacency matrix into vector with source and target.
1661
+ """
1662
+ Fast conversion of adjacency matrix → edge list.
1663
+ Removes self-loops and filters by min_weight.
1664
+ """
1665
+ weights = adjmat.to_numpy(copy=False)
1585
1666
 
1586
- Parameters
1587
- ----------
1588
- adjmat : pd.DataFrame()
1589
- Adjacency matrix.
1667
+ # Mask of valid edges (stack() drops NaN, so we mimic that)
1668
+ mask = ~np.isnan(weights)
1590
1669
 
1591
- min_weight : float
1592
- edges are returned with a minimum weight.
1670
+ # Extract coordinates of non-NaN entries
1671
+ row_idx, col_idx = np.nonzero(mask)
1593
1672
 
1594
- Returns
1595
- -------
1596
- pd.DataFrame()
1597
- nodes that are connected based on source and target
1673
+ index = adjmat.index.to_numpy()
1674
+ columns = adjmat.columns.to_numpy()
1598
1675
 
1599
- Examples
1600
- --------
1601
- >>> source = ['Cloudy', 'Cloudy', 'Sprinkler', 'Rain']
1602
- >>> target = ['Sprinkler', 'Rain', 'Wet_Grass', 'Wet_Grass']
1603
- >>> adjmat = vec2adjmat(source, target, weight=[1, 2, 1, 3])
1604
- >>> vector = adjmat2vec(adjmat)
1676
+ # Build edge list
1677
+ df = pd.DataFrame({
1678
+ 'source': index[row_idx],
1679
+ 'target': columns[col_idx],
1680
+ 'weight': weights[row_idx, col_idx],
1681
+ })
1605
1682
 
1606
- """
1607
- # Convert adjacency matrix into vector
1608
- logger.info('Converting adjacency matrix into source-target..')
1609
- adjmat = adjmat.stack().reset_index()
1610
- # Set columns
1611
- adjmat.columns = ['source', 'target', 'weight']
1612
- # Remove self loops and no-connected edges
1613
- Iloc1 = adjmat['source'] != adjmat['target']
1614
- Iloc2 = adjmat['weight'] >= min_weight
1615
- Iloc = Iloc1 & Iloc2
1616
- # Take only connected nodes
1617
- adjmat = adjmat.loc[Iloc, :]
1618
- adjmat.reset_index(drop=True, inplace=True)
1619
- return adjmat
1683
+ # Remove self-loops
1684
+ df = df[df['source'] != df['target']]
1620
1685
 
1686
+ # Apply minimum weight filter
1687
+ if min_weight is not None:
1688
+ df = df[df['weight'] >= min_weight]
1689
+
1690
+ df.reset_index(drop=True, inplace=True)
1691
+ return df
1621
1692
 
1622
- # def _check_hex_color(color, n=None, cmap='Set1'):
1623
- # if isinstance(color, str) and len(color) != 7:
1624
- # logger.warning('Input parameter [color] has wrong format. Must be like color="#000000" <auto-fixing>')
1625
- # return get_hex_color(color, cmap=cmap)[0]
1626
- # if isinstance(color, (list, np.ndarray, pd.Series, pd.Series, StringArray)) and len(color) == 0:
1627
- # logger.warning('Input parameter [color] has wrong format and length. Must be like: color=["#000000", "...", "#000000"] <auto-fixing>')
1628
- # return get_hex_color(color, cmap=cmap)[0]
1629
- # if isinstance(color, (list, np.ndarray, pd.Series, pd.Series, StringArray)) and (not np.all(list(map(lambda x: len(x) == 7, color)))):
1630
- # logger.warning('[color] contains incorrect hex-colors. Hex must be of length 7: ["#000000", "#000000", etc] <auto-fixing>')
1631
- # return get_hex_color(color, cmap=cmap)[0]
1632
- # if (n is not None) and isinstance(color, (list, np.ndarray, pd.Series, pd.Series, StringArray)) and len(color) != n:
1633
- # logger.warning(f'Input parameter [color] has wrong length. Must be of length: {str(n)} <auto-fixing>')
1634
- # return get_hex_color(color, cmap=cmap)[0]
1635
- # # Return original input
1636
- # return color
1637
1693
 
1694
+ # %%
1638
1695
  def _check_hex_color(color, n=None, cmap='Set1'):
1639
1696
  seq_types = (list, np.ndarray, pd.Series, StringArray)
1640
1697
 
@@ -1,17 +1,25 @@
1
1
  # %%
2
2
  from d3graph import d3graph, vec2adjmat, import_example
3
3
  import pandas as pd
4
+ import time
5
+
4
6
 
5
7
  df = pd.read_csv('https://github.com/d3blocks/d3blocks/files/11995798/Df.csv', sep=',', index_col=False)
6
8
  del df['Unnamed: 0']
7
9
  df = df[0:5000]
8
10
  adjmat = vec2adjmat(source=df['source'], target=df['target'], weight=df['weight'])
9
11
 
10
- # sticky=False — classic spring-back behaviour
11
12
  d3 = d3graph()
13
+
14
+ start = time.perf_counter()
12
15
  d3.graph(adjmat)
16
+ end = time.perf_counter()
17
+
13
18
  d3.show()
14
19
 
20
+ print(f"Elapsed: {end - start:.6f} seconds")
21
+
22
+
15
23
  # %% SET PATH ISSUE https://github.com/erdogant/d3graph/issues/42
16
24
  from pathlib import Path
17
25
  from d3graph import d3graph, vec2adjmat, import_example
@@ -123,15 +131,18 @@ adjmat = vec2adjmat(source=df['source'], target=df['target'], weight=df['weight'
123
131
 
124
132
 
125
133
  # sticky=True (default) — drag to pin, right-click to release
126
- d3 = d3graph(adjmat, sticky=True)
134
+ d3 = d3graph(sticky=True)
135
+ d3.graph(adjmat)
127
136
  d3.show()
128
137
 
129
138
  # sticky=False — classic spring-back behaviour
130
- d3 = d3graph(adjmat, sticky=False)
139
+ d3 = d3graph(sticky=False)
140
+ d3.graph(adjmat)
131
141
  d3.show()
132
142
 
133
143
  # Can also be overridden per-render:
134
- d3.show(adjmat, sticky=False)
144
+ d3.show(sticky=False)
145
+ d3.graph(adjmat)
135
146
  d3.show()
136
147
 
137
148
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: d3graph
3
- Version: 2.9.0
3
+ Version: 2.9.2
4
4
  Summary: Python package to create interactive network based on d3js.
5
5
  Author-email: Erdogan Taskesen <erdogant@gmail.com>
6
6
  License-Expression: BSD-3-Clause
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes