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

bags.md

476 lines · skills/dask/references/bags.md at 5f2bc03

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

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 Bag implements functional operations including `map`, `filter`, `fold`, and `groupby` on generic Python objects. It processes data in parallel while maintaining a small memory footprint through Python iterators. Bags function as "a parallel version of PyToolz or a Pythonic version of the PySpark RDD."
8

Overview

Dask Bag implements functional operations including map, filter, fold, and groupby on generic Python objects. It processes data in parallel while maintaining a small memory footprint through Python iterators. Bags function as "a parallel version of PyToolz or a Pythonic version of the PySpark RDD."

Raw lines 5 to 8
9## Core Concept
11A Dask Bag is a collection of Python objects distributed across partitions:
12- Each partition contains generic Python objects
13- Operations use functional programming patterns
14- Processing uses streaming/iterators for memory efficiency
15- Ideal for unstructured or semi-structured data

Core Concept

A Dask Bag is a collection of Python objects distributed across partitions:

  • Each partition contains generic Python objects
  • Operations use functional programming patterns
  • Processing uses streaming/iterators for memory efficiency
  • Ideal for unstructured or semi-structured data
Raw lines 9 to 16
17## Key Capabilities

Key Capabilities

Raw lines 17 to 18
19### Functional Operations
20- `map`: Transform each element
21- `filter`: Select elements based on condition
22- `fold`: Reduce elements with combining function
23- `groupby`: Group elements by key
24- `pluck`: Extract fields from records
25- `flatten`: Flatten nested structures

Functional Operations

  • map: Transform each element
  • filter: Select elements based on condition
  • fold: Reduce elements with combining function
  • groupby: Group elements by key
  • pluck: Extract fields from records
  • flatten: Flatten nested structures
Raw lines 19 to 26
27### Use Cases
28- Text processing and log analysis
29- JSON record processing
30- ETL on unstructured data
31- Data cleaning before structured analysis

Use Cases

  • Text processing and log analysis
  • JSON record processing
  • ETL on unstructured data
  • Data cleaning before structured analysis
Raw lines 27 to 32
33## When to Use Dask Bags
35**Use Bags When**:
36- Working with general Python objects requiring flexible computation
37- Data doesn't fit structured array or tabular formats
38- Processing text, JSON, or custom Python objects
39- Initial data cleaning and ETL is needed
40- Memory-efficient streaming is important
42**Use Other Collections When**:
43- Data is structured (use DataFrames instead)
44- Numeric computing (use Arrays instead)
45- Operations require complex groupby or shuffles (use DataFrames)
47**Key Recommendation**: Use Bag to clean and process data, then transform it into an array or DataFrame before embarking on more complex operations that require shuffle steps.

When to Use Dask Bags

Use Bags When:

  • Working with general Python objects requiring flexible computation
  • Data doesn't fit structured array or tabular formats
  • Processing text, JSON, or custom Python objects
  • Initial data cleaning and ETL is needed
  • Memory-efficient streaming is important

Use Other Collections When:

  • Data is structured (use DataFrames instead)
  • Numeric computing (use Arrays instead)
  • Operations require complex groupby or shuffles (use DataFrames)

Key Recommendation: Use Bag to clean and process data, then transform it into an array or DataFrame before embarking on more complex operations that require shuffle steps.

Raw lines 33 to 48
49## Important Limitations
51Bags sacrifice performance for generality:
52- Default to the processes scheduler when no distributed client/config overrides it; threads are supported
53- Remain immutable (create new bags for changes)
54- Operate slower than array/DataFrame equivalents
55- Handle `groupby` inefficiently (use `foldby` when possible)
56- Operations requiring substantial inter-worker communication are slow

Important Limitations

Bags sacrifice performance for generality:

  • Default to the processes scheduler when no distributed client/config overrides it; threads are supported
  • Remain immutable (create new bags for changes)
  • Operate slower than array/DataFrame equivalents
  • Handle groupby inefficiently (use foldby when possible)
  • Operations requiring substantial inter-worker communication are slow
Raw lines 49 to 57
58## Creating Bags

Creating Bags

Raw lines 58 to 59
60### From Sequences
61```python
62import dask.bag as db
64# From Python list
65bag = db.from_sequence([1, 2, 3, 4, 5], partition_size=2)
67# From range
68bag = db.from_sequence(range(10000), partition_size=1000)
69```

From Sequences

import dask.bag as db

# From Python list
bag = db.from_sequence([1, 2, 3, 4, 5], partition_size=2)

# From range
bag = db.from_sequence(range(10000), partition_size=1000)
Raw lines 60 to 70
71### From Text Files
72```python
73# Single file
74bag = db.read_text('data.txt')
76# Multiple files with glob
77bag = db.read_text('data/*.txt')
79# With encoding
80bag = db.read_text('data/*.txt', encoding='utf-8')
82# Custom line processing
83bag = db.read_text('logs/*.log', blocksize='64MB')
84```

From Text Files

# Single file
bag = db.read_text('data.txt')

# Multiple files with glob
bag = db.read_text('data/*.txt')

# With encoding
bag = db.read_text('data/*.txt', encoding='utf-8')

# Custom line processing
bag = db.read_text('logs/*.log', blocksize='64MB')
Raw lines 71 to 85
86### From Delayed Objects
87```python
88import dask
90@dask.delayed
91def load_data(filename):
92 with open(filename) as f:
93 return [line.strip() for line in f]
95files = ['file1.txt', 'file2.txt', 'file3.txt']
96partitions = [load_data(f) for f in files]
97bag = db.from_delayed(partitions)
98```

From Delayed Objects

import dask

@dask.delayed
def load_data(filename):
    with open(filename) as f:
        return [line.strip() for line in f]

files = ['file1.txt', 'file2.txt', 'file3.txt']
partitions = [load_data(f) for f in files]
bag = db.from_delayed(partitions)
Raw lines 86 to 99
100### From Custom Sources
101```python
102import glob
103import json
105def read_json_file(filename):
106 with open(filename, encoding='utf-8') as f:
107 return json.load(f) # One complete JSON object per file
109# Pass only filenames locally; loading occurs in worker tasks.
110# from_sequence eagerly materializes an iterable, so do not pass a data loader.
111bag = db.from_sequence(sorted(glob.glob('data/*.json')), partition_size=10)
112bag = bag.map(read_json_file)
113```

From Custom Sources

import glob
import json

def read_json_file(filename):
    with open(filename, encoding='utf-8') as f:
        return json.load(f)  # One complete JSON object per file

# Pass only filenames locally; loading occurs in worker tasks.
# from_sequence eagerly materializes an iterable, so do not pass a data loader.
bag = db.from_sequence(sorted(glob.glob('data/*.json')), partition_size=10)
bag = bag.map(read_json_file)
Raw lines 100 to 114
115## Common Operations

Common Operations

Raw lines 115 to 116
117### Map (Transform)
118```python
119import dask.bag as db
121bag = db.read_text('data/*.json')
123# Parse JSON
124import json
125parsed = bag.map(json.loads)
127# Extract field
128values = parsed.map(lambda x: x['value'])
130# Complex transformation
131def process_record(record):
132 return {
133 'id': record['id'],
134 'value': record['value'] * 2,
135 'category': record.get('category', 'unknown')
136 }
138processed = parsed.map(process_record)
139```

Map (Transform)

import dask.bag as db

bag = db.read_text('data/*.json')

# Parse JSON
import json
parsed = bag.map(json.loads)

# Extract field
values = parsed.map(lambda x: x['value'])

# Complex transformation
def process_record(record):
    return {
        'id': record['id'],
        'value': record['value'] * 2,
        'category': record.get('category', 'unknown')
    }

processed = parsed.map(process_record)
Raw lines 117 to 140
141### Filter
142```python
143# Filter by condition
144valid = parsed.filter(lambda x: x['status'] == 'valid')
146# Multiple conditions
147filtered = parsed.filter(lambda x: x['value'] > 100 and x['year'] == 2024)
149# Filter with custom function
150def is_valid_record(record):
151 return record.get('status') == 'valid' and record.get('value') is not None
153valid_records = parsed.filter(is_valid_record)
154```

Filter

# Filter by condition
valid = parsed.filter(lambda x: x['status'] == 'valid')

# Multiple conditions
filtered = parsed.filter(lambda x: x['value'] > 100 and x['year'] == 2024)

# Filter with custom function
def is_valid_record(record):
    return record.get('status') == 'valid' and record.get('value') is not None

valid_records = parsed.filter(is_valid_record)
Raw lines 141 to 155
156### Pluck (Extract Fields)
157```python
158# Extract single field
159ids = parsed.pluck('id')
161# Extract multiple fields (creates tuples)
162key_pairs = parsed.pluck(['id', 'value'])
163```

Pluck (Extract Fields)

# Extract single field
ids = parsed.pluck('id')

# Extract multiple fields (creates tuples)
key_pairs = parsed.pluck(['id', 'value'])
Raw lines 156 to 164
165### Flatten
166```python
167# Flatten nested lists
168nested = db.from_sequence([[1, 2], [3, 4], [5, 6]])
169flat = nested.flatten() # [1, 2, 3, 4, 5, 6]
171# Flatten after map
172bag = db.read_text('data/*.txt')
173words = bag.map(str.split).flatten() # All words from all files
174```

Flatten

# Flatten nested lists
nested = db.from_sequence([[1, 2], [3, 4], [5, 6]])
flat = nested.flatten()  # [1, 2, 3, 4, 5, 6]

# Flatten after map
bag = db.read_text('data/*.txt')
words = bag.map(str.split).flatten()  # All words from all files
Raw lines 165 to 175
176### GroupBy (Expensive)
177```python
178# Group by key (requires shuffle)
179grouped = parsed.groupby(lambda x: x['category'])
181# Aggregate after grouping
182counts = grouped.map(lambda key_items: (key_items[0], len(list(key_items[1]))))
183result = counts.compute()
184```

GroupBy (Expensive)

# Group by key (requires shuffle)
grouped = parsed.groupby(lambda x: x['category'])

# Aggregate after grouping
counts = grouped.map(lambda key_items: (key_items[0], len(list(key_items[1]))))
result = counts.compute()
Raw lines 176 to 185
186### FoldBy (Preferred for Aggregations)
187```python
188# FoldBy is more efficient than groupby for aggregations
189def add(acc, item):
190 return acc + item['value']
192def combine(acc1, acc2):
193 return acc1 + acc2
195# Sum values by category
196sums = parsed.foldby(
197 key='category',
198 binop=add,
199 initial=0,
200 combine=combine
203result = sums.compute()
204```

FoldBy (Preferred for Aggregations)

# FoldBy is more efficient than groupby for aggregations
def add(acc, item):
    return acc + item['value']

def combine(acc1, acc2):
    return acc1 + acc2

# Sum values by category
sums = parsed.foldby(
    key='category',
    binop=add,
    initial=0,
    combine=combine
)

result = sums.compute()
Raw lines 186 to 205
206### Reductions
207```python
208# Count elements
209count = bag.count().compute()
211# Get all distinct values (requires memory)
212distinct = bag.distinct().compute()
214# Take first n elements
215first_ten = bag.take(10, npartitions=-1) # Default inspects only first partition
217# Fold/reduce
218total = bag.fold(
219 lambda acc, x: acc + x['value'],
220 initial=0,
221 combine=lambda a, b: a + b
222).compute()
223```

Reductions

# Count elements
count = bag.count().compute()

# Get all distinct values (requires memory)
distinct = bag.distinct().compute()

# Take first n elements
first_ten = bag.take(10, npartitions=-1)  # Default inspects only first partition

# Fold/reduce
total = bag.fold(
    lambda acc, x: acc + x['value'],
    initial=0,
    combine=lambda a, b: a + b
).compute()
Raw lines 206 to 224
225## Converting to Other Collections
227Supply explicit `meta` when possible: without it `to_dataframe()` computes a sample, can miss rare types, and fails on an empty bag. Validate/cast each record; meta is not a general data-cleaning policy.

Converting to Other Collections

Supply explicit meta when possible: without it to_dataframe() computes a sample, can miss rare types, and fails on an empty bag. Validate/cast each record; meta is not a general data-cleaning policy.

Raw lines 225 to 228
229### To DataFrame
230```python
231import dask.bag as db
232import dask.dataframe as dd
234# Bag of dictionaries
235bag = db.read_text('data/*.json').map(json.loads)
237# Convert to DataFrame
238ddf = bag.to_dataframe()
240# With explicit columns
241ddf = bag.to_dataframe(meta={'id': int, 'value': float, 'category': str})
242```

To DataFrame

import dask.bag as db
import dask.dataframe as dd

# Bag of dictionaries
bag = db.read_text('data/*.json').map(json.loads)

# Convert to DataFrame
ddf = bag.to_dataframe()

# With explicit columns
ddf = bag.to_dataframe(meta={'id': int, 'value': float, 'category': str})
Raw lines 229 to 243
244### To List/Compute
245```python
246# Compute to Python list (loads all in memory)
247result = bag.compute()
249# Take sample
250sample = bag.take(100, npartitions=-1)
251```

To List/Compute

# Compute to Python list (loads all in memory)
result = bag.compute()

# Take sample
sample = bag.take(100, npartitions=-1)
Raw lines 244 to 252
253## Common Patterns

Common Patterns

Raw lines 253 to 254
255### JSON Processing
256```python
257import dask.bag as db
258import json
260# Read and parse JSON Lines (one complete JSON object per line)
261bag = db.read_text('logs/*.json')
262parsed = bag.map(json.loads)
264# Filter valid records
265valid = parsed.filter(lambda x: x.get('status') == 'success')
267# Extract relevant fields
268processed = valid.map(lambda x: {
269 'user_id': x['user']['id'],
270 'timestamp': x['timestamp'],
271 'value': x['metrics']['value']
272})
274# Convert to DataFrame for analysis
275ddf = processed.to_dataframe()
277# Analyze
278summary = ddf.groupby('user_id')['value'].mean().compute()
279```

JSON Processing

import dask.bag as db
import json

# Read and parse JSON Lines (one complete JSON object per line)
bag = db.read_text('logs/*.json')
parsed = bag.map(json.loads)

# Filter valid records
valid = parsed.filter(lambda x: x.get('status') == 'success')

# Extract relevant fields
processed = valid.map(lambda x: {
    'user_id': x['user']['id'],
    'timestamp': x['timestamp'],
    'value': x['metrics']['value']
})

# Convert to DataFrame for analysis
ddf = processed.to_dataframe()

# Analyze
summary = ddf.groupby('user_id')['value'].mean().compute()
Raw lines 255 to 280
281### Log Analysis
282```python
283# Read log files
284logs = db.read_text('logs/*.log')
286# Parse log lines
287def parse_log_line(line):
288 parts = line.split(' ')
289 return {
290 'timestamp': parts[0],
291 'level': parts[1],
292 'message': ' '.join(parts[2:])
293 }
295parsed_logs = logs.map(parse_log_line)
297# Filter errors
298errors = parsed_logs.filter(lambda x: x['level'] == 'ERROR')
300# Count by message pattern
301error_counts = errors.foldby(
302 key='message',
303 binop=lambda acc, x: acc + 1,
304 initial=0,
305 combine=lambda a, b: a + b
308result = error_counts.compute()
309```

Log Analysis

# Read log files
logs = db.read_text('logs/*.log')

# Parse log lines
def parse_log_line(line):
    parts = line.split(' ')
    return {
        'timestamp': parts[0],
        'level': parts[1],
        'message': ' '.join(parts[2:])
    }

parsed_logs = logs.map(parse_log_line)

# Filter errors
errors = parsed_logs.filter(lambda x: x['level'] == 'ERROR')

# Count by message pattern
error_counts = errors.foldby(
    key='message',
    binop=lambda acc, x: acc + 1,
    initial=0,
    combine=lambda a, b: a + b
)

result = error_counts.compute()
Raw lines 281 to 310
311### Text Processing
312```python
313# Read text files
314text = db.read_text('documents/*.txt')
316# Split into words
317words = text.map(str.lower).map(str.split).flatten()
319# Count word frequencies
320def increment(acc, word):
321 return acc + 1
323def combine_counts(a, b):
324 return a + b
326word_counts = words.foldby(
327 key=lambda word: word,
328 binop=increment,
329 initial=0,
330 combine=combine_counts
333# Get top words
334top_words = word_counts.compute()
335sorted_words = sorted(top_words, key=lambda x: x[1], reverse=True)[:100]
336```

Text Processing

# Read text files
text = db.read_text('documents/*.txt')

# Split into words
words = text.map(str.lower).map(str.split).flatten()

# Count word frequencies
def increment(acc, word):
    return acc + 1

def combine_counts(a, b):
    return a + b

word_counts = words.foldby(
    key=lambda word: word,
    binop=increment,
    initial=0,
    combine=combine_counts
)

# Get top words
top_words = word_counts.compute()
sorted_words = sorted(top_words, key=lambda x: x[1], reverse=True)[:100]
Raw lines 311 to 337
338### Data Cleaning Pipeline
339```python
340import dask.bag as db
341import json
343# Read raw data
344raw = db.read_text('raw_data/*.json').map(json.loads)
346# Validation function
347def is_valid(record):
348 required_fields = ['id', 'timestamp', 'value']
349 return all(field in record for field in required_fields)
351# Cleaning function
352def clean_record(record):
353 return {
354 'id': int(record['id']),
355 'timestamp': record['timestamp'],
356 'value': float(record['value']),
357 'category': record.get('category', 'unknown'),
358 'tags': record.get('tags', [])
359 }
361# Pipeline
362cleaned = (raw
363 .filter(is_valid)
364 .map(clean_record)
365 .filter(lambda x: x['value'] > 0)
368# Convert to DataFrame
369ddf = cleaned.to_dataframe()
371# Save cleaned data
372ddf.to_parquet('cleaned_data/')
373```

Data Cleaning Pipeline

import dask.bag as db
import json

# Read raw data
raw = db.read_text('raw_data/*.json').map(json.loads)

# Validation function
def is_valid(record):
    required_fields = ['id', 'timestamp', 'value']
    return all(field in record for field in required_fields)

# Cleaning function
def clean_record(record):
    return {
        'id': int(record['id']),
        'timestamp': record['timestamp'],
        'value': float(record['value']),
        'category': record.get('category', 'unknown'),
        'tags': record.get('tags', [])
    }

# Pipeline
cleaned = (raw
    .filter(is_valid)
    .map(clean_record)
    .filter(lambda x: x['value'] > 0)
)

# Convert to DataFrame
ddf = cleaned.to_dataframe()

# Save cleaned data
ddf.to_parquet('cleaned_data/')
Raw lines 338 to 374
375## Performance Considerations

Performance Considerations

Raw lines 375 to 376
377### Efficient Operations
378- Map, filter, pluck: Very efficient (streaming)
379- Flatten: Efficient
380- FoldBy with good key distribution: Reasonable
381- take: eager sample; default only scans the first partition (Bag has no head method)

Efficient Operations

  • Map, filter, pluck: Very efficient (streaming)
  • Flatten: Efficient
  • FoldBy with good key distribution: Reasonable
  • take: eager sample; default only scans the first partition (Bag has no head method)
Raw lines 377 to 382
383### Expensive Operations
384- GroupBy: Requires shuffle, can be slow
385- Distinct: Requires collecting all unique values
386- Operations requiring full data materialization

Expensive Operations

  • GroupBy: Requires shuffle, can be slow
  • Distinct: Requires collecting all unique values
  • Operations requiring full data materialization
Raw lines 383 to 387
388### Optimization Tips
390**1. Use FoldBy Instead of GroupBy**
391```python
392# Better: Use foldby for aggregations
393result = bag.foldby(key='category', binop=add, initial=0,
394 combine=lambda a, b: a + b)
396# Worse: GroupBy then reduce
397result = bag.groupby('category').map(
398 lambda x: (x[0], sum(item['value'] for item in x[1])))
399```
401**2. Convert to DataFrame Early**
402```python
403# For structured operations, convert to DataFrame
404bag = db.read_text('data/*.json').map(json.loads)
405bag = bag.filter(lambda x: x['status'] == 'valid')
406ddf = bag.to_dataframe() # Now use efficient DataFrame operations
407```
409**3. Control Partition Size**
410```python
411# Balance between too many and too few partitions
412bag = db.read_text('data/*.txt', blocksize='64MB') # Reasonable partition size
413```
415**4. Use Lazy Evaluation**
416```python
417# Chain operations before computing
418result = (bag
419 .map(process1)
420 .filter(condition)
421 .map(process2)
422 .compute() # Single compute at the end
424```

Optimization Tips

1. Use FoldBy Instead of GroupBy

# Better: Use foldby for aggregations
result = bag.foldby(key='category', binop=add, initial=0,
                    combine=lambda a, b: a + b)

# Worse: GroupBy then reduce
result = bag.groupby('category').map(
    lambda x: (x[0], sum(item['value'] for item in x[1])))

2. Convert to DataFrame Early

# For structured operations, convert to DataFrame
bag = db.read_text('data/*.json').map(json.loads)
bag = bag.filter(lambda x: x['status'] == 'valid')
ddf = bag.to_dataframe()  # Now use efficient DataFrame operations

3. Control Partition Size

# Balance between too many and too few partitions
bag = db.read_text('data/*.txt', blocksize='64MB')  # Reasonable partition size

4. Use Lazy Evaluation

# Chain operations before computing
result = (bag
    .map(process1)
    .filter(condition)
    .map(process2)
    .compute()  # Single compute at the end
)
Raw lines 388 to 425
426## Debugging Tips

Debugging Tips

Raw lines 426 to 427
428### Inspect Partitions
429```python
430# Get number of partitions
431print(bag.npartitions)
433# Take sample
434sample = bag.take(10)
435print(sample)
436```

Inspect Partitions

# Get number of partitions
print(bag.npartitions)

# Take sample
sample = bag.take(10)
print(sample)
Raw lines 428 to 437
438### Validate on Small Data
439```python
440# Test logic on small subset
441small_bag = db.from_sequence(sample_data, partition_size=10)
442result = process_pipeline(small_bag).compute()
443# Validate results, then scale
444```

Validate on Small Data

# Test logic on small subset
small_bag = db.from_sequence(sample_data, partition_size=10)
result = process_pipeline(small_bag).compute()
# Validate results, then scale
Raw lines 438 to 445
446### Check Intermediate Results
447```python
448# Compute intermediate steps to debug
449step1 = bag.map(parse).take(5)
450print("After parsing:", step1)
452step2 = bag.map(parse).filter(validate).take(5)
453print("After filtering:", step2)
454```

Check Intermediate Results

# Compute intermediate steps to debug
step1 = bag.map(parse).take(5)
print("After parsing:", step1)

step2 = bag.map(parse).filter(validate).take(5)
print("After filtering:", step2)
Raw lines 446 to 455
456## Memory Management
458Bags are designed for memory-efficient processing:
460```python
461# Streaming processing - doesn't load all in memory
462bag = db.read_text('huge_file.txt') # Lazy
463processed = bag.map(process_line) # Still lazy
464result = processed.compute() # Collects ALL results in client memory
465```
467For very large results, avoid computing to memory:
469```python
470# Don't compute huge results to memory
471# result = bag.compute() # Could overflow memory
473# Instead, convert and save to disk
474ddf = bag.to_dataframe()
475ddf.to_parquet('output/')
476```

Memory Management

Bags are designed for memory-efficient processing:

# Streaming processing - doesn't load all in memory
bag = db.read_text('huge_file.txt')  # Lazy
processed = bag.map(process_line)     # Still lazy
result = processed.compute()          # Collects ALL results in client memory

For very large results, avoid computing to memory:

# Don't compute huge results to memory
# result = bag.compute()  # Could overflow memory

# Instead, convert and save to disk
ddf = bag.to_dataframe()
ddf.to_parquet('output/')
Raw lines 456 to 476