0
# Core Data Types
1
2
Essential data structures including matrices, layouts, and utility classes that support graph operations and analysis. These foundational types enable efficient computation and data manipulation in igraph.
3
4
## Capabilities
5
6
### Matrix Operations
7
8
Simple matrix data type for coordinate pairs, adjacency matrices, and linear algebra operations.
9
10
```python { .api }
11
class Matrix:
12
def __init__(self, data=None, nrow=0, ncol=0):
13
"""
14
Create matrix from data or dimensions.
15
16
Parameters:
17
- data: list/nested list, matrix data
18
- nrow: int, number of rows (if data is flat list)
19
- ncol: int, number of columns (if data is flat list)
20
"""
21
22
@classmethod
23
def Fill(cls, value, shape):
24
"""
25
Create matrix filled with specific value.
26
27
Parameters:
28
- value: fill value
29
- shape: tuple (nrows, ncols)
30
31
Returns:
32
Matrix filled with value
33
"""
34
35
@classmethod
36
def Zero(cls, shape):
37
"""
38
Create zero matrix.
39
40
Parameters:
41
- shape: tuple (nrows, ncols)
42
43
Returns:
44
Matrix filled with zeros
45
"""
46
47
@classmethod
48
def Identity(cls, n):
49
"""
50
Create identity matrix.
51
52
Parameters:
53
- n: int, matrix size (n x n)
54
55
Returns:
56
Identity matrix
57
"""
58
59
def __getitem__(self, key):
60
"""Access matrix elements by [i, j] or [i]."""
61
62
def __setitem__(self, key, value):
63
"""Set matrix elements."""
64
65
@property
66
def shape(self):
67
"""Get matrix dimensions as (nrows, ncols)."""
68
69
def transpose(self):
70
"""
71
Get transposed matrix.
72
73
Returns:
74
Matrix, transposed version
75
"""
76
```
77
78
**Usage Examples:**
79
80
```python
81
from igraph.datatypes import Matrix
82
83
# Create matrix from nested list
84
data = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
85
m1 = Matrix(data)
86
print(f"Matrix shape: {m1.shape}")
87
88
# Create from flat list with dimensions
89
flat_data = [1, 2, 3, 4, 5, 6]
90
m2 = Matrix(flat_data, nrow=2, ncol=3)
91
92
# Create special matrices
93
zero_matrix = Matrix.Zero((3, 3))
94
identity = Matrix.Identity(4)
95
filled = Matrix.Fill(7.5, (2, 4))
96
97
# Access elements
98
print(f"Element [1,2]: {m1[1, 2]}")
99
print(f"Row 0: {m1[0]}")
100
101
# Set elements
102
m1[0, 0] = 99
103
m1[2] = [10, 11, 12] # Set entire row
104
105
# Matrix operations
106
transposed = m1.transpose()
107
print(f"Original shape: {m1.shape}")
108
print(f"Transposed shape: {transposed.shape}")
109
110
# Use with graph operations
111
import igraph as ig
112
g = ig.Graph.Ring(5)
113
114
# Convert adjacency to Matrix
115
adj_list = g.get_adjacency()
116
adj_matrix = Matrix(adj_list)
117
print(f"Adjacency matrix shape: {adj_matrix.shape}")
118
```
119
120
### Layout Coordinates
121
122
Represents graph layout coordinates in n-dimensional space with transformation methods.
123
124
```python { .api }
125
class Layout:
126
def __init__(self, coords=None, dim=2):
127
"""
128
Create layout from coordinates.
129
130
Parameters:
131
- coords: list of tuples/lists, vertex coordinates
132
- dim: int, number of dimensions
133
"""
134
135
def __len__(self):
136
"""Get number of vertices in layout."""
137
138
def __getitem__(self, key):
139
"""Access coordinates by vertex index."""
140
141
def __setitem__(self, key, value):
142
"""Set coordinates for vertex."""
143
144
@property
145
def coords(self):
146
"""Get all coordinates as list of tuples."""
147
148
def copy(self):
149
"""
150
Create independent copy of layout.
151
152
Returns:
153
Layout, deep copy
154
"""
155
156
def scale(self, *args):
157
"""
158
Scale layout coordinates.
159
160
Parameters:
161
- *args: scaling factors (single value or per-dimension)
162
163
Returns:
164
None (modifies in place)
165
"""
166
167
def translate(self, *args):
168
"""
169
Translate layout coordinates.
170
171
Parameters:
172
- *args: translation offsets per dimension
173
174
Returns:
175
None (modifies in place)
176
"""
177
178
def rotate(self, angle, dim1=0, dim2=1):
179
"""
180
Rotate layout in specified plane.
181
182
Parameters:
183
- angle: float, rotation angle in radians
184
- dim1, dim2: int, dimensions defining rotation plane
185
186
Returns:
187
None (modifies in place)
188
"""
189
190
def mirror(self, dim):
191
"""
192
Mirror layout along specified dimension.
193
194
Parameters:
195
- dim: int, dimension to mirror (0=x, 1=y, etc.)
196
197
Returns:
198
None (modifies in place)
199
"""
200
201
def fit_into(self, bbox, keep_aspect_ratio=True):
202
"""
203
Fit layout into bounding box.
204
205
Parameters:
206
- bbox: BoundingBox or (left, top, right, bottom)
207
- keep_aspect_ratio: bool, preserve proportions
208
209
Returns:
210
None (modifies in place)
211
"""
212
213
def centroid(self):
214
"""
215
Calculate layout centroid.
216
217
Returns:
218
tuple, center coordinates
219
"""
220
221
def boundaries(self):
222
"""
223
Get layout boundaries.
224
225
Returns:
226
tuple, (min_coords, max_coords) for each dimension
227
"""
228
```
229
230
**Usage Examples:**
231
232
```python
233
from igraph.layout import Layout
234
from igraph.drawing import BoundingBox
235
import math
236
237
# Create layout manually
238
coords = [(0, 0), (1, 0), (1, 1), (0, 1), (0.5, 0.5)]
239
layout = Layout(coords)
240
241
# Access and modify coordinates
242
print(f"Vertex 0 position: {layout[0]}")
243
layout[0] = (0.1, 0.1) # Move vertex slightly
244
245
# Layout transformations
246
layout_copy = layout.copy()
247
248
# Scale uniformly
249
layout_copy.scale(2.0) # Double all coordinates
250
251
# Scale per dimension
252
layout_copy.scale(1.5, 0.8) # Stretch x, compress y
253
254
# Translation
255
layout_copy.translate(10, 5) # Move right 10, up 5
256
257
# Rotation (45 degrees)
258
layout_copy.rotate(math.pi/4) # Rotate around origin
259
260
# Mirror horizontally
261
layout_copy.mirror(0) # Flip along x-axis
262
263
# Fit into bounding box
264
bbox = BoundingBox(0, 0, 800, 600)
265
layout_copy.fit_into(bbox, keep_aspect_ratio=True)
266
267
# Layout analysis
268
centroid = layout.centroid()
269
boundaries = layout.boundaries()
270
print(f"Layout centroid: {centroid}")
271
print(f"Layout boundaries: {boundaries}")
272
273
# Create 3D layout
274
coords_3d = [(0, 0, 0), (1, 0, 0), (0, 1, 0), (0, 0, 1)]
275
layout_3d = Layout(coords_3d, dim=3)
276
277
# 3D rotation (around z-axis)
278
layout_3d.rotate(math.pi/6, dim1=0, dim2=1) # Rotate x-y plane
279
```
280
281
### Census and Analysis Results
282
283
Specialized data types for storing structural analysis results.
284
285
```python { .api }
286
class DyadCensus:
287
def __init__(self, mutual=0, asymmetric=0, null=0):
288
"""
289
Dyad census results for directed graphs.
290
291
Parameters:
292
- mutual: int, number of mutual dyads
293
- asymmetric: int, number of asymmetric dyads
294
- null: int, number of null dyads
295
"""
296
297
@property
298
def mutual(self):
299
"""Number of mutual dyads (edges in both directions)."""
300
301
@property
302
def asymmetric(self):
303
"""Number of asymmetric dyads (edge in one direction)."""
304
305
@property
306
def null(self):
307
"""Number of null dyads (no edges between vertices)."""
308
309
class TriadCensus:
310
def __init__(self, **kwargs):
311
"""
312
Triad census results with counts for all 16 triad types.
313
314
Triad types (MAN notation):
315
- 003: empty triad
316
- 012, 102, 021D, 021U, 021C: various asymmetric patterns
317
- 111D, 111U: mixed patterns
318
- 030T, 030C: transitive and cyclic triads
319
- 201: partially connected
320
- 120D, 120U, 120C: different orientations
321
- 210: almost complete
322
- 300: complete triad
323
"""
324
325
def __getattribute__(self, name):
326
"""Access triad counts by type name (e.g., census.003)."""
327
```
328
329
**Usage Examples:**
330
331
```python
332
import igraph as ig
333
334
# Dyad census for directed graph
335
g_dir = ig.Graph.Erdos_Renyi(20, 30, directed=True)
336
dyad_census = g_dir.dyad_census()
337
338
print(f"Mutual dyads: {dyad_census.mutual}")
339
print(f"Asymmetric dyads: {dyad_census.asymmetric}")
340
print(f"Null dyads: {dyad_census.null}")
341
print(f"Total possible dyads: {dyad_census.mutual + dyad_census.asymmetric + dyad_census.null}")
342
343
# Verify total
344
n = g_dir.vcount()
345
expected_total = n * (n - 1) // 2
346
print(f"Expected total: {expected_total}")
347
348
# Triad census
349
triad_census = g_dir.triad_census()
350
351
# Access specific triad types
352
print(f"Empty triads (003): {triad_census.__getattribute__('003')}")
353
print(f"Complete triads (300): {triad_census.__getattribute__('300')}")
354
print(f"Transitive triads (030T): {triad_census.__getattribute__('030T')}")
355
356
# Analyze triad distribution
357
triad_types = ['003', '012', '102', '021D', '021U', '021C',
358
'111D', '111U', '030T', '030C', '201',
359
'120D', '120U', '120C', '210', '300']
360
361
total_triads = 0
362
for triad_type in triad_types:
363
count = triad_census.__getattribute__(triad_type)
364
total_triads += count
365
if count > 0:
366
print(f"{triad_type}: {count}")
367
368
print(f"Total triads: {total_triads}")
369
expected_triads = n * (n - 1) * (n - 2) // 6
370
print(f"Expected total: {expected_triads}")
371
```
372
373
### Unique ID Generation
374
375
Dictionary-like class for assigning unique IDs to names or objects.
376
377
```python { .api }
378
class UniqueIdGenerator:
379
def __init__(self, id_generator=None, initial=None):
380
"""
381
Create unique ID generator.
382
383
Parameters:
384
- id_generator: function, custom ID generation function
385
- initial: dict, initial name-to-ID mappings
386
"""
387
388
def __getitem__(self, name):
389
"""
390
Get ID for name, creating new ID if needed.
391
392
Parameters:
393
- name: object, name/key to get ID for
394
395
Returns:
396
int, unique ID for name
397
"""
398
399
def __contains__(self, name):
400
"""Check if name already has assigned ID."""
401
402
def __len__(self):
403
"""Get number of assigned IDs."""
404
405
def keys(self):
406
"""Get all names with assigned IDs."""
407
408
def values(self):
409
"""Get all assigned ID values."""
410
411
def items(self):
412
"""Get (name, ID) pairs."""
413
414
def reverse_dict(self):
415
"""
416
Get reverse mapping from IDs to names.
417
418
Returns:
419
dict, ID-to-name mapping
420
"""
421
```
422
423
**Usage Examples:**
424
425
```python
426
from igraph.datatypes import UniqueIdGenerator
427
428
# Create ID generator for vertex names
429
id_gen = UniqueIdGenerator()
430
431
# Assign IDs to names
432
names = ["Alice", "Bob", "Carol", "Alice", "Dave", "Bob"]
433
vertex_ids = []
434
435
for name in names:
436
vertex_id = id_gen[name] # Gets existing ID or creates new one
437
vertex_ids.append(vertex_id)
438
439
print(f"Name to ID mapping: {list(id_gen.items())}")
440
print(f"Vertex IDs: {vertex_ids}") # [0, 1, 2, 0, 3, 1]
441
442
# Check if name exists
443
print(f"'Alice' has ID: {'Alice' in id_gen}")
444
print(f"'Eve' has ID: {'Eve' in id_gen}")
445
446
# Get reverse mapping
447
reverse = id_gen.reverse_dict()
448
print(f"ID to name: {reverse}")
449
450
# Use with custom ID generation
451
def custom_id_gen():
452
"""Generate IDs starting from 100."""
453
counter = 100
454
while True:
455
yield counter
456
counter += 1
457
458
custom_gen = UniqueIdGenerator(id_generator=custom_id_gen().__next__)
459
custom_gen["first"] = None # Triggers ID generation
460
custom_gen["second"] = None
461
print(f"Custom IDs: {list(custom_gen.items())}")
462
463
# Pre-populate with initial mappings
464
initial_map = {"root": 0, "admin": 1}
465
preset_gen = UniqueIdGenerator(initial=initial_map)
466
preset_gen["user1"] # Gets ID 2
467
preset_gen["user2"] # Gets ID 3
468
print(f"Preset generator: {list(preset_gen.items())}")
469
470
# Use in graph construction
471
def build_graph_from_edges(edge_list):
472
"""Build graph from string-based edge list."""
473
id_gen = UniqueIdGenerator()
474
numeric_edges = []
475
476
for source, target in edge_list:
477
source_id = id_gen[source]
478
target_id = id_gen[target]
479
numeric_edges.append((source_id, target_id))
480
481
g = ig.Graph(edges=numeric_edges, directed=False)
482
483
# Add vertex names
484
reverse_map = id_gen.reverse_dict()
485
g.vs["name"] = [reverse_map[i] for i in range(len(id_gen))]
486
487
return g
488
489
# Example usage
490
string_edges = [
491
("Alice", "Bob"),
492
("Bob", "Carol"),
493
("Carol", "Dave"),
494
("Alice", "Dave")
495
]
496
497
g = build_graph_from_edges(string_edges)
498
print(f"Graph vertices: {g.vs['name']}")
499
print(f"Graph edges: {g.get_edgelist()}")
500
```
501
502
### Geometric Utilities
503
504
Geometric classes for visualization and spatial operations.
505
506
```python { .api }
507
class BoundingBox:
508
def __init__(self, *args):
509
"""
510
Create bounding box.
511
512
Parameters:
513
- *args: (left, top, right, bottom) or ((left, top), (right, bottom))
514
"""
515
516
@property
517
def left(self):
518
"""Left coordinate."""
519
520
@property
521
def top(self):
522
"""Top coordinate."""
523
524
@property
525
def right(self):
526
"""Right coordinate."""
527
528
@property
529
def bottom(self):
530
"""Bottom coordinate."""
531
532
@property
533
def width(self):
534
"""Bounding box width."""
535
536
@property
537
def height(self):
538
"""Bounding box height."""
539
540
def area(self):
541
"""Calculate area."""
542
543
def contains(self, point):
544
"""Check if point is inside bounding box."""
545
546
class Point:
547
def __init__(self, x=0, y=0):
548
"""
549
Create 2D point.
550
551
Parameters:
552
- x, y: float, coordinates
553
"""
554
555
def distance(self, other):
556
"""Calculate distance to another point."""
557
558
class Rectangle:
559
def __init__(self, corner1, corner2=None, width=None, height=None):
560
"""
561
Create rectangle from corners or corner + dimensions.
562
563
Parameters:
564
- corner1: Point or tuple, first corner
565
- corner2: Point or tuple, opposite corner (OR use width/height)
566
- width: float, rectangle width
567
- height: float, rectangle height
568
"""
569
570
def area(self):
571
"""Calculate rectangle area."""
572
573
def contains(self, point):
574
"""Check if point is inside rectangle."""
575
```
576
577
**Usage Examples:**
578
579
```python
580
from igraph.drawing.utils import BoundingBox, Point, Rectangle
581
582
# Bounding box operations
583
bbox1 = BoundingBox(0, 0, 100, 80)
584
bbox2 = BoundingBox((10, 5), (90, 75)) # Alternative syntax
585
586
print(f"Box dimensions: {bbox1.width} x {bbox1.height}")
587
print(f"Box area: {bbox1.area()}")
588
589
# Point operations
590
p1 = Point(25, 30)
591
p2 = Point(75, 60)
592
593
distance = p1.distance(p2)
594
print(f"Distance between points: {distance:.2f}")
595
596
# Check containment
597
is_inside = bbox1.contains(p1)
598
print(f"Point {p1.x}, {p1.y} inside bbox: {is_inside}")
599
600
# Rectangle operations
601
rect1 = Rectangle(Point(10, 10), Point(50, 40))
602
rect2 = Rectangle(Point(20, 20), width=30, height=25)
603
604
print(f"Rectangle 1 area: {rect1.area()}")
605
print(f"Rectangle 2 contains (30, 30): {rect2.contains(Point(30, 30))}")
606
607
# Use in layout fitting
608
layout = ig.Graph.Ring(8).layout_circle()
609
target_bbox = BoundingBox(50, 50, 750, 550)
610
611
# Fit layout into bounding box
612
layout.fit_into(target_bbox, keep_aspect_ratio=True)
613
614
# Verify all points are within bounds
615
for i, (x, y) in enumerate(layout):
616
point = Point(x, y)
617
if not target_bbox.contains(point):
618
print(f"Point {i} outside bounds: ({x:.1f}, {y:.1f})")
619
```