Skill Tracer
Skills › Scientific Agent Skills › dask › arrays.md

arrays.md

513 lines · skills/dask/references/arrays.md at 5f2bc03

Open on GitHub
1# Dask Arrays
2
3Reviewed with Dask/distributed 2026.8.0. File paths, deployment settings, and undefined application functions are illustrative; executed local checks and current official sources are in [review.md](review.md).
4

Dask Arrays

Reviewed with Dask/distributed 2026.8.0. File paths, deployment settings, and undefined application functions are illustrative; executed local checks and current official sources are in review.md.

Raw lines 1 to 4
5## Overview
6
7Dask Array implements NumPy's ndarray interface using blocked algorithms. It coordinates many NumPy arrays arranged into a grid to enable computation on datasets larger than available memory, utilizing parallelism across multiple cores.
8

Overview

Dask Array implements NumPy's ndarray interface using blocked algorithms. It coordinates many NumPy arrays arranged into a grid to enable computation on datasets larger than available memory, utilizing parallelism across multiple cores.

Raw lines 5 to 8
9## Core Concept
11A Dask Array is divided into chunks (blocks):
12- Each chunk is a regular NumPy array
13- Operations are applied to each chunk in parallel
14- Results are combined automatically
15- Enables out-of-core computation (data larger than RAM)

Core Concept

A Dask Array is divided into chunks (blocks):

  • Each chunk is a regular NumPy array
  • Operations are applied to each chunk in parallel
  • Results are combined automatically
  • Enables out-of-core computation (data larger than RAM)
Raw lines 9 to 16
17## Key Capabilities

Key Capabilities

Raw lines 17 to 18
19### What Dask Arrays Support
21**Mathematical Operations**:
22- Arithmetic operations (+, -, *, /)
23- Scalar functions (exponentials, logarithms, trigonometric)
24- Element-wise operations
26**Reductions**:
27- `sum()`, `mean()`, `std()`, `var()`
28- Reductions along specified axes
29- `min()`, `max()`, `argmin()`, `argmax()`
31**Linear Algebra**:
32- Tensor contractions
33- Dot products and matrix multiplication
34- Some decompositions (SVD, QR)
36**Data Manipulation**:
37- Transposition
38- Slicing (standard and fancy indexing)
39- Reshaping
40- Concatenation and stacking
42**Array Protocols**:
43- Universal functions (ufuncs)
44- NumPy protocols for interoperability

What Dask Arrays Support

Mathematical Operations:

  • Arithmetic operations (+, -, *, /)
  • Scalar functions (exponentials, logarithms, trigonometric)
  • Element-wise operations

Reductions:

  • sum(), mean(), std(), var()
  • Reductions along specified axes
  • min(), max(), argmin(), argmax()

Linear Algebra:

  • Tensor contractions
  • Dot products and matrix multiplication
  • Some decompositions (SVD, QR)

Data Manipulation:

  • Transposition
  • Slicing (standard and fancy indexing)
  • Reshaping
  • Concatenation and stacking

Array Protocols:

  • Universal functions (ufuncs)
  • NumPy protocols for interoperability
Raw lines 19 to 45
46## When to Use Dask Arrays
48**Use Dask Arrays When**:
49- Arrays exceed available RAM
50- Computation can be parallelized across chunks
51- Working with NumPy-style numerical operations
52- Need to scale NumPy code to larger datasets
54**Stick with NumPy When**:
55- Arrays fit comfortably in memory
56- Operations require global views of data
57- Using specialized functions not available in Dask
58- Performance is adequate with NumPy alone

When to Use Dask Arrays

Use Dask Arrays When:

  • Arrays exceed available RAM
  • Computation can be parallelized across chunks
  • Working with NumPy-style numerical operations
  • Need to scale NumPy code to larger datasets

Stick with NumPy When:

  • Arrays fit comfortably in memory
  • Operations require global views of data
  • Using specialized functions not available in Dask
  • Performance is adequate with NumPy alone
Raw lines 46 to 59
60## Important Limitations
62Dask Arrays intentionally don't implement certain NumPy features:
64**Not Implemented**:
65- Most `np.linalg` functions (only basic operations available)
66- Operations difficult to parallelize (like full sorting)
67- Memory-inefficient operations (converting to lists, iterating via loops)
68- Many specialized functions (driven by community needs)
70**Workarounds**: `map_blocks` is appropriate only when each block can be processed independently. A block FFT is not a global FFT; neighborhood filters need overlap; global reductions need a valid combine step.

Important Limitations

Dask Arrays intentionally don't implement certain NumPy features:

Not Implemented:

  • Most np.linalg functions (only basic operations available)
  • Operations difficult to parallelize (like full sorting)
  • Memory-inefficient operations (converting to lists, iterating via loops)
  • Many specialized functions (driven by community needs)

Workarounds: map_blocks is appropriate only when each block can be processed independently. A block FFT is not a global FFT; neighborhood filters need overlap; global reductions need a valid combine step.

Raw lines 60 to 71
72## Creating Dask Arrays

Creating Dask Arrays

Raw lines 72 to 73
74### From NumPy Arrays
75```python
76import dask.array as da
77import numpy as np
79# Create from NumPy array with specified chunks
80x = np.arange(10000)
81dx = da.from_array(x, chunks=1000) # Creates 10 chunks of 1000 elements each
82```

From NumPy Arrays

import dask.array as da
import numpy as np

# Create from NumPy array with specified chunks
x = np.arange(10000)
dx = da.from_array(x, chunks=1000)  # Creates 10 chunks of 1000 elements each
Raw lines 74 to 83
84### Random Arrays
85```python
86# Create random array with specified chunks
87x = da.random.random((10000, 10000), chunks=(1000, 1000))
89# Other random functions
90x = da.random.normal(10, 0.1, size=(10000, 10000), chunks=(1000, 1000))
91```

Random Arrays

# Create random array with specified chunks
x = da.random.random((10000, 10000), chunks=(1000, 1000))

# Other random functions
x = da.random.normal(10, 0.1, size=(10000, 10000), chunks=(1000, 1000))
Raw lines 84 to 92
93### Zeros, Ones, and Empty
94```python
95# Create arrays filled with constants
96zeros = da.zeros((10000, 10000), chunks=(1000, 1000))
97ones = da.ones((10000, 10000), chunks=(1000, 1000))
98empty = da.empty((10000, 10000), chunks=(1000, 1000))
99```

Zeros, Ones, and Empty

# Create arrays filled with constants
zeros = da.zeros((10000, 10000), chunks=(1000, 1000))
ones = da.ones((10000, 10000), chunks=(1000, 1000))
empty = da.empty((10000, 10000), chunks=(1000, 1000))
Raw lines 93 to 100
101### From Functions
102```python
103import dask
104import dask.array as da
105import numpy as np
107def create_block(i, j):
108 # Deterministic small synthetic block
109 return np.full((4, 5), i * 10 + j, dtype='float64')
111blocks = [[da.from_delayed(dask.delayed(create_block)(i, j),
112 shape=(4, 5), dtype='float64')
113 for j in range(3)] for i in range(2)]
114x = da.block(blocks) # (8, 15); from_delayed accepts ONE delayed value
115```

From Functions

import dask
import dask.array as da
import numpy as np

def create_block(i, j):
    # Deterministic small synthetic block
    return np.full((4, 5), i * 10 + j, dtype='float64')

blocks = [[da.from_delayed(dask.delayed(create_block)(i, j),
                           shape=(4, 5), dtype='float64')
           for j in range(3)] for i in range(2)]
x = da.block(blocks)  # (8, 15); from_delayed accepts ONE delayed value
Raw lines 101 to 116
117### From Disk
118```python
119# Load from HDF5
120import h5py
121with h5py.File('myfile.hdf5', mode='r') as f:
122 x = da.from_array(f['/data'], chunks=(1000, 1000))
123 mean = x.mean().compute(scheduler='threads') # While handle is open
124# h5py handles cannot be pickled for process/distributed workers.
125# For those schedulers, open files inside worker tasks and return concrete chunks.
127# Load from Zarr
128import zarr
129z = zarr.open('myfile.zarr', mode='r')
130x = da.from_array(z, chunks=(1000, 1000))
131```

From Disk

# Load from HDF5
import h5py
with h5py.File('myfile.hdf5', mode='r') as f:
    x = da.from_array(f['/data'], chunks=(1000, 1000))
    mean = x.mean().compute(scheduler='threads')  # While handle is open
# h5py handles cannot be pickled for process/distributed workers.
# For those schedulers, open files inside worker tasks and return concrete chunks.

# Load from Zarr
import zarr
z = zarr.open('myfile.zarr', mode='r')
x = da.from_array(z, chunks=(1000, 1000))
Raw lines 117 to 132
133## Common Operations

Common Operations

Raw lines 133 to 134
135### Arithmetic Operations
136```python
137import dask.array as da
139x = da.random.random((10000, 10000), chunks=(1000, 1000))
140y = da.random.random((10000, 10000), chunks=(1000, 1000))
142# Element-wise operations (lazy)
143z = x + y
144z = x * y
145z = da.exp(x)
146z = da.log(y)
148# Compute result
149result = z.compute()
150```

Arithmetic Operations

import dask.array as da

x = da.random.random((10000, 10000), chunks=(1000, 1000))
y = da.random.random((10000, 10000), chunks=(1000, 1000))

# Element-wise operations (lazy)
z = x + y
z = x * y
z = da.exp(x)
z = da.log(y)

# Compute result
result = z.compute()
Raw lines 135 to 151
152### Reductions
153```python
154# Reductions along axes
155total = x.sum().compute()
156mean = x.mean().compute()
157std = x.std().compute()
159# Reduction along specific axis
160row_means = x.mean(axis=1).compute()
161col_sums = x.sum(axis=0).compute()
162```

Reductions

# Reductions along axes
total = x.sum().compute()
mean = x.mean().compute()
std = x.std().compute()

# Reduction along specific axis
row_means = x.mean(axis=1).compute()
col_sums = x.sum(axis=0).compute()
Raw lines 152 to 163
164### Slicing and Indexing
165```python
166# Standard slicing (returns Dask Array)
167subset = x[1000:5000, 2000:8000]
169# Fancy indexing
170indices = [0, 5, 10, 15]
171selected = x[indices, :]
173# Boolean indexing
174mask = x > 0.5
175filtered = x[mask]
176```

Slicing and Indexing

# Standard slicing (returns Dask Array)
subset = x[1000:5000, 2000:8000]

# Fancy indexing
indices = [0, 5, 10, 15]
selected = x[indices, :]

# Boolean indexing
mask = x > 0.5
filtered = x[mask]
Raw lines 164 to 177
178### Matrix Operations
179```python
180# Matrix multiplication
181A = da.random.random((10000, 5000), chunks=(1000, 1000))
182B = da.random.random((5000, 8000), chunks=(1000, 1000))
183C = da.matmul(A, B)
184result = C.compute()
186# Dot product
187dot_product = da.dot(A, B)
189# Transpose
190AT = A.T
191```

Matrix Operations

# Matrix multiplication
A = da.random.random((10000, 5000), chunks=(1000, 1000))
B = da.random.random((5000, 8000), chunks=(1000, 1000))
C = da.matmul(A, B)
result = C.compute()

# Dot product
dot_product = da.dot(A, B)

# Transpose
AT = A.T
Raw lines 178 to 192
193### Linear Algebra
194```python
195# Exact SVD/QR need a single column of chunks here.
196# Check that a full-width block and decomposition workspaces fit memory.
197import dask
198A = da.random.default_rng(42).random((40, 6), chunks=(10, 6))
199U, s, Vt = da.linalg.svd(A)
200U_computed, s_computed, Vt_computed = dask.compute(U, s, Vt)
202# QR decomposition
203Q, R = da.linalg.qr(A)
204Q_computed, R_computed = dask.compute(Q, R)
206# Note: Only some linalg operations are available
207```

Linear Algebra

# Exact SVD/QR need a single column of chunks here.
# Check that a full-width block and decomposition workspaces fit memory.
import dask
A = da.random.default_rng(42).random((40, 6), chunks=(10, 6))
U, s, Vt = da.linalg.svd(A)
U_computed, s_computed, Vt_computed = dask.compute(U, s, Vt)

# QR decomposition
Q, R = da.linalg.qr(A)
Q_computed, R_computed = dask.compute(Q, R)

# Note: Only some linalg operations are available
Raw lines 193 to 208
209### Reshaping and Manipulation
210```python
211# Reshape
212x = da.random.random((10000, 10000), chunks=(1000, 1000))
213reshaped = x.reshape(5000, 20000)
215# Transpose
216transposed = x.T
218# Concatenate
219x1 = da.random.random((5000, 10000), chunks=(1000, 1000))
220x2 = da.random.random((5000, 10000), chunks=(1000, 1000))
221combined = da.concatenate([x1, x2], axis=0)
223# Stack
224stacked = da.stack([x1, x2], axis=0)
225```

Reshaping and Manipulation

# Reshape
x = da.random.random((10000, 10000), chunks=(1000, 1000))
reshaped = x.reshape(5000, 20000)

# Transpose
transposed = x.T

# Concatenate
x1 = da.random.random((5000, 10000), chunks=(1000, 1000))
x2 = da.random.random((5000, 10000), chunks=(1000, 1000))
combined = da.concatenate([x1, x2], axis=0)

# Stack
stacked = da.stack([x1, x2], axis=0)
Raw lines 209 to 226
227## Chunking Strategy
229Chunking is critical for Dask Array performance.

Chunking Strategy

Chunking is critical for Dask Array performance.

Raw lines 227 to 230
231### Chunk Size Guidelines
233**Good Chunk Sizes**:
234- Measure uncompressed bytes (`prod(chunk_shape) * dtype.itemsize`)
235- Start around 100 MiB for substantial arrays, then measure task duration and peak memory
236- Budget concurrent input, output, and temporary buffers; align chunks with storage
238**Example Calculation**:
239```python
240# For float64 data (8 bytes per element)
241# Target 100 MB chunks: 100 MB / 8 bytes = 12.5M elements
243# For 2D array (10000, 10000):
244x = da.random.random((10000, 10000), chunks=(1000, 1000)) # ~8 MB per chunk
245```

Chunk Size Guidelines

Good Chunk Sizes:

  • Measure uncompressed bytes (prod(chunk_shape) * dtype.itemsize)
  • Start around 100 MiB for substantial arrays, then measure task duration and peak memory
  • Budget concurrent input, output, and temporary buffers; align chunks with storage

Example Calculation:

# For float64 data (8 bytes per element)
# Target 100 MB chunks: 100 MB / 8 bytes = 12.5M elements

# For 2D array (10000, 10000):
x = da.random.random((10000, 10000), chunks=(1000, 1000))  # ~8 MB per chunk
Raw lines 231 to 246
247### Viewing Chunk Structure
248```python
249# Check chunks
250print(x.chunks) # ((1000, 1000, ...), (1000, 1000, ...))
252# Number of chunks
253print(x.npartitions)
255# Chunk sizes in bytes
256print(x.nbytes / x.npartitions)
257```

Viewing Chunk Structure

# Check chunks
print(x.chunks)  # ((1000, 1000, ...), (1000, 1000, ...))

# Number of chunks
print(x.npartitions)

# Chunk sizes in bytes
print(x.nbytes / x.npartitions)
Raw lines 247 to 258
259### Rechunking
260```python
261# Change chunk sizes
262x = da.random.random((10000, 10000), chunks=(500, 500))
263x_rechunked = x.rechunk((2000, 2000))
265# Rechunk specific dimension
266x_rechunked = x.rechunk({0: 2000, 1: 'auto'})
267```

Rechunking

# Change chunk sizes
x = da.random.random((10000, 10000), chunks=(500, 500))
x_rechunked = x.rechunk((2000, 2000))

# Rechunk specific dimension
x_rechunked = x.rechunk({0: 2000, 1: 'auto'})
Raw lines 259 to 268
269## Custom Operations with map_blocks
271For operations not available in Dask, use `map_blocks`:
273```python
274import dask.array as da
275import numpy as np
277def custom_function(block):
278 # Apply custom NumPy operation
279 return np.log1p(block)
281x = da.random.random((10000, 10000), chunks=(1000, 1000))
282result = da.map_blocks(custom_function, x, dtype='float64',
283 meta=np.empty((0, 0), dtype='float64'))
285# Compute
286output = result.compute()
287```
289For a global FFT use `da.fft.fft2(x.rechunk({0: -1, 1: -1}))` only if that full transform fits memory: each transform axis must have one chunk. Mapping `np.fft.fft2` independently over chunks changes the scientific calculation.

Custom Operations with map_blocks

For operations not available in Dask, use map_blocks:

import dask.array as da
import numpy as np

def custom_function(block):
    # Apply custom NumPy operation
    return np.log1p(block)

x = da.random.random((10000, 10000), chunks=(1000, 1000))
result = da.map_blocks(custom_function, x, dtype='float64',
                       meta=np.empty((0, 0), dtype='float64'))

# Compute
output = result.compute()

For a global FFT use da.fft.fft2(x.rechunk({0: -1, 1: -1})) only if that full transform fits memory: each transform axis must have one chunk. Mapping np.fft.fft2 independently over chunks changes the scientific calculation.

Raw lines 269 to 290
291### map_blocks with Different Output Shape
292```python
293def reduction_function(block):
294 # Preserve rank: one 1x1 output for each two-dimensional input block
295 return np.array([[block.mean()]], dtype='float64')
297result = da.map_blocks(reduction_function, x, dtype='float64',
298 chunks=(1, 1), meta=np.empty((0, 0), dtype='float64'))
299# result.shape == x.numblocks. Do not average these means unweighted when
300# edge chunks have different sizes; x.mean() is the correct global reduction.
301# drop_axis concatenates chunks along dropped axes before calling the function.
302```

map_blocks with Different Output Shape

def reduction_function(block):
    # Preserve rank: one 1x1 output for each two-dimensional input block
    return np.array([[block.mean()]], dtype='float64')

result = da.map_blocks(reduction_function, x, dtype='float64',
                       chunks=(1, 1), meta=np.empty((0, 0), dtype='float64'))
# result.shape == x.numblocks. Do not average these means unweighted when
# edge chunks have different sizes; x.mean() is the correct global reduction.
# drop_axis concatenates chunks along dropped axes before calling the function.
Raw lines 291 to 303
304## Lazy Evaluation and Computation

Lazy Evaluation and Computation

Raw lines 304 to 305
306### Lazy Operations
307```python
308# All operations are lazy (instant, no computation)
309x = da.random.random((10000, 10000), chunks=(1000, 1000))
310y = x + 100
311z = y.mean(axis=0)
312result = z * 2
314# Nothing computed yet, just task graph built
315```

Lazy Operations

# All operations are lazy (instant, no computation)
x = da.random.random((10000, 10000), chunks=(1000, 1000))
y = x + 100
z = y.mean(axis=0)
result = z * 2

# Nothing computed yet, just task graph built
Raw lines 306 to 316
317### Triggering Computation
318```python
319# Compute single result
320final = result.compute()
322# Compute multiple results efficiently
323result1, result2 = dask.compute(operation1, operation2)
324```

Triggering Computation

# Compute single result
final = result.compute()

# Compute multiple results efficiently
result1, result2 = dask.compute(operation1, operation2)
Raw lines 317 to 325
326### Persist in Memory
327```python
328# Keep intermediate results in memory
329x_cached = x.persist()
331# Reuse cached results
332y1 = (x_cached + 10).compute()
333y2 = (x_cached * 2).compute()
334```

Persist in Memory

# Keep intermediate results in memory
x_cached = x.persist()

# Reuse cached results
y1 = (x_cached + 10).compute()
y2 = (x_cached * 2).compute()
Raw lines 326 to 335
336## Saving Results

Saving Results

Raw lines 336 to 337
338### To NumPy
339```python
340# Convert to NumPy (loads all in memory)
341numpy_array = dask_array.compute()
342```

To NumPy

# Convert to NumPy (loads all in memory)
numpy_array = dask_array.compute()
Raw lines 338 to 343
344### To Disk
345```python
346# Save to Zarr; mode='w-' fails if output exists. mode='w' replaces it.
347# **zarr_array_kwargs means direct keywords, e.g. zarr_format=3, not a nested dict.
348da.to_zarr(x, 'output.zarr', mode='w-', zarr_format=3)
350# Save to HDF5
351import h5py
352with h5py.File('output.hdf5', mode='w') as f:
353 dset = f.create_dataset('/data', shape=x.shape, dtype=x.dtype)
354 da.store(x, dset, scheduler='threads') # Local open HDF5 handle
355```

To Disk

# Save to Zarr; mode='w-' fails if output exists. mode='w' replaces it.
# **zarr_array_kwargs means direct keywords, e.g. zarr_format=3, not a nested dict.
da.to_zarr(x, 'output.zarr', mode='w-', zarr_format=3)

# Save to HDF5
import h5py
with h5py.File('output.hdf5', mode='w') as f:
    dset = f.create_dataset('/data', shape=x.shape, dtype=x.dtype)
    da.store(x, dset, scheduler='threads')  # Local open HDF5 handle
Raw lines 344 to 356
357## Performance Considerations

Performance Considerations

Raw lines 357 to 358
359### Efficient Operations
360- Element-wise operations: Very efficient
361- Reductions with parallelizable operations: Efficient
362- Slicing along chunk boundaries: Efficient
363- Matrix operations with good chunk alignment: Efficient

Efficient Operations

  • Element-wise operations: Very efficient
  • Reductions with parallelizable operations: Efficient
  • Slicing along chunk boundaries: Efficient
  • Matrix operations with good chunk alignment: Efficient
Raw lines 359 to 364
365### Expensive Operations
366- Slicing across many chunks: Requires data movement
367- Operations requiring global sorting: Not well supported
368- Extremely irregular access patterns: Poor performance
369- Operations with poor chunk alignment: Requires rechunking

Expensive Operations

  • Slicing across many chunks: Requires data movement
  • Operations requiring global sorting: Not well supported
  • Extremely irregular access patterns: Poor performance
  • Operations with poor chunk alignment: Requires rechunking
Raw lines 365 to 370
371### Optimization Tips
373**1. Choose Good Chunk Sizes**
374```python
375# Aim for balanced chunks
376# About 100 MB of float64 per chunk (2500 * 5000 * 8)
377x = da.random.random((100000, 10000), chunks=(2500, 5000))
378```
380**2. Align Chunks for Operations**
381```python
382# Make sure chunks align for operations
383x = da.random.random((10000, 10000), chunks=(1000, 1000))
384y = da.random.random((10000, 10000), chunks=(1000, 1000)) # Aligned
385z = x + y # Efficient
386```
388**3. Use Appropriate Scheduler**
389```python
390# Arrays work well with threaded scheduler (default)
391# Shared memory access is efficient
392result = x.compute() # Uses threads by default
393```
395**4. Minimize Data Transfer**
396```python
397# Better: Compute on each chunk, then transfer results
398means = x.mean(axis=1).compute() # Transfers less data
400# Worse: Transfer all data then compute
401x_numpy = x.compute()
402means = x_numpy.mean(axis=1) # Transfers more data
403```

Optimization Tips

1. Choose Good Chunk Sizes

# Aim for balanced chunks
# About 100 MB of float64 per chunk (2500 * 5000 * 8)
x = da.random.random((100000, 10000), chunks=(2500, 5000))

2. Align Chunks for Operations

# Make sure chunks align for operations
x = da.random.random((10000, 10000), chunks=(1000, 1000))
y = da.random.random((10000, 10000), chunks=(1000, 1000))  # Aligned
z = x + y  # Efficient

3. Use Appropriate Scheduler

# Arrays work well with threaded scheduler (default)
# Shared memory access is efficient
result = x.compute()  # Uses threads by default

4. Minimize Data Transfer

# Better: Compute on each chunk, then transfer results
means = x.mean(axis=1).compute()  # Transfers less data

# Worse: Transfer all data then compute
x_numpy = x.compute()
means = x_numpy.mean(axis=1)  # Transfers more data
Raw lines 371 to 404
405## Common Patterns

Common Patterns

Raw lines 405 to 406
407### Image Processing
408```python
409import dask.array as da
411# Load large image stack
412images = da.from_zarr('images.zarr')
414# Assume axes (frame, y, x): filter spatial axes only, in floating point.
415# sigma=2 and radius=8 must agree with the overlap depth.
416from scipy.ndimage import gaussian_filter
417images = images.astype('float64')
419def apply_gaussian(block):
420 return gaussian_filter(block, sigma=(0, 2, 2), radius=(0, 8, 8), mode='reflect')
422filtered = da.map_overlap(apply_gaussian, images, depth=(0, 8, 8),
423 boundary='reflect', trim=True, dtype='float64')
424# Compare to SciPy on a fixture spanning chunk boundaries and image edges.
426# Compute statistics
427mean_intensity = filtered.mean().compute()
428```

Image Processing

import dask.array as da

# Load large image stack
images = da.from_zarr('images.zarr')

# Assume axes (frame, y, x): filter spatial axes only, in floating point.
# sigma=2 and radius=8 must agree with the overlap depth.
from scipy.ndimage import gaussian_filter
images = images.astype('float64')

def apply_gaussian(block):
    return gaussian_filter(block, sigma=(0, 2, 2), radius=(0, 8, 8), mode='reflect')

filtered = da.map_overlap(apply_gaussian, images, depth=(0, 8, 8),
                          boundary='reflect', trim=True, dtype='float64')
# Compare to SciPy on a fixture spanning chunk boundaries and image edges.

# Compute statistics
mean_intensity = filtered.mean().compute()
Raw lines 407 to 429
430### Scientific Computing
431```python
432# Large-scale numerical simulation
433x = da.random.default_rng(42).random((1000, 1000), chunks=(250, 250))
435# Apply iterative computation
436for i in range(num_iterations):
437 x = da.exp(-x) * da.sin(x)
438 x = x.persist() # Budget all retained chunks and temporary arrays
440# Final result
441result = x.compute()
442```

Scientific Computing

# Large-scale numerical simulation
x = da.random.default_rng(42).random((1000, 1000), chunks=(250, 250))

# Apply iterative computation
for i in range(num_iterations):
    x = da.exp(-x) * da.sin(x)
    x = x.persist()  # Budget all retained chunks and temporary arrays

# Final result
result = x.compute()
Raw lines 430 to 443
444### Data Analysis
445```python
446# Load large dataset
447data = da.from_zarr('measurements.zarr')
449# Compute statistics
450mean = data.mean(axis=0)
451std = data.std(axis=0)
452normalized = (data - mean) / da.where(std > 0, std, 1)
453# Define a separate policy for NaNs; constant columns become zero.
455# Save normalized data
456da.to_zarr(normalized, 'normalized.zarr', mode='w-')
457```

Data Analysis

# Load large dataset
data = da.from_zarr('measurements.zarr')

# Compute statistics
mean = data.mean(axis=0)
std = data.std(axis=0)
normalized = (data - mean) / da.where(std > 0, std, 1)
# Define a separate policy for NaNs; constant columns become zero.

# Save normalized data
da.to_zarr(normalized, 'normalized.zarr', mode='w-')
Raw lines 444 to 458
459## Integration with Other Tools

Integration with Other Tools

Raw lines 459 to 460
461### XArray
462```python
463import xarray as xr
464import dask.array as da
466# XArray wraps Dask arrays with labeled dimensions
467data = da.random.random((1000, 2000, 3000), chunks=(100, 200, 300))
468dataset = xr.DataArray(
469 data,
470 dims=['time', 'y', 'x'],
471 coords={'time': range(1000), 'y': range(2000), 'x': range(3000)}
473```

XArray

import xarray as xr
import dask.array as da

# XArray wraps Dask arrays with labeled dimensions
data = da.random.random((1000, 2000, 3000), chunks=(100, 200, 300))
dataset = xr.DataArray(
    data,
    dims=['time', 'y', 'x'],
    coords={'time': range(1000), 'y': range(2000), 'x': range(3000)}
)
Raw lines 461 to 474
475### Scikit-learn (via Dask-ML)
476```python
477# Some scikit-learn compatible operations
478from dask_ml.preprocessing import StandardScaler
480X = da.random.random((10000, 100), chunks=(1000, 100))
481scaler = StandardScaler()
482X_scaled = scaler.fit_transform(X)
483```

Scikit-learn (via Dask-ML)

# Some scikit-learn compatible operations
from dask_ml.preprocessing import StandardScaler

X = da.random.random((10000, 100), chunks=(1000, 100))
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
Raw lines 475 to 484
485## Debugging Tips

Debugging Tips

Raw lines 485 to 486
487### Visualize Task Graph
488```python
489# Visualize computation graph (for small arrays)
490x = da.random.random((100, 100), chunks=(10, 10))
491y = x + 1
492y.visualize(filename='graph.png') # Needs Python graphviz and system Graphviz
493```

Visualize Task Graph

# Visualize computation graph (for small arrays)
x = da.random.random((100, 100), chunks=(10, 10))
y = x + 1
y.visualize(filename='graph.png')  # Needs Python graphviz and system Graphviz
Raw lines 487 to 494
495### Check Array Properties
496```python
497# Inspect before computing
498print(f"Shape: {x.shape}")
499print(f"Dtype: {x.dtype}")
500print(f"Chunks: {x.chunks}")
501print(f"Number of tasks: {len(x.__dask_graph__())}")
502```

Check Array Properties

# Inspect before computing
print(f"Shape: {x.shape}")
print(f"Dtype: {x.dtype}")
print(f"Chunks: {x.chunks}")
print(f"Number of tasks: {len(x.__dask_graph__())}")
Raw lines 495 to 503
504### Test on Small Arrays First
505```python
506# Test logic on small array
507small_x = da.random.random((100, 100), chunks=(50, 50))
508result_small = computation(small_x).compute()
510# Validate, then scale
511large_x = da.random.default_rng(42).random((1000, 1000), chunks=(250, 250))
512result_large = computation(large_x).mean().compute() # Or write partitioned output
513```

Test on Small Arrays First

# Test logic on small array
small_x = da.random.random((100, 100), chunks=(50, 50))
result_small = computation(small_x).compute()

# Validate, then scale
large_x = da.random.default_rng(42).random((1000, 1000), chunks=(250, 250))
result_large = computation(large_x).mean().compute()  # Or write partitioned output
Raw lines 504 to 513