Pandas - Read from S3 Bucket

• Pandas integrates seamlessly with S3 through the s3fs library, allowing you to read files directly using standard `read_csv()`, `read_parquet()`, and other I/O functions with S3 URLs

Key Insights

• Pandas integrates seamlessly with S3 through the s3fs library, allowing you to read files directly using standard read_csv(), read_parquet(), and other I/O functions with S3 URLs • AWS credentials can be managed through IAM roles, environment variables, or the AWS credentials file—with IAM roles being the most secure approach for production environments • Reading large datasets from S3 requires strategic use of chunking, column selection, and file format optimization to minimize data transfer costs and memory usage

Basic S3 File Reading

Reading files from S3 with Pandas requires the s3fs library, which provides the filesystem interface. Install both dependencies:

pip install pandas s3fs boto3

The simplest approach uses the S3 path directly in Pandas read functions:

import pandas as pd

# Read CSV from S3
df = pd.read_csv('s3://my-bucket/data/sales.csv')

# Read parquet from S3
df = pd.read_parquet('s3://my-bucket/data/sales.parquet')

# Read Excel from S3
df = pd.read_excel('s3://my-bucket/data/sales.xlsx')

# Read JSON from S3
df = pd.read_json('s3://my-bucket/data/sales.json')

This works because Pandas automatically detects the S3 protocol and uses s3fs under the hood.

Configuring AWS Credentials

For production environments, use IAM roles attached to your EC2 instance or ECS task. For local development, configure credentials using one of these methods:

import pandas as pd

# Method 1: Environment variables (set before running Python)
# export AWS_ACCESS_KEY_ID=your_access_key
# export AWS_SECRET_ACCESS_KEY=your_secret_key
# export AWS_DEFAULT_REGION=us-east-1

df = pd.read_csv('s3://my-bucket/data/sales.csv')

# Method 2: Explicit credentials in storage_options
df = pd.read_csv(
    's3://my-bucket/data/sales.csv',
    storage_options={
        'key': 'your_access_key',
        'secret': 'your_secret_key',
        'token': 'session_token'  # Optional, for temporary credentials
    }
)

# Method 3: Using AWS profile
df = pd.read_csv(
    's3://my-bucket/data/sales.csv',
    storage_options={
        'profile': 'my-aws-profile'
    }
)

For cross-account access or assuming roles:

import boto3
import pandas as pd

# Assume role and get temporary credentials
sts_client = boto3.client('sts')
assumed_role = sts_client.assume_role(
    RoleArn='arn:aws:iam::123456789012:role/DataAccessRole',
    RoleSessionName='pandas-session'
)

credentials = assumed_role['Credentials']

df = pd.read_csv(
    's3://other-account-bucket/data/sales.csv',
    storage_options={
        'key': credentials['AccessKeyId'],
        'secret': credentials['SecretAccessKey'],
        'token': credentials['SessionToken']
    }
)

Reading Large Files Efficiently

For large datasets, reading the entire file into memory is inefficient. Use chunking to process data in batches:

import pandas as pd

# Read in chunks
chunk_size = 100000
chunks = []

for chunk in pd.read_csv(
    's3://my-bucket/data/large_sales.csv',
    chunksize=chunk_size
):
    # Process each chunk
    filtered_chunk = chunk[chunk['amount'] > 1000]
    chunks.append(filtered_chunk)

# Combine processed chunks
df = pd.concat(chunks, ignore_index=True)

Select only required columns to reduce data transfer:

# Read only specific columns
df = pd.read_csv(
    's3://my-bucket/data/sales.csv',
    usecols=['date', 'product_id', 'amount', 'customer_id']
)

# Read with data type specification to reduce memory
df = pd.read_csv(
    's3://my-bucket/data/sales.csv',
    dtype={
        'product_id': 'int32',
        'customer_id': 'int32',
        'amount': 'float32'
    },
    parse_dates=['date']
)

Working with Partitioned Data

For partitioned datasets stored in S3, read multiple files efficiently:

import pandas as pd
from glob import glob
import s3fs

# Initialize s3fs filesystem
fs = s3fs.S3FileSystem()

# List all parquet files in a partitioned structure
files = fs.glob('s3://my-bucket/data/year=2024/month=*/day=*/*.parquet')

# Read all files into a single DataFrame
dfs = []
for file in files:
    df = pd.read_parquet(f's3://{file}')
    dfs.append(df)

combined_df = pd.concat(dfs, ignore_index=True)

For date-partitioned data with filtering:

import s3fs
import pandas as pd
from datetime import datetime, timedelta

fs = s3fs.S3FileSystem()

# Read only last 7 days of data
end_date = datetime.now()
start_date = end_date - timedelta(days=7)

dfs = []
current_date = start_date

while current_date <= end_date:
    path = f's3://my-bucket/data/year={current_date.year}/month={current_date.month:02d}/day={current_date.day:02d}/*.parquet'
    
    matching_files = fs.glob(path)
    
    for file in matching_files:
        df = pd.read_parquet(f's3://{file}')
        dfs.append(df)
    
    current_date += timedelta(days=1)

df = pd.concat(dfs, ignore_index=True)

Handling Compressed Files

Pandas automatically handles common compression formats:

import pandas as pd

# Gzip compressed CSV
df = pd.read_csv('s3://my-bucket/data/sales.csv.gz', compression='gzip')

# Auto-detect compression from extension
df = pd.read_csv('s3://my-bucket/data/sales.csv.gz')

# Bzip2 compression
df = pd.read_csv('s3://my-bucket/data/sales.csv.bz2', compression='bz2')

# ZIP file (specify file within archive)
df = pd.read_csv('s3://my-bucket/data/sales.zip', compression='zip')

Error Handling and Retries

Implement robust error handling for network issues and missing files:

import pandas as pd
from botocore.exceptions import ClientError, NoCredentialsError
import time

def read_s3_with_retry(s3_path, max_retries=3, **kwargs):
    """Read from S3 with exponential backoff retry logic."""
    
    for attempt in range(max_retries):
        try:
            df = pd.read_csv(s3_path, **kwargs)
            return df
        
        except NoCredentialsError:
            raise Exception("AWS credentials not found")
        
        except ClientError as e:
            error_code = e.response['Error']['Code']
            
            if error_code == 'NoSuchKey':
                raise FileNotFoundError(f"File not found: {s3_path}")
            elif error_code == 'AccessDenied':
                raise PermissionError(f"Access denied to: {s3_path}")
            else:
                if attempt < max_retries - 1:
                    wait_time = 2 ** attempt
                    print(f"Retry {attempt + 1}/{max_retries} after {wait_time}s")
                    time.sleep(wait_time)
                else:
                    raise
        
        except Exception as e:
            if attempt < max_retries - 1:
                wait_time = 2 ** attempt
                print(f"Error: {e}. Retrying in {wait_time}s...")
                time.sleep(wait_time)
            else:
                raise

# Usage
df = read_s3_with_retry(
    's3://my-bucket/data/sales.csv',
    max_retries=3,
    usecols=['date', 'amount']
)

Performance Optimization with Parquet

Parquet files offer superior performance for S3 operations due to columnar storage and built-in compression:

import pandas as pd

# Read parquet (much faster than CSV)
df = pd.read_parquet('s3://my-bucket/data/sales.parquet')

# Read specific columns from parquet (only downloads needed columns)
df = pd.read_parquet(
    's3://my-bucket/data/sales.parquet',
    columns=['date', 'product_id', 'amount']
)

# Read with row filtering (if parquet has statistics)
df = pd.read_parquet(
    's3://my-bucket/data/sales.parquet',
    filters=[('date', '>=', '2024-01-01'), ('amount', '>', 100)]
)

Converting CSV to Parquet for better S3 performance:

# Read CSV and save as parquet
df = pd.read_csv('s3://my-bucket/data/sales.csv')
df.to_parquet(
    's3://my-bucket/data/sales.parquet',
    compression='snappy',
    index=False
)

These patterns provide production-ready approaches for reading data from S3 into Pandas DataFrames, balancing performance, cost, and reliability.

Liked this? There's more.

Every week: one practical technique, explained simply, with code you can use immediately.