📐 Array Shape and Dimensions

Understanding array shape and dimensions is fundamental to working with NumPy effectively! Shape tells you how your data is organized, while dimensions indicate how many "levels" of nesting your array has. This knowledge is essential for data manipulation, mathematical operations, and avoiding common errors.

Think of shape as the "floor plan" of your data structure!

import numpy as np

# Examples of different shapes and dimensions
vector = np.array([1, 2, 3, 4])                    # 1D
matrix = np.array([[1, 2, 3], [4, 5, 6]])          # 2D
cube = np.array([[[1, 2], [3, 4]], [[5, 6], [7, 8]]])  # 3D

print(f"Vector: {vector}")
print(f"  Shape: {vector.shape}, Dimensions: {vector.ndim}")

print(f"Matrix: \n{matrix}")  
print(f"  Shape: {matrix.shape}, Dimensions: {matrix.ndim}")

print(f"Cube shape: {cube.shape}, Dimensions: {cube.ndim}")

🎯 Understanding Array Shape

Shape is a tuple that tells you the size along each dimension. It's like reading the dimensions of a box: length × width × height.

import numpy as np

# Different shapes explained
array_1d = np.array([10, 20, 30, 40, 50])          # Shape: (5,)
array_2d = np.array([[1, 2, 3], [4, 5, 6]])        # Shape: (2, 3) 
array_3d = np.zeros((2, 3, 4))                     # Shape: (2, 3, 4)

print(f"1D array: {array_1d}")
print(f"  Shape: {array_1d.shape}{len(array_1d)} elements")

print(f"2D array: \n{array_2d}")
print(f"  Shape: {array_2d.shape}{array_2d.shape[0]} rows, {array_2d.shape[1]} columns")

print(f"3D array shape: {array_3d.shape}")
print(f"  → {array_3d.shape[0]} layers, {array_3d.shape[1]} rows, {array_3d.shape[2]} columns")

Shape interpretation:

  • (5,): 1D array with 5 elements
  • (2, 3): 2D array with 2 rows and 3 columns
  • (2, 3, 4): 3D array with 2 layers, 3 rows, 4 columns

📏 Dimensions Explained

Dimensions (ndim) tell you how many indices you need to access a single element:

import numpy as np

# Understanding dimensions
data_1d = np.array([10, 20, 30])
data_2d = np.array([[1, 2], [3, 4], [5, 6]])  
data_3d = np.array([[[1, 2], [3, 4]], [[5, 6], [7, 8]]])

print(f"1D array: {data_1d}")
print(f"  Dimensions: {data_1d.ndim}")
print(f"  Access element: data_1d[0] = {data_1d[0]}")

print(f"2D array: \n{data_2d}")
print(f"  Dimensions: {data_2d.ndim}")
print(f"  Access element: data_2d[1, 0] = {data_2d[1, 0]}")

print(f"3D array dimensions: {data_3d.ndim}")
print(f"  Access element: data_3d[0, 1, 1] = {data_3d[0, 1, 1]}")

🔍 Real-world Shape Examples

Let's see how shape applies to real data structures:

import numpy as np

print("🌍 Real-world Array Shapes")
print("=" * 30)

# Example 1: Image data
image_rgb = np.random.randint(0, 255, (480, 640, 3))
print(f"RGB Image shape: {image_rgb.shape}")
print(f"  Height: {image_rgb.shape[0]} pixels")
print(f"  Width: {image_rgb.shape[1]} pixels") 
print(f"  Channels: {image_rgb.shape[2]} (Red, Green, Blue)")

# Example 2: Time series data
time_series = np.random.randn(365, 24)  # 365 days, 24 hours each
print(f"Time series shape: {time_series.shape}")
print(f"  Days: {time_series.shape[0]}")
print(f"  Hours per day: {time_series.shape[1]}")

# Example 3: Student grades
grades = np.array([[85, 92, 78], [91, 87, 95], [76, 89, 82]])
print(f"Grades shape: {grades.shape}")
print(f"  Students: {grades.shape[0]}")
print(f"  Subjects: {grades.shape[1]}")

📊 Working with Shape Information

You can use shape information to understand and manipulate your data:

import numpy as np

# Using shape for calculations
sales_data = np.array([
    [150, 200, 175, 180],  # Q1
    [160, 210, 185, 190],  # Q2  
    [140, 195, 170, 175],  # Q3
    [170, 220, 195, 200]   # Q4
])

print(f"Sales data shape: {sales_data.shape}")
print(f"Data: \n{sales_data}")

# Extract shape information
num_quarters = sales_data.shape[0] 
num_products = sales_data.shape[1]
total_data_points = sales_data.size

print(f"\nData analysis:")
print(f"  Quarters tracked: {num_quarters}")
print(f"  Products tracked: {num_products}")
print(f"  Total data points: {total_data_points}")

# Calculate using shape
quarterly_totals = np.sum(sales_data, axis=1)  # Sum across products
product_totals = np.sum(sales_data, axis=0)    # Sum across quarters

print(f"  Quarterly totals: {quarterly_totals}")
print(f"  Product totals: {product_totals}")

🔄 Reshaping Arrays

You can change an array's shape without changing its data using reshape():

import numpy as np

# Reshaping examples
original = np.arange(12)  # 12 elements: 0, 1, 2, ..., 11
print(f"Original: {original}")
print(f"Original shape: {original.shape}")

# Different reshape options
as_matrix = original.reshape(3, 4)    # 3 rows, 4 columns
as_tall = original.reshape(6, 2)      # 6 rows, 2 columns  
as_cube = original.reshape(2, 2, 3)   # 2×2×3 cube
auto_cols = original.reshape(4, -1)    # 4 rows, auto columns

print(f"As matrix (3×4): \n{as_matrix}")
print(f"As tall (6×2): \n{as_tall}")
print(f"As cube (2×2×3) shape: {as_cube.shape}")
print(f"Auto columns (4×?): \n{auto_cols}")

📏 Shape Compatibility for Operations

Understanding shape is crucial for mathematical operations and broadcasting:

import numpy as np

# Shape compatibility examples
matrix = np.array([[1, 2, 3], [4, 5, 6]])       # Shape: (2, 3)
vector = np.array([10, 20, 30])                 # Shape: (3,)
scalar = 5

print(f"Matrix shape: {matrix.shape}")
print(f"Vector shape: {vector.shape}")

# Compatible operations
result1 = matrix + vector  # Broadcasting works!
result2 = matrix * scalar  # Scalar multiplication

print(f"Matrix: \n{matrix}")
print(f"Vector: {vector}")
print(f"Matrix + Vector: \n{result1}")
print(f"Matrix × Scalar: \n{result2}")

# Check compatibility
print(f"Compatible for addition? {matrix.shape[-1] == vector.shape[0]}")

🎯 Common Shape Operations

Here are essential operations you'll use frequently:

import numpy as np

# Common shape operations
data = np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]])

print(f"Original: \n{data}")
print(f"Shape: {data.shape}")

# Transpose (swap rows and columns)
transposed = data.T
print(f"Transposed: \n{transposed}")
print(f"Transposed shape: {transposed.shape}")

# Flatten to 1D
flattened = data.flatten()
print(f"Flattened: {flattened}")
print(f"Flattened shape: {flattened.shape}")

# Add new dimension
expanded = data[np.newaxis, :]  # Add dimension at start
print(f"Expanded shape: {expanded.shape}")

# Squeeze out size-1 dimensions
squeezed = np.squeeze(expanded)
print(f"Squeezed shape: {squeezed.shape}")

🔍 Shape Debugging Tips

When working with arrays, shape mismatches are common. Here's how to debug:

import numpy as np

# Common shape debugging scenarios
def debug_shapes(arr1, arr2, operation="operation"):
    print(f"Shape debugging for {operation}:")
    print(f"  Array 1 shape: {arr1.shape}")
    print(f"  Array 2 shape: {arr2.shape}")
    
    # Check compatibility
    try:
        result = arr1 + arr2  # Test operation
        print(f"  ✅ Compatible! Result shape: {result.shape}")
    except ValueError as e:
        print(f"  ❌ Shape mismatch: {e}")
        print(f"  💡 Try reshaping or broadcasting")

# Test different scenarios
a = np.array([[1, 2, 3], [4, 5, 6]])        # (2, 3)
b = np.array([10, 20, 30])                  # (3,)
c = np.array([[10], [20]])                  # (2, 1)

debug_shapes(a, b, "matrix + vector")
debug_shapes(a, c, "matrix + column vector")

# Fix shape mismatch
b_reshaped = b.reshape(1, 3)  # Make it (1, 3)
debug_shapes(a, b_reshaped, "matrix + reshaped vector")

🎯 Key Takeaways

🚀 What's Next?

Great! Now you understand how arrays are structured. Next, let's explore data types and memory usage - understanding how NumPy stores different kinds of numbers efficiently.

Continue to: Data Types and Memory

Ready to master data storage! 🔢✨

Was this helpful?

😔Poor
🙁Fair
😊Good
😄Great
🤩Excellent