# Parallel computing and large dataset handling using Dask
import pyarrow # dask.dataframe needs pyarrow
import dask.dataframe as dd
import pandas as pd
import numpy as np
# Generate sample data
dates = pd.date_range(start="2023-01-01", periods=10000, freq="h")
temperature = 20 + 10 * np.random.randn(len(dates))
precipitation = np.random.rand(len(dates))
# Create a Pandas DataFrame
df = pd.DataFrame({
"date": dates,
"temperature": temperature,
"precipitation": precipitation
})
# Convert the Pandas DataFrame to a Dask DataFrame
ddf = dd.from_pandas(df, npartitions=10)
# Calculate the mean temperature per day
ddf["date"] = ddf["date"].dt.date # Convert to date only (without time)
mean_temp_per_day = ddf.groupby("date").temperature.mean().compute()
print("Mean Temperature Per Day:")
mean_temp_per_day.head()Dask splits work on data into many small pieces and runs them as a graph
of tasks. dask.dataframe mirrors the pandas API on
a table cut into partitions, dask.array does the same for
NumPy arrays cut into chunks, and dask.delayed
turns your own functions into tasks. Operations are lazy: they build
the graph, and nothing runs until you call .compute(). On a laptop or
a cluster, people use it for data too big for memory and to spread work
across cores. On this page it runs in your browser, so you can try it
without installing anything.
Run the example first, then paste any snippet below into a new cell to try it.
The example imports pyarrow first because dask.dataframe needs it.
It builds 10,000 hourly readings in pandas, with timestamps from
pd.date_range() and random temperatures and precipitation.
dd.from_pandas(df, npartitions=10) splits the table into 10 partitions
of 1,000 rows, each an ordinary pandas DataFrame. .dt.date drops the
time of day, and groupby("date").temperature.mean() describes the
daily average without computing it. .compute() runs the tasks and
returns a pandas Series with one value per day, 417 in all, and the last
line shows the first five. The data has no seed, so the numbers change
on every run.
A Dask DataFrame takes most pandas methods. Printing a result before
compute() shows its structure, with ... where the values will be:
import numpy as np
import pandas as pd
import pyarrow # dask.dataframe needs pyarrow
import dask.dataframe as dd
rng = np.random.default_rng(seed=0)
df = pd.DataFrame({
"store": rng.choice(["north", "south", "east"], 3000),
"amount": rng.integers(5, 200, 3000),
})
ddf = dd.from_pandas(df, npartitions=6)
big = ddf[ddf.amount > 100]
summary = big.groupby("store").amount.agg(["count", "mean"])
print(summary) # the plan, no numbers yet
print(summary.compute().round(1)) # a pandas DataFrame
dd.read_csv() accepts a wildcard and treats all matching files as one
DataFrame. This writes three monthly files first:
import pandas as pd
import pyarrow # dask.dataframe needs pyarrow
import dask.dataframe as dd
for month in [1, 2, 3]:
pd.DataFrame({
"month": month,
"product": ["tea", "coffee", "cocoa"],
"units": [10 * month, 20 + month, 5],
}).to_csv(f"sales-{month}.csv", index=False)
ddf = dd.read_csv("sales-*.csv")
print(ddf.npartitions)
print(ddf.groupby("product").units.sum().compute())
Small files like these become one partition each.
da.from_array() cuts a NumPy array into blocks. Reductions such as
mean() work block by block and then combine the partial results:
import numpy as np
import dask.array as da
rng = np.random.default_rng(seed=0)
data = rng.normal(10, 2, size=(4000, 300))
x = da.from_array(data, chunks=(1000, 300)) # 4 blocks of 1,000 rows
col_means = x.mean(axis=0)
print(x.numblocks)
print(col_means) # lazy until compute()
result = col_means.compute()
print(result[:3].round(3), np.allclose(result, data.mean(axis=0)))
@dask.delayed makes a function return a placeholder instead of
running. Calls on placeholders chain into a graph, and compute() runs
it:
import dask
@dask.delayed
def load(n):
return list(range(n))
@dask.delayed
def total(values):
return sum(values)
parts = [total(load(n)) for n in [10, 20, 30]]
grand = dask.delayed(sum)(parts)
print(type(grand).__name__) # Delayed: nothing has run yet
print(grand.compute()) # 45 + 190 + 435
print(dask.compute(*parts)) # several results in one pass
compute(scheduler="threads") raises a ValueError. Partitions still
work, but they are processed in turn, not in parallel.dask.dataframe, as the example does. Otherwise
import dask.dataframe fails with
ModuleNotFoundError: No module named 'pyarrow'.head() looks only at the first partition. If that holds fewer rows
than you ask for, you get fewer rows and a warning. Pass
npartitions=-1 to search all of them.