Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

1. What Is Dask?

Dask is a tool for computer programs. Dask takes one large task and divides the task into smaller tasks to complete the with fewer or limited resources. Dask is written in the Python programming language and many scientists use Python for data analysis.

Dask gives users two types of tools:

The Task Graph

Dask represents a large task as a task graph instead of running the task immediately. A task graph has two parts:

Dask builds the task graph first and runs the task graph later. This method is called lazy evaluation. For example, when a user creates a large Dask Array or Dask DataFrame, Dask does not process any data yet. Dask records one node for each piece of the array or table. When the code calls a method, for example .mean(), Dask adds more nodes to the graph: one node for each partial result, and one final node that combines the partial results. Section 10 shows this pattern with a concrete example: one node for every 5,000 x 5,000 chunk of a 100,000 x 100,000 array, plus the nodes that combine the chunks into the final mean and standard deviation.

Dask does not run any node in the graph until the code calls .compute() or .persist(). At that point, Dask sends the complete task graph to a scheduler. The scheduler finds every node with no unfinished dependency and assigns that node to an available worker (Section 4 describes workers). A worker runs its assigned node and returns the result to the scheduler. When a node’s dependencies are all finished, the scheduler marks that node as ready and assigns the node to a worker. This process repeats until every node in the graph is complete.

Because two independent nodes have no edge between them, the scheduler can assign the two nodes to two different workers at the same time. This is why Dask can process many pieces of one large task in parallel.


2. Why Scientists Use Dask

Many research projects use large amounts of data. A seismologist can have years of continuous ground-motion data from hundreds of sensors. A climate scientist can have decades of satellite images. A genomics researcher can have millions of DNA sequences to compare.

Three problems occur frequently:

  1. The data does not fit in memory. GeoLab can give a user up to 29 gigabytes of RAM. A dataset can be 500 gigabytes. The user cannot load all the data at the same time.

  2. The analysis takes too much time. Even when the data fits in memory, a calculation on all the data, one step at a time, can take hours or days.

  3. The GeoLab instance does not have enough resources. Some tasks are too large for a single powerful environment to complete in a reasonable time.

Dask helps solve these three problems:

Dask uses the same commands as numpy, pandas, and standard Python functions. Users do not have to learn a new programming method to get these benefits. Users write standard Python code. Dask divides the work and runs the work efficiently.


3. Local Dask: Using GeoLab’s Resources

The Dask local scheduler is the simplest way to use Dask in GeoLab. This code creates a local instance of Dask:

from dask.distributed import Client

client = Client()

The dask.distributed package provides Dask’s client, scheduler, and worker system. This package creates a cluster whether the cluster is local, as in this section, or spread across many machines, as Section 4 describes. The package name stays the same in both cases.

Dask starts workers on the cores available in GeoLab. For example, if GeoLab gives a user 8 cores, Dask can create workers to use these cores. The workers complete tasks at the same time.

When to Use Local Dask

Use local Dask for these tasks:

Limits of Local Dask

A local Dask cluster depends on the hardware available to it. If a dataset needs 500 gigabytes of memory and hundreds of cores to finish in a reasonable time, GeoLab cannot support the task. In this case, use distributed Dask.


4. Dask Distributed: Using Many Computers at Once

Dask Distributed uses the same method as local Dask, but on a larger scale. This document uses “Dask Distributed” as the name for a cluster spread across many machines. This name is different from dask.distributed, the Python package name. Section 3 already used the dask.distributed package to create a local cluster; the same package creates a cluster whether the cluster is local or spread across many machines.

Workers do not stay on one machine. Workers can operate on many separate machines, frequently in the cloud. A network connects the machines.

Dask Distributed has three parts:

Workers can operate on many physical machines in a data center. As a result, a distributed Dask cluster can have more memory and more processing power than one computer. A distributed cluster can have hundreds or thousands of cores and terabytes of memory.

When to Use Distributed Dask

Use distributed Dask for these tasks:

Limits of Distributed Dask

Distributed computing has costs:

Because of these costs, a distributed cluster can operate slower than a local instance for small tasks. The time to set up a cluster and the time to move data over the network can take longer than the task itself.


5. Local vs. Distributed: Which One Should You Use?

Local DaskDask Distributed
Location of workersGeoLab’s own coresMany machines, frequently in the cloud
Setup timeAlmost instantSeconds to a few minutes (workers must start)
Maximum job sizeLimited by one machineCan scale to very large jobs
CostNo cost (part of GeoLab)Frequently costs money or uses shared resources
Best useLearning, small-to-medium data, quick analysisLarge datasets, shared classroom or lab computing, time-critical jobs

Rule: start with local Dask. Test the code on a small part of the data. Confirm that the logic is correct. Confirm that the task is too large or too slow for GeoLab’s local resources. Then switch to a distributed cluster to scale up the task. This method prevents users from spending cloud computing time to debug a simple error.


6. Connect to Dask Distributed in GeoLab

GeoLab is a cloud-based JupyterHub platform, built in partnership with 2i2c. GeoLab gives researchers access to a shared pool of computing resources. A user can request a Dask Gateway cluster instead of running Dask only locally in a GeoLab session. A Dask Gateway cluster is a group of additional worker machines that run on shared cloud infrastructure. A user can connect to the cluster from a notebook.

The dask_gateway package manages requests for a distributed Dask cluster.

Procedure: Connect in GeoLab

Step 1: Connect to the Gateway. The Gateway object connects to the GeoLab cluster management system.

from dask_gateway import Gateway

# GeoLab is already configured for the Gateway.
# Users do not need to enter addresses or passwords.
gateway = Gateway()

Step 2: Show the available cluster options (optional). GeoLab lets users select settings, for example, the amount of memory for each worker.

options = gateway.cluster_options()
options   # Shows an interactive form of cluster settings in Jupyter

Step 3: Create or reuse a cluster. This step reserves worker machines for the user. Check if a cluster is already running before you start a new cluster. If you do not check, you can create more than one cluster and pay for computing resources that you do not use.

clusters = gateway.list_clusters()

if clusters:
    # Reuse a cluster that started earlier
    cluster = gateway.connect(clusters[0].name)
    print(f"Connected to existing cluster: {clusters[0].name}")
else:
    # No cluster exists, so create a new cluster
    cluster = gateway.new_cluster()
    # adapt() tells Dask to increase or decrease the number
    # of workers between 2 and 10, based on the workload
    cluster.adapt(minimum=2, maximum=10)
    print("Created new cluster")

Step 4: Get a client connected to the cluster. The client sends the user’s Python code to the cluster.

client = cluster.get_client()
client   # Shows a summary: number of workers, cores, memory, dashboard link

The client widget includes a link to the Dask dashboard. The dashboard is a live, browser-based view of task execution, worker activity, and memory use. Use the dashboard to understand how a computation runs, not only whether it finished. For more information about the dashboard, refer to the Dask dashboard documentation.

Step 5: Wait for the workers to start (optional). Workers can take time to start. A new cluster takes more time to start than an existing cluster. Wait until at least two workers are ready before you send more tasks.

client.wait_for_workers(n_workers=2)

Step 6: Run the analysis. After the client connects, Dask automatically sends the work to the distributed cluster instead of the local instance. This applies to any code that uses Dask Array, Dask DataFrame, delayed, or client.map.

Step 7: Close the cluster. When the task is complete, close the cluster. This action releases the shared computing resources for other users.

client.close()
cluster.close()   # Necessary only if you created your own Gateway cluster

7. Dask DataFrame

Section 6 connects a client to a distributed cluster. This section and the next two sections show how to use that client. Dask DataFrame, Dask Futures, and .persist() each send work through the same client to the same cluster.

A Dask DataFrame divides one large table into many smaller pandas DataFrames. Dask calls each smaller table a partition. Dask can process each partition separately.

Use a Dask DataFrame for tabular data too large for the available memory, for example a table with millions of rows.

import numpy as np
import pandas as pd
import dask.dataframe as dd

# Create a pandas DataFrame with example data.
station_data = pd.DataFrame({
    "station": np.random.choice(["STA01", "STA02", "STA03", "STA04"], size=200_000),
    "amplitude": np.random.random(200_000),
})

# Convert the pandas DataFrame into a Dask DataFrame with 8 partitions.
ddf = dd.from_pandas(station_data, npartitions=8)

# This calculation is lazy. Dask does not run it yet.
mean_amplitude = ddf.groupby("station")["amplitude"].mean()

# .compute() runs the calculation and returns a pandas result.
result = mean_amplitude.compute()
print(result)

A Dask DataFrame can also read data directly from files, instead of from an existing pandas DataFrame. Two common methods are:

ddf = dd.read_csv("data/station-observations-*.csv")
ddf = dd.read_parquet("data/station-observations/")

Dask reads only the file metadata at first. Dask reads each partition only when the code calls .compute() or .persist().


8. Dask Futures

Dask Delayed builds a complete task graph before any task runs. Dask Futures use a different method: the code sends each task to the scheduler immediately, instead of building the complete graph first.

Use Dask Futures when the workflow does not know every task in advance, or when the workflow must act on each result as soon as it finishes, instead of waiting for every result together.

These examples reuse a small function that squares a number:

def square_it(n):
    return n * n

The client.submit() method sends one task to the scheduler and returns a Future immediately. A Future represents a task that is pending, running, or finished.

# Submit one task and receive a Future immediately.
future = client.submit(square_it, 10)

print(future.status)   # for example, "pending" or "finished"

result = future.result()   # Waits for the task and returns its value
print(result)

To submit the same function across many inputs, use client.map():

numbers = list(range(20))
futures = client.map(square_it, numbers)

To collect every result at the same time, use client.gather():

results = client.gather(futures)

To process each result as soon as it finishes, instead of waiting for every result, use as_completed():

from dask.distributed import as_completed

for finished in as_completed(futures):
    result = finished.result()
    print(result)
Dask DelayedDask Futures
ExecutionLazy: builds a task graph firstEager: sends each task immediately
Requires.compute() to run the graphAn active client
Best useA workflow the user can define completely in advanceA workflow that changes while it runs, or that needs results as they finish

9. Compute vs. Persist

Two methods run a Dask task graph: .compute() and .persist(). The methods store the result in different places.

from dask.distributed import wait

# Build a lazy intermediate result.
filtered = ddf[ddf["amplitude"] >= 0.5]

# persist() starts the calculation now and stores the result on the workers.
filtered = filtered.persist()

# wait() pauses until every partition finishes.
wait(filtered)

# A later calculation reuses the persisted result without repeating the filter.
mean_result = filtered["amplitude"].mean().compute()
MethodResult locationReturned objectBest use
.compute()The notebookA Python, NumPy, or pandas objectA small, final result
.persist()Worker memoryA Dask objectAn intermediate result the workflow reuses

10. A Full Working Example

This example shows a complete procedure that a user can run in GeoLab. The procedure connects to a distributed cluster, creates a large array of random numbers, calculates statistics on the array, and closes the cluster. The comments in the code explain each step.

# ------------------------------------------------------------------
# 1. Connect to GeoLab's distributed Dask cluster through the Gateway
# ------------------------------------------------------------------
from dask_gateway import Gateway

gateway = Gateway()  # Connects to GeoLab's shared Dask cluster manager automatically

clusters = gateway.list_clusters()

if clusters:
    # Reuse an existing cluster if one is available
    cluster = gateway.connect(clusters[0].name)
    print(f"Connected to existing cluster: {clusters[0].name}")
else:
    # Request a new cluster
    cluster = gateway.new_cluster()
    # Let the cluster scale automatically between 2 and 6 workers
    # based on the workload
    cluster.adapt(minimum=2, maximum=6)
    print("Created new cluster")

# The client sends work to the cluster
client = cluster.get_client()

# Confirm at least 2 workers are ready before the task starts
client.wait_for_workers(n_workers=2)
print(client)  # Shows a summary: number of workers, total cores, total memory

# ------------------------------------------------------------------
# 2. Create a large dataset with Dask Array
#    (this array is too large for GeoLab's memory,
#     but Dask processes the array in chunks)
# ------------------------------------------------------------------
import dask.array as da

# Create a 100,000 x 100,000 array of random numbers.
# This array has 10 billion numbers. Dask divides the array into
# chunks of 5,000 x 5,000 to process the array piece by piece.
x = da.random.random((100_000, 100_000), chunks=(5_000, 5_000))

print(x)  # Dask does not calculate the array yet -- Dask creates a plan
          # (this method is called "lazy evaluation")

# ------------------------------------------------------------------
# 3. Run the calculations
#    Dask does not run a calculation on the cluster until the code
#    calls .compute() or .persist() -- before this step, Dask only
#    builds the task graph
# ------------------------------------------------------------------
mean_value = x.mean()          # still only a plan at this point
std_value = x.std()            # still only a plan at this point

# .compute() sends the task graph to the scheduler. The scheduler
# assigns the work to the workers on the cluster, then collects
# the final result
result_mean = mean_value.compute()
result_std = std_value.compute()

print(f"Mean:  {result_mean:.4f}")
print(f"Stdev: {result_std:.4f}")

# ------------------------------------------------------------------
# 4. Run a custom function on many pieces of data with
#    client.map() -- use this method when the calculation is not
#    a simple array or dataframe operation, but a custom Python function
# ------------------------------------------------------------------
def square_it(n):
    return n * n

numbers = list(range(20))
futures = client.map(square_it, numbers)   # Sends 20 tasks to the cluster
results = client.gather(futures)           # Collects the results

print(results)  # [0, 1, 4, 9, 16, 25, ... 361]

# ------------------------------------------------------------------
# 5. Close the cluster to release the shared resources
#    (necessary -- if you do not close the cluster, the workers
#    continue to run and use shared computing resources)
# ------------------------------------------------------------------
client.close()
cluster.close()
print("Cluster closed.")

Explanation of the Example


11. Summary

Dask converts one large, slow task into many small, fast tasks. The tasks run at the same time.

The code that a user writes changes very little between local and distributed use. The user changes only the connection method. Dask manages the rest of the task.