Working with Matrices¶
Matrices in Groggy provide matrix representations of graph structure and enable matrix-based graph algorithms. Think linear algebra operations on graph topology and attributes.
What are GraphMatrices?¶
GraphMatrix is Groggy's matrix type for graph-related linear algebra:
import groggy as gr
g = gr.generators.karate_club()
# Adjacency matrix
A = g.adjacency_matrix() # GraphMatrix
print(type(A))
# Laplacian matrix
L = g.laplacian_matrix() # GraphMatrix
# Shape
print(A.shape()) # (num_nodes, num_nodes)
Common matrix types: - Adjacency matrix: Who connects to whom - Laplacian matrix: For spectral analysis - Weight matrix: Weighted connections - Feature matrices: Node/edge attributes as matrices
Creating Matrices¶
From Graph Structure¶
Get structural matrices:
g = gr.Graph()
n0, n1, n2 = g.add_node(), g.add_node(), g.add_node()
g.add_edge(n0, n1)
g.add_edge(n0, n2)
g.add_edge(n1, n2)
# Adjacency matrix (0/1 for connections)
A = g.adjacency_matrix()
print(A.shape()) # (3, 3)
# Alternative access
A = g.adj() # Same as adjacency_matrix()
# Laplacian matrix
L = g.laplacian_matrix()
From Subgraphs¶
Subgraphs also have matrix methods:
# Filter to subgraph
sub = g.nodes[:10]
# Get matrices for subgraph
A_sub = sub.adjacency_matrix()
L_sub = sub.to_matrix() # Generic matrix conversion
From Attributes¶
Edge weights can create weighted matrices:
g = gr.Graph()
n0, n1, n2 = g.add_node(), g.add_node(), g.add_node()
g.add_edge(n0, n1, weight=5.0)
g.add_edge(n0, n2, weight=2.0)
g.add_edge(n1, n2, weight=1.0)
# Weight matrix
W = g.edges.weight_matrix() # GraphMatrix with weights
Manual Creation¶
Create matrices directly:
# From data
data = [[1, 0, 1],
[0, 1, 0],
[1, 0, 1]]
M = gr.matrix(data) # GraphMatrix
# Identity matrix
I = gr.GraphMatrix.identity(3) # 3x3 identity
# From array
arr = gr.num_array([1, 2, 3, 4, 5, 6])
M = arr.reshape((2, 3)) # 2x3 matrix (if supported)
Matrix Properties¶
Shape and Type¶
A = g.adjacency_matrix()
# Shape
rows, cols = A.shape()
print(f"Shape: {rows}x{cols}")
# Data type
dtype = A.dtype()
print(f"Type: {dtype}")
# Sparsity
if A.is_sparse():
print("Sparse matrix")
else:
print("Dense matrix")
Checking Properties¶
# Square matrix?
if A.is_square():
print("Square matrix")
# Symmetric?
if A.is_symmetric():
print("Symmetric")
# Empty?
if A.is_empty():
print("No elements")
# Numeric?
if A.is_numeric():
print("Contains numbers")
Matrix Operations¶
Basic Arithmetic¶
A = g.adjacency_matrix()
# Element-wise operations
abs_A = A.abs() # Absolute values
exp_A = A.exp() # Exponential
log_A = A.log() # Logarithm (careful with zeros!)
# Matrix norms
frobenius = A.norm() # Frobenius norm
l1_norm = A.norm_l1() # L1 norm
inf_norm = A.norm_inf() # Infinity norm
Activation Functions¶
Neural network-style activations:
# Activation functions (useful for GNNs)
relu_A = A.relu()
leaky_A = A.leaky_relu()
elu_A = A.elu()
gelu_A = A.gelu()
sigmoid_A = A.sigmoid()
softmax_A = A.softmax()
tanh_A = A.tanh()
Statistical Operations¶
# Global statistics
max_val = A.max()
min_val = A.min()
mean_val = A.mean()
sum_val = A.sum()
print(f"Matrix stats: min={min_val}, max={max_val}, mean={mean_val:.2f}")
# Axis-wise (if supported)
# row_max = A.max_axis(0) # Max per column
# col_max = A.max_axis(1) # Max per row
Matrix Multiplication¶
# Matrix-matrix multiply
C = A.matmul(B) # A @ B
# Matrix-vector multiply
v = gr.num_array([1, 2, 3])
result = A.matmul_vec(v) # A @ v
Accessing Matrix Data¶
Row and Column Access¶
A = g.adjacency_matrix()
# Get row (if supported)
# row_0 = A.get_row(0)
# Get column (if supported)
# col_0 = A.get_column(0)
# Iterate rows
for row in A.iter_rows():
print(row)
# Iterate columns
for col in A.iter_columns():
print(col)
Conversion¶
# To dense (if sparse)
dense_A = A.dense()
# Flatten to array
flat = A.flatten() # NumArray
print(flat.head())
# Get raw data
data = A.data() # List of lists
print(data)
# Column names (if applicable)
cols = A.columns()
Sparse vs Dense¶
Checking Sparsity¶
A = g.adjacency_matrix()
if A.is_sparse():
print("Stored as sparse matrix")
# Efficient for large graphs with few edges
else:
print("Stored as dense matrix")
# Better for small or dense graphs
Converting¶
# Force to dense
dense_A = A.dense()
# Sparse is automatic based on sparsity
# Groggy chooses representation internally
Common Patterns¶
Pattern 1: Spectral Analysis¶
# Get Laplacian
L = g.laplacian_matrix()
# Eigenvalue decomposition (if supported)
# eigenvalues, eigenvectors = L.eigenvalue_decomposition()
# For spectral clustering, embeddings, etc.
Pattern 2: Power Iteration¶
# Matrix power (if supported)
# A2 = A.power(2) # A^2
# A3 = A.power(3) # A^3
# For path counting, centrality, etc.
Pattern 3: Adjacency Properties¶
A = g.adjacency_matrix()
# Density from matrix
edges = A.sum() / 2 # Undirected graph
n = A.shape()[0]
max_edges = n * (n - 1) / 2
density = edges / max_edges
print(f"Graph density: {density:.3f}")
Pattern 4: Degree Calculation¶
A = g.adjacency_matrix()
# Row sums = out-degree (if supported)
# out_degrees = A.sum_axis(1)
# Column sums = in-degree
# in_degrees = A.sum_axis(0)
# Or use graph methods
degrees = g.degree() # NumArray
Pattern 5: Matrix to NumPy¶
A = g.adjacency_matrix()
# Convert to numpy
import numpy as np
# Via data
data = A.data()
np_matrix = np.array(data)
# Or via flatten and reshape
flat = A.flatten()
shape = A.shape()
np_matrix = np.array(flat.to_list()).reshape(shape)
Pattern 6: Feature Matrix¶
# Collect node features as matrix
ages = g.nodes["age"]
scores = g.nodes["score"]
# Stack into matrix (manually)
import numpy as np
X = np.column_stack([
ages.to_list(),
scores.to_list()
])
print(X.shape) # (num_nodes, 2)
Pattern 7: Weighted Adjacency¶
# Get weighted adjacency
W = g.edges.weight_matrix()
# Use in algorithms
# pagerank_scores = compute_pagerank(W)
# Or normalize
max_weight = W.max()
W_normalized = W.map(lambda x: x / max_weight) if max_weight > 0 else W
Matrix Transformations¶
Apply Functions¶
A = g.adjacency_matrix()
# Apply function to each element
squared = A.map(lambda x: x ** 2)
doubled = A.map(lambda x: x * 2)
# Or use apply
transformed = A.apply(lambda x: max(0, x - 1))
Neural Network Ops¶
# For GNN implementations
hidden = A.relu()
activated = hidden.sigmoid()
normalized = activated.softmax()
# Dropout (if supported)
# dropped = hidden.dropout(0.5)
Performance Considerations¶
Sparse Matrices¶
For large graphs, sparse representation is crucial:
# Large graph
g = gr.generators.erdos_renyi(n=10000, p=0.001)
# Adjacency is sparse
A = g.adjacency_matrix()
if A.is_sparse():
# Efficient storage and operations
print("Using sparse representation")
Dense Operations¶
Small or dense graphs use dense matrices:
# Complete graph - all edges exist
g = gr.generators.complete_graph(100)
A = g.adjacency_matrix()
# Likely stored as dense since almost all entries are 1
Memory¶
# Sparse: ~O(num_edges) memory
# Dense: O(n^2) memory
# For n=10,000:
# - Sparse with 100,000 edges: ~1 MB
# - Dense: ~400 MB (10k x 10k floats)
Limitations¶
Matrix Size¶
Large graphs can create huge matrices:
# 100,000 nodes = 10 billion matrix entries
# Even sparse, operations can be expensive
# Consider:
# - Working with subgraphs
# - Using specialized algorithms
# - Approximate methods
Missing Operations¶
Some operations may not be implemented:
Quick Reference¶
Creating Matrices¶
A = g.adjacency_matrix() # 0/1 adjacency
L = g.laplacian_matrix() # Graph Laplacian
W = g.edges.weight_matrix() # Weighted edges
M = g.to_matrix() # Generic conversion
I = gr.GraphMatrix.identity(n) # Identity matrix
Properties¶
shape = A.shape() # (rows, cols)
is_sparse = A.is_sparse() # True/False
is_square = A.is_square() # True/False
is_sym = A.is_symmetric() # True/False
dtype = A.dtype() # "float", "int", etc.
Operations¶
# Element-wise
abs_A = A.abs()
exp_A = A.exp()
log_A = A.log()
# Norms
frobenius = A.norm()
l1 = A.norm_l1()
inf = A.norm_inf()
# Statistics
max_val = A.max()
min_val = A.min()
mean_val = A.mean()
sum_val = A.sum()
# Activations
relu_A = A.relu()
sigmoid_A = A.sigmoid()
softmax_A = A.softmax()
Conversion¶
See Also¶
- GraphMatrix API Reference: Complete method reference
- Graph Core Guide: Getting matrices from graphs
- Subgraphs Guide: Subgraph matrices
- Arrays Guide: Working with array data
- Algorithms Guide: Matrix-based algorithms