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

dataframes.md

381 lines · skills/dask/references/dataframes.md at 5f2bc03

Open on GitHub
1# Dask DataFrames
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 DataFrames

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 DataFrames enable parallel processing of large tabular data by distributing work across multiple pandas DataFrames. As described in the documentation, "Dask DataFrames are a collection of many pandas DataFrames" with a supported subset of the pandas API; global operations can require shuffles or be unavailable.
8
9Since **dask 2025.1.0**, the expression-based implementation with logical query planning is the only DataFrame backend. Import from `dask.dataframe` only — avoid legacy submodule paths. DataFrame I/O requires **PyArrow 16+** (dask 2026.1.2+).

Overview

Dask DataFrames enable parallel processing of large tabular data by distributing work across multiple pandas DataFrames. As described in the documentation, "Dask DataFrames are a collection of many pandas DataFrames" with a supported subset of the pandas API; global operations can require shuffles or be unavailable.

Since dask 2025.1.0, the expression-based implementation with logical query planning is the only DataFrame backend. Import from dask.dataframe only — avoid legacy submodule paths. DataFrame I/O requires PyArrow 16+ (dask 2026.1.2+).

Raw lines 5 to 10
11## Core Concept
13A Dask DataFrame is divided into multiple pandas DataFrames (partitions) along the index:
14- Each partition is a regular pandas DataFrame
15- Operations are applied to each partition in parallel
16- Results are combined automatically

Core Concept

A Dask DataFrame is divided into multiple pandas DataFrames (partitions) along the index:

  • Each partition is a regular pandas DataFrame
  • Operations are applied to each partition in parallel
  • Results are combined automatically
Raw lines 11 to 17
18## Key Capabilities

Key Capabilities

Raw lines 18 to 19
20### Scale
21- Capacity is workload-dependent; budget partition, shuffle, and result memory
22- Handle datasets exceeding available RAM

Scale

  • Capacity is workload-dependent; budget partition, shuffle, and result memory
  • Handle datasets exceeding available RAM
Raw lines 20 to 23
24### Compatibility
25- Implements most of the pandas API
26- Easy transition from pandas code
27- Works with familiar operations

Compatibility

  • Implements most of the pandas API
  • Easy transition from pandas code
  • Works with familiar operations
Raw lines 24 to 28
29## When to Use Dask DataFrames
31**Use Dask When**:
32- Dataset exceeds available RAM
33- Computations require significant time and pandas optimization hasn't helped
34- Need to scale from prototype (pandas) to production (larger data)
35- Working with multiple files that should be processed together
37**Stick with Pandas When**:
38- Data fits comfortably in memory
39- Computations complete in subseconds
40- Simple operations without custom `.apply()` functions
41- Iterative development and exploration

When to Use Dask DataFrames

Use Dask When:

  • Dataset exceeds available RAM
  • Computations require significant time and pandas optimization hasn't helped
  • Need to scale from prototype (pandas) to production (larger data)
  • Working with multiple files that should be processed together

Stick with Pandas When:

  • Data fits comfortably in memory
  • Computations complete in subseconds
  • Simple operations without custom .apply() functions
  • Iterative development and exploration
Raw lines 29 to 42
43## Reading Data
45Dask mirrors pandas reading syntax with added support for multiple files:

Reading Data

Dask mirrors pandas reading syntax with added support for multiple files:

Raw lines 43 to 46
47### Single File
48```python
49import dask.dataframe as dd
51# Read single file
52ddf = dd.read_csv('data.csv')
53ddf = dd.read_parquet('data.parquet')
54```

Single File

import dask.dataframe as dd

# Read single file
ddf = dd.read_csv('data.csv')
ddf = dd.read_parquet('data.parquet')
Raw lines 47 to 55
56### Multiple Files
57```python
58# Read multiple files using glob patterns
59ddf = dd.read_csv('data/*.csv')
60ddf = dd.read_parquet('data/year=*/month=*/day=*.parquet')
62# Remote Parquet (requires s3fs: uv pip install s3fs)
63ddf = dd.read_parquet('s3://mybucket/data/*.parquet', storage_options={'anon': False})
64```

Multiple Files

# Read multiple files using glob patterns
ddf = dd.read_csv('data/*.csv')
ddf = dd.read_parquet('data/year=*/month=*/day=*.parquet')

# Remote Parquet (requires s3fs: uv pip install s3fs)
ddf = dd.read_parquet('s3://mybucket/data/*.parquet', storage_options={'anon': False})
Raw lines 56 to 65
66### Optimizations
67```python
68# Specify columns to read (reduces memory)
69ddf = dd.read_parquet('data.parquet', columns=['col1', 'col2'])
71# Control partitioning
72ddf = dd.read_csv('data.csv', blocksize='64MB') # About 64 MB of source bytes, not in-memory pandas size
73```

Optimizations

# Specify columns to read (reduces memory)
ddf = dd.read_parquet('data.parquet', columns=['col1', 'col2'])

# Control partitioning
ddf = dd.read_csv('data.csv', blocksize='64MB')  # About 64 MB of source bytes, not in-memory pandas size
Raw lines 66 to 74
75## Common Operations
77Most transformations build lazy graphs. Reads may sample metadata; `head`, index-division discovery, `persist`, and writes can also execute work.

Common Operations

Most transformations build lazy graphs. Reads may sample metadata; head, index-division discovery, persist, and writes can also execute work.

Raw lines 75 to 78
79### Filtering
80```python
81# Same as pandas
82filtered = ddf[ddf['column'] > 100]
83filtered = ddf.query('column > 100')
84```

Filtering

# Same as pandas
filtered = ddf[ddf['column'] > 100]
filtered = ddf.query('column > 100')
Raw lines 79 to 85
86### Column Operations
87```python
88# Add columns
89ddf['new_column'] = ddf['col1'] + ddf['col2']
91# Select columns
92subset = ddf[['col1', 'col2', 'col3']]
94# Drop columns
95ddf = ddf.drop(columns=['unnecessary_col'])
96```

Column Operations

# Add columns
ddf['new_column'] = ddf['col1'] + ddf['col2']

# Select columns
subset = ddf[['col1', 'col2', 'col3']]

# Drop columns
ddf = ddf.drop(columns=['unnecessary_col'])
Raw lines 86 to 97
98### Aggregations
99```python
100# Standard aggregations work as expected
101mean = ddf['column'].mean().compute()
102sum_total = ddf['column'].sum().compute()
103counts = ddf['category'].value_counts().compute()
104```

Aggregations

# Standard aggregations work as expected
mean = ddf['column'].mean().compute()
sum_total = ddf['column'].sum().compute()
counts = ddf['category'].value_counts().compute()
Raw lines 98 to 105
106### GroupBy
107```python
108# GroupBy operations (may require shuffle)
109grouped = ddf.groupby('category')['value'].mean().compute()
111# Multiple aggregations
112agg_result = ddf.groupby('category').agg({
113 'value': ['mean', 'sum', 'count'],
114 'amount': 'sum'
115}).compute()
116```

GroupBy

# GroupBy operations (may require shuffle)
grouped = ddf.groupby('category')['value'].mean().compute()

# Multiple aggregations
agg_result = ddf.groupby('category').agg({
    'value': ['mean', 'sum', 'count'],
    'amount': 'sum'
}).compute()
Raw lines 106 to 117
118### Joins and Merges
119```python
120# Merge DataFrames
121merged = dd.merge(ddf1, ddf2, on='key', how='left')
123# Join on index
124joined = ddf1.join(ddf2, on='key')
125```

Joins and Merges

# Merge DataFrames
merged = dd.merge(ddf1, ddf2, on='key', how='left')

# Join on index
joined = ddf1.join(ddf2, on='key')
Raw lines 118 to 126
127### Sorting
128```python
129# Sorting (expensive operation, requires data movement)
130sorted_ddf = ddf.sort_values('column')
131result = sorted_ddf.compute()
132```

Sorting

# Sorting (expensive operation, requires data movement)
sorted_ddf = ddf.sort_values('column')
result = sorted_ddf.compute()
Raw lines 127 to 133
134## Custom Operations

Custom Operations

Raw lines 134 to 135
136### Apply Functions
138**To Partitions (Efficient)**:
139```python
140# Apply function to entire partitions
141def custom_partition_function(partition_df):
142 # partition_df is a pandas DataFrame
143 return partition_df.assign(new_col=partition_df['col1'] * 2)
145ddf = ddf.map_partitions(custom_partition_function)
146```
148**To Rows (Less Efficient)**:
149```python
150# Python row calls execute within each partition; prefer vectorized operations
151ddf['result'] = ddf.apply(lambda row: custom_function(row), axis=1, meta=('result', 'float'))
152```
154**Note**: Use vectorized pandas code inside `map_partitions` when possible. Merely moving the same row loop into a partition function does not remove its Python overhead.

Apply Functions

To Partitions (Efficient):

# Apply function to entire partitions
def custom_partition_function(partition_df):
    # partition_df is a pandas DataFrame
    return partition_df.assign(new_col=partition_df['col1'] * 2)

ddf = ddf.map_partitions(custom_partition_function)

To Rows (Less Efficient):

# Python row calls execute within each partition; prefer vectorized operations
ddf['result'] = ddf.apply(lambda row: custom_function(row), axis=1, meta=('result', 'float'))

Note: Use vectorized pandas code inside map_partitions when possible. Merely moving the same row loop into a partition function does not remove its Python overhead.

Raw lines 136 to 155
156### Meta Parameter
158When Dask can't infer output structure, specify the `meta` parameter:
159```python
160# For apply operations
161ddf['new'] = ddf.apply(func, axis=1, meta=('new', 'float64'))
163# For map_partitions
164import pandas as pd
165ddf = ddf.map_partitions(func, meta=pd.DataFrame({
166 'col1': pd.Series(dtype='float64'),
167 'col2': pd.Series(dtype='int64')
168}))
169```

Meta Parameter

When Dask can't infer output structure, specify the meta parameter:

# For apply operations
ddf['new'] = ddf.apply(func, axis=1, meta=('new', 'float64'))

# For map_partitions
import pandas as pd
ddf = ddf.map_partitions(func, meta=pd.DataFrame({
    'col1': pd.Series(dtype='float64'),
    'col2': pd.Series(dtype='int64')
}))
Raw lines 156 to 170
171## Lazy Evaluation and Computation

Lazy Evaluation and Computation

Raw lines 171 to 172
173### Lazy Operations
174```python
175# These operations are lazy (instant, no computation)
176filtered = ddf[ddf['value'] > 100]
177aggregated = filtered.groupby('category')[['value']].mean()
178final = aggregated[aggregated['value'] < 500]
180# Nothing has computed yet
181```

Lazy Operations

# These operations are lazy (instant, no computation)
filtered = ddf[ddf['value'] > 100]
aggregated = filtered.groupby('category')[['value']].mean()
final = aggregated[aggregated['value'] < 500]

# Nothing has computed yet
Raw lines 173 to 182
183### Triggering Computation
184```python
185# Compute single result
186result = final.compute()
188# Compute multiple results efficiently
189result1, result2, result3 = dask.compute(
190 operation1,
191 operation2,
192 operation3
194```

Triggering Computation

# Compute single result
result = final.compute()

# Compute multiple results efficiently
result1, result2, result3 = dask.compute(
    operation1,
    operation2,
    operation3
)
Raw lines 183 to 195
196### Persist in Memory
198Persist only a reusable intermediate that fits worker memory, after projection/filtering. It blocks downstream optimizer pushdown through that boundary.
199```python
200# Keep results in distributed memory for reuse
201ddf_cached = ddf.persist()
203# Now multiple operations on ddf_cached won't recompute
204result1 = ddf_cached['value'].mean().compute()
205result2 = ddf_cached['value'].sum().compute()
206```

Persist in Memory

Persist only a reusable intermediate that fits worker memory, after projection/filtering. It blocks downstream optimizer pushdown through that boundary.

# Keep results in distributed memory for reuse
ddf_cached = ddf.persist()

# Now multiple operations on ddf_cached won't recompute
result1 = ddf_cached['value'].mean().compute()
result2 = ddf_cached['value'].sum().compute()
Raw lines 196 to 207
208## Index Management

Index Management

Raw lines 208 to 209
210### Setting Index
211```python
212# Shuffle unsorted input into sorted index partitions
213ddf = ddf.set_index('timestamp')
214# sorted=True asserts the column is ALREADY globally sorted across partitions.
215# It does not sort input. Use it only after verifying ordering and divisions.
216```

Setting Index

# Shuffle unsorted input into sorted index partitions
ddf = ddf.set_index('timestamp')
# sorted=True asserts the column is ALREADY globally sorted across partitions.
# It does not sort input. Use it only after verifying ordering and divisions.
Raw lines 210 to 217
218### Index Properties
219- Sorted index enables efficient filtering and joins
220- Index determines partitioning
221- Some operations perform better with appropriate index

Index Properties

  • Sorted index enables efficient filtering and joins
  • Index determines partitioning
  • Some operations perform better with appropriate index
Raw lines 218 to 222
223## Writing Results

Writing Results

Raw lines 223 to 224
225### To Files
226```python
227# Write to multiple files (one per partition)
228ddf.to_parquet('output/data.parquet')
229ddf.to_csv('output/data-*.csv')
231# Write to single file (forces computation and concatenation)
232ddf.compute().to_csv('output/single_file.csv')
233```

To Files

# Write to multiple files (one per partition)
ddf.to_parquet('output/data.parquet')
ddf.to_csv('output/data-*.csv')

# Write to single file (forces computation and concatenation)
ddf.compute().to_csv('output/single_file.csv')
Raw lines 225 to 234
235### To Memory (Pandas)
236```python
237# Convert to pandas (loads all data in memory)
238pdf = ddf.compute()
239```

To Memory (Pandas)

# Convert to pandas (loads all data in memory)
pdf = ddf.compute()
Raw lines 235 to 240
241## Performance Considerations

Performance Considerations

Raw lines 241 to 242
243### Efficient Operations
244- Column selection and filtering: Very efficient
245- Simple aggregations (sum, mean, count): Efficient
246- Row-wise operations on partitions: Efficient with `map_partitions`

Efficient Operations

  • Column selection and filtering: Very efficient
  • Simple aggregations (sum, mean, count): Efficient
  • Row-wise operations on partitions: Efficient with map_partitions
Raw lines 243 to 247
248### Expensive Operations
249- Sorting: Requires data shuffle across workers
250- GroupBy with many groups: May require shuffle
251- Complex joins: Depends on data distribution
252- Row-wise apply: Python row-loop overhead within partitions

Expensive Operations

  • Sorting: Requires data shuffle across workers
  • GroupBy with many groups: May require shuffle
  • Complex joins: Depends on data distribution
  • Row-wise apply: Python row-loop overhead within partitions
Raw lines 248 to 253
254### Optimization Tips
256**1. Select Columns Early**
257```python
258# Better: Read only needed columns
259ddf = dd.read_parquet('data.parquet', columns=['col1', 'col2'])
260```
262**2. Filter Before GroupBy**
263```python
264# Better: Reduce data before expensive operations
265result = ddf[ddf['year'] == 2024].groupby('category').sum().compute()
266```
268**3. Use Efficient File Formats**
269```python
270# Use Parquet instead of CSV for better performance
271ddf.to_parquet('data.parquet') # Faster, smaller, columnar
272```
274**4. Repartition Appropriately**
275```python
276# If partitions are too small
277ddf = ddf.repartition(npartitions=10)
279# If partitions are too large
280ddf = ddf.repartition(partition_size='100MB')
281```

Optimization Tips

1. Select Columns Early

# Better: Read only needed columns
ddf = dd.read_parquet('data.parquet', columns=['col1', 'col2'])

2. Filter Before GroupBy

# Better: Reduce data before expensive operations
result = ddf[ddf['year'] == 2024].groupby('category').sum().compute()

3. Use Efficient File Formats

# Use Parquet instead of CSV for better performance
ddf.to_parquet('data.parquet')  # Faster, smaller, columnar

4. Repartition Appropriately

# If partitions are too small
ddf = ddf.repartition(npartitions=10)

# If partitions are too large
ddf = ddf.repartition(partition_size='100MB')
Raw lines 254 to 282
283## Common Patterns

Common Patterns

Raw lines 283 to 284
285### ETL Pipeline
286```python
287import dask.dataframe as dd
289# Read data
290ddf = dd.read_csv('raw_data/*.csv')
292# Transform
293ddf = ddf[ddf['status'] == 'valid']
294ddf['amount'] = ddf['amount'].astype('float64')
295ddf = ddf.dropna(subset=['important_col'])
297# Aggregate
298# Named aggregations keep string column names required by Dask's Parquet writer.
299summary = ddf.groupby('category').agg(
300 amount_sum=('amount', 'sum'),
301 amount_mean=('amount', 'mean'),
302 quantity_count=('quantity', 'count'),
305# Write results
306summary.to_parquet('output/summary.parquet')
307```

ETL Pipeline

import dask.dataframe as dd

# Read data
ddf = dd.read_csv('raw_data/*.csv')

# Transform
ddf = ddf[ddf['status'] == 'valid']
ddf['amount'] = ddf['amount'].astype('float64')
ddf = ddf.dropna(subset=['important_col'])

# Aggregate
# Named aggregations keep string column names required by Dask's Parquet writer.
summary = ddf.groupby('category').agg(
    amount_sum=('amount', 'sum'),
    amount_mean=('amount', 'mean'),
    quantity_count=('quantity', 'count'),
)

# Write results
summary.to_parquet('output/summary.parquet')
Raw lines 285 to 308
309### Time Series Analysis
310```python
311# Read time series data
312ddf = dd.read_parquet('timeseries/*.parquet')
314# Parse timestamps and sort globally; do not assert unverified input ordering
315ddf['timestamp'] = dd.to_datetime(ddf['timestamp'])
316ddf = ddf.set_index('timestamp')
318# Resample time series (requires sorted datetime index)
319hourly = ddf[['value']].resample('1h').mean()
321# Compute statistics
322result = hourly.compute()
323```

Time Series Analysis

# Read time series data
ddf = dd.read_parquet('timeseries/*.parquet')

# Parse timestamps and sort globally; do not assert unverified input ordering
ddf['timestamp'] = dd.to_datetime(ddf['timestamp'])
ddf = ddf.set_index('timestamp')

# Resample time series (requires sorted datetime index)
hourly = ddf[['value']].resample('1h').mean()

# Compute statistics
result = hourly.compute()
Raw lines 309 to 324
325### Combining Multiple Files
326```python
327# Read multiple files as single DataFrame
328ddf = dd.read_csv('data/2024-*.csv')
330# Process combined data
331result = ddf.groupby('category')['value'].sum().compute()
332```

Combining Multiple Files

# Read multiple files as single DataFrame
ddf = dd.read_csv('data/2024-*.csv')

# Process combined data
result = ddf.groupby('category')['value'].sum().compute()
Raw lines 325 to 333
334## Limitations and Differences from Pandas

Limitations and Differences from Pandas

Raw lines 334 to 335
336### Not All Pandas Features Available
337Some pandas operations are not implemented in Dask:
338- Some string methods
339- Certain window functions
340- Some specialized statistical functions

Not All Pandas Features Available

Some pandas operations are not implemented in Dask:

  • Some string methods
  • Certain window functions
  • Some specialized statistical functions
Raw lines 336 to 341
342### Partitioning Matters
343- Operations within partitions are efficient
344- Cross-partition operations may be expensive
345- Index-based operations benefit from sorted index

Partitioning Matters

  • Operations within partitions are efficient
  • Cross-partition operations may be expensive
  • Index-based operations benefit from sorted index
Raw lines 342 to 346
347### Lazy Evaluation
348- Operations don't execute until `.compute()`
349- Need to be aware of computation triggers
350- Can't inspect intermediate results without computing

Lazy Evaluation

  • Operations don't execute until .compute()
  • Need to be aware of computation triggers
  • Can't inspect intermediate results without computing
Raw lines 347 to 351
352## Debugging Tips

Debugging Tips

Raw lines 352 to 353
354### Inspect Partitions
355```python
356# Get number of partitions
357print(ddf.npartitions)
359# Compute single partition
360first_partition = ddf.get_partition(0).compute()
362# View first few rows (computes first partition)
363print(ddf.head())
364```

Inspect Partitions

# Get number of partitions
print(ddf.npartitions)

# Compute single partition
first_partition = ddf.get_partition(0).compute()

# View first few rows (computes first partition)
print(ddf.head())
Raw lines 354 to 365
366### Validate Operations on Small Data
367```python
368# Test on small sample first
369sample = ddf.head(1000)
370# Validate logic works
371# Then scale to full dataset
372result = ddf.compute()
373```

Validate Operations on Small Data

# Test on small sample first
sample = ddf.head(1000)
# Validate logic works
# Then scale to full dataset
result = ddf.compute()
Raw lines 366 to 374
375### Check Dtypes
377Specify `dtype=` at CSV read time when later partitions can contain nulls or mixed types absent from the initial sample. A successful `head()` does not validate every file. If `map_partitions` changes the index, use `clear_divisions=True`; `meta` defines structure but does not cast result dtypes.
378```python
379# Verify data types are correct
380print(ddf.dtypes)
381```

Check Dtypes

Specify dtype= at CSV read time when later partitions can contain nulls or mixed types absent from the initial sample. A successful head() does not validate every file. If map_partitions changes the index, use clear_divisions=True; meta defines structure but does not cast result dtypes.

# Verify data types are correct
print(ddf.dtypes)
Raw lines 375 to 381