Skip to content

Basic Usage

This guide covers the core functionality of ParquetFrame with detailed examples and explanations.

Reading Parquet Files

Automatic Extension Detection

ParquetFrame can automatically detect and read both .parquet and .pqt files:

import parquetframe as pqf

# These are equivalent:
df1 = pqf.read("data.parquet")  # Explicit extension
df2 = pqf.read("data.pqt")      # Alternative extension
df3 = pqf.read("data")          # Auto-detect extension

Backend Selection

ParquetFrame automatically chooses the optimal backend based on file size:

# Small file (< 10MB default) → pandas for speed
small_df = pqf.read("small_data.parquet")
print(f"Backend: {'Dask' if small_df.islazy else 'pandas'}")

# Large file (> 10MB default) → Dask for memory efficiency
large_df = pqf.read("large_data.parquet")
print(f"Backend: {'Dask' if large_df.islazy else 'pandas'}")

Custom Parameters

# Custom size threshold
df = pqf.read("data.parquet", threshold_mb=50)  # Use Dask for files > 50MB

# Force specific backend
pandas_df = pqf.read("data.parquet", islazy=False)  # Always pandas
dask_df = pqf.read("data.parquet", islazy=True)     # Always Dask

# Pass additional read options
df = pqf.read("data.parquet", columns=['id', 'name', 'value'])
df = pqf.read("data.parquet", filters=[('category', '==', 'A')])

Working with DataFrames

Accessing the Underlying DataFrame

df = pqf.read("data.parquet")

# Access the underlying pandas or Dask DataFrame
underlying_df = df._df

# Check the type
print(type(underlying_df))  # pandas.DataFrame or dask.dataframe.DataFrame

Standard Operations

All pandas and Dask operations work transparently:

df = pqf.read("sales_data.parquet")

# Basic operations
print(df.shape)
print(df.columns)
print(df.dtypes)

# Data exploration
print(df.head())
print(df.describe())
print(df.info())

# Filtering
active_customers = df[df['status'] == 'active']
high_value = df.query("order_value > 1000")

# Grouping and aggregation
by_region = df.groupby('region').sum()
summary = df.groupby(['region', 'product']).agg({
    'sales': ['sum', 'mean'],
    'quantity': 'sum'
})

Method Chaining

ParquetFrame supports fluent method chaining:

result = (pqf.read("raw_data.parquet")
    .dropna()
    .query("amount > 0")
    .groupby(['region', 'product'])
    .agg({'sales': 'sum', 'profit': 'mean'})
    .reset_index()
    .sort_values('sales', ascending=False)
    .head(10))

Backend Conversion

Converting Between Backends

df = pqf.read("data.parquet")

# Convert pandas to Dask
if not df.islazy:
    df.to_dask()
    print("Converted to Dask")

# Convert Dask to pandas
if df.islazy:
    df.to_pandas()
    print("Converted to pandas")

Using the islazy Property

df = pqf.read("data.parquet")

# Check current backend
print(f"Current backend: {'Dask' if df.islazy else 'pandas'}")

# Switch backends using property
df.islazy = True   # Convert to Dask
df.islazy = False  # Convert to pandas

Custom Partitions for Dask

df = pqf.read("data.parquet", islazy=False)  # Start with pandas

# Convert to Dask with specific number of partitions
df.to_dask(npartitions=8)
print(f"Dask partitions: {df._df.npartitions}")

Saving Data

Basic Saving

df = pqf.read("input.parquet")

# Process data
result = df.groupby('category').sum()

# Save with automatic extension
result.save("output")  # Saves as "output.parquet"

Custom Extensions and Options

# Specify extension
result.save("output.pqt")

# Pass compression options
result.save("compressed_output", compression='snappy')
result.save("gzip_output", compression='gzip')

# Other parquet options
result.save("custom_output",
           compression='snappy',
           index=False,
           partition_cols=['region'])

Chaining with Save

# Save returns self, enabling chaining
final_result = (pqf.read("input.parquet")
    .groupby('category').sum()
    .save("intermediate_result")  # Save intermediate
    .query("total > 1000")
    .save("final_result"))        # Save final

Working with Different Data Types

Mixed Data Types

import pandas as pd

# Create test data with mixed types
df = pd.DataFrame({
    'id': range(1000),
    'name': [f'item_{i}' for i in range(1000)],
    'price': [10.99 + i * 0.1 for i in range(1000)],
    'active': [i % 2 == 0 for i in range(1000)],
    'date': pd.date_range('2023-01-01', periods=1000, freq='H')
})

# Save and read back
pf = pqf.ParquetFrame(df)
pf.save("mixed_types")
loaded = pqf.read("mixed_types")

# Data types are preserved
print(loaded.dtypes)

DateTime Operations

df = pqf.read("time_series.parquet")

# DateTime operations work with both backends
daily_summary = df.groupby(df.timestamp.dt.date).sum()
monthly_avg = df.groupby(df.timestamp.dt.to_period('M')).mean()

# For Dask, some operations might need .compute()
if df.islazy:
    result = daily_summary.compute()
else:
    result = daily_summary

Error Handling

Common Error Patterns

import parquetframe as pqf

# Handle missing files
try:
    df = pqf.read("nonexistent.parquet")
except FileNotFoundError as e:
    print(f"File not found: {e}")

# Handle empty ParquetFrame
try:
    empty_pf = pqf.create_empty()
    empty_pf.save("output")  # This will fail
except TypeError as e:
    print(f"Cannot save empty frame: {e}")

# Handle attribute errors
try:
    df = pqf.read("data.parquet")
    result = df.nonexistent_method()
except AttributeError as e:
    print(f"Method not found: {e}")

Validating Data

def validate_dataframe(pf):
    """Validate ParquetFrame data quality."""
    if pf._df is None:
        raise ValueError("ParquetFrame is empty")

    if len(pf) == 0:
        raise ValueError("DataFrame has no rows")

    # Check for required columns
    required_cols = ['id', 'amount', 'date']
    missing_cols = set(required_cols) - set(pf.columns)
    if missing_cols:
        raise ValueError(f"Missing columns: {missing_cols}")

    print("✅ Data validation passed")

# Usage
df = pqf.read("data.parquet")
validate_dataframe(df)

Performance Considerations

When to Use Each Backend

import os

def choose_backend_strategy(file_path):
    """Demonstrate backend selection strategy."""
    file_size_mb = os.path.getsize(file_path) / (1024 * 1024)

    if file_size_mb < 5:
        # Very small files - always use pandas for speed
        return pqf.read(file_path, islazy=False)
    elif file_size_mb < 50:
        # Medium files - let ParquetFrame decide
        return pqf.read(file_path)
    else:
        # Large files - ensure Dask for memory efficiency
        return pqf.read(file_path, islazy=True)

Memory Management

# For large datasets, be mindful of memory usage
large_df = pqf.read("huge_dataset.parquet", islazy=True)

# Avoid loading entire dataset into memory
# Instead of: result = large_df.compute()  # Don't do this
# Do this:
sample = large_df.sample(frac=0.01).compute()  # 1% sample
summary = large_df.describe().compute()        # Summary statistics

# Or process in chunks
chunks = []
for i in range(large_df.npartitions):
    chunk_result = large_df.get_partition(i).sum().compute()
    chunks.append(chunk_result)

Integration Patterns

With Pandas Ecosystem

import pandas as pd
import numpy as np

df = pqf.read("data.parquet")

# Ensure pandas backend for pandas-specific operations
if df.islazy:
    df.to_pandas()

# Now use pandas-specific features
correlation_matrix = df._df.corr()
pivot_table = df._df.pivot_table(
    values='sales',
    index='region',
    columns='product',
    aggfunc='sum'
)

With Dask Ecosystem

import dask.dataframe as dd
from dask.distributed import Client

# Set up Dask client for distributed computing
client = Client()  # Connect to local cluster

df = pqf.read("large_dataset.parquet", islazy=True)

# Use Dask-specific features
print(f"Partitions: {df._df.npartitions}")
print(f"Memory usage: {df._df.memory_usage_per_partition().compute()}")

# Visualize computation graph
df._df.visualize("computation_graph.png")

client.close()

With Machine Learning Workflows

from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler

# Load and prepare data
df = pqf.read("ml_dataset.parquet")

# For ML workflows, usually need pandas
if df.islazy:
    print("Converting large dataset to pandas for ML...")
    # Consider sampling for very large datasets
    if len(df) > 1_000_000:
        df = df.sample(frac=0.1)  # Use 10% sample
    df.to_pandas()

# Standard ML preprocessing
X = df.drop('target', axis=1)._df
y = df['target']._df

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)

scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)

Next: Learn about Advanced Features for more sophisticated use cases.