---
name: xlsx
description: Create, read, and edit Excel spreadsheets via openpyxl and pandas
metadata:
  activation:
    keywords: [spreadsheet, excel, xlsx, csv, workbook, .xlsx]
    patterns:
      - "(?i)\\b(create|write|edit|read|open|analyze)\\b.{0,30}\\b(excel|xlsx|spreadsheet|workbook|csv)\\b"
      - "(?i)\\b(financial|budget|forecast|model|pivot|chart)\\b.{0,20}\\b(excel|xlsx|spreadsheet)\\b"
      - "(?i)\\.(xlsx|xlsm|csv|tsv)\\b"
  requires:
    tools: [shell]
    packages: [openpyxl]
---

# Excel Spreadsheet (.xlsx) Workflows

## Overview

Choose the right tool based on the task. Use formulas over hardcoded values — spreadsheets should recalculate when source data changes.

## Quick Reference

| Task | Tool | Install |
|------|------|---------|
| Data analysis | `pandas` | `pip install pandas openpyxl` |
| Create with formatting/formulas | `openpyxl` | `pip install openpyxl` |
| Read CSV/TSV | `pandas` or stdlib `csv` | Built-in |
| Recalculate formulas | LibreOffice headless | System package |
| Charts and pivots | `openpyxl` | `pip install openpyxl` |

## Reading and Analyzing Data

```python
import pandas as pd

# Read Excel
df = pd.read_excel('file.xlsx')                          # First sheet
df = pd.read_excel('file.xlsx', sheet_name='Sales')      # Named sheet
all_sheets = pd.read_excel('file.xlsx', sheet_name=None) # All sheets as dict

# Quick analysis
df.head()       # Preview first rows
df.info()       # Column types and nulls
df.describe()   # Summary statistics
df.shape        # (rows, cols)

# Read CSV
df = pd.read_csv('data.csv')
df = pd.read_csv('data.tsv', sep='\t')
```

## Creating Spreadsheets

### With openpyxl (formatting + formulas)

```python
from openpyxl import Workbook
from openpyxl.styles import Font, PatternFill, Alignment
from openpyxl.utils import get_column_letter

wb = Workbook()
ws = wb.active
ws.title = "Report"

# Headers with formatting
headers = ['Name', 'Q1', 'Q2', 'Q3', 'Q4', 'Total']
header_font = Font(bold=True, color='FFFFFF', size=11)
header_fill = PatternFill('solid', fgColor='2F5496')

for col, header in enumerate(headers, 1):
    cell = ws.cell(row=1, column=col, value=header)
    cell.font = header_font
    cell.fill = header_fill
    cell.alignment = Alignment(horizontal='center')

# Data rows
data = [
    ['Product A', 1500, 1800, 2100, 2400],
    ['Product B', 800, 950, 1100, 1250],
]
for row_idx, row_data in enumerate(data, 2):
    for col_idx, value in enumerate(row_data, 1):
        ws.cell(row=row_idx, column=col_idx, value=value)

# FORMULAS — never hardcode calculated values
for row in range(2, len(data) + 2):
    ws.cell(row=row, column=6, value=f'=SUM(B{row}:E{row})')

# Total row
total_row = len(data) + 2
ws.cell(row=total_row, column=1, value='Total')
ws.cell(row=total_row, column=1).font = Font(bold=True)
for col in range(2, 7):
    letter = get_column_letter(col)
    ws.cell(row=total_row, column=col, value=f'=SUM({letter}2:{letter}{total_row - 1})')

# Column widths
ws.column_dimensions['A'].width = 15
for col in range(2, 7):
    ws.column_dimensions[get_column_letter(col)].width = 12

# Number format
for row in range(2, total_row + 1):
    for col in range(2, 7):
        ws.cell(row=row, column=col).number_format = '#,##0'

wb.save('report.xlsx')
```

### With pandas (data export)

```python
import pandas as pd

df = pd.DataFrame({
    'Name': ['Alice', 'Bob', 'Carol'],
    'Sales': [45000, 38000, 52000],
    'Region': ['North', 'South', 'East'],
})

# Simple export
df.to_excel('output.xlsx', index=False, sheet_name='Sales')

# Multiple sheets
with pd.ExcelWriter('output.xlsx', engine='openpyxl') as writer:
    df.to_excel(writer, sheet_name='Sales', index=False)
    df.describe().to_excel(writer, sheet_name='Summary')
```

## Editing Existing Files

```python
from openpyxl import load_workbook

wb = load_workbook('existing.xlsx')
ws = wb.active

# Modify cells
ws['A1'] = 'Updated Value'
ws.cell(row=5, column=3, value='New Data')

# Add new sheet
ws2 = wb.create_sheet('Analysis')
ws2['A1'] = 'New Analysis'

# Insert/delete rows
ws.insert_rows(3)        # Insert at row 3
ws.delete_cols(2)         # Delete column B

wb.save('modified.xlsx')
```

**Warning**: `load_workbook(data_only=True)` reads cached values instead of formulas. If you save after this, **formulas are permanently replaced with values**.

**Warning**: `insert_rows` / `delete_rows` / `insert_cols` / `delete_cols` do NOT rewrite formula references. A formula `=A2+B2` stays as `=A2+B2` even when those cells shift to row 3 — Excel will then compute it against the wrong cells. Fix references manually or open in Excel/LibreOffice (which auto-updates them on edit).

## Recalculating Formulas

openpyxl writes formula strings but does NOT compute values. Use LibreOffice to recalculate:

```bash
# Recalculate all formulas (re-saving via LibreOffice forces recalc)
libreoffice --headless --calc --convert-to xlsx:"Calc MS Excel 2007 XML" output.xlsx
```

After recalculation, verify there are no formula errors:

```python
from openpyxl import load_workbook

wb = load_workbook('output.xlsx', data_only=True)
ws = wb.active
errors = []
for row in ws.iter_rows():
    for cell in row:
        if isinstance(cell.value, str) and cell.value.startswith('#'):
            errors.append(f"{cell.coordinate}: {cell.value}")
if errors:
    print("Formula errors found:", errors)
```

## Critical Rules

- **Always use Excel formulas** — `=SUM(B2:B9)` not hardcoded `5000`
- **Set number formats** — currency: `'$#,##0'`, percentage: `'0.0%'`, dates: `'YYYY-MM-DD'`
- **Cell indices are 1-based** — `row=1, column=1` is cell A1
- **Preserve formulas on edit** — never use `data_only=True` when saving
- **Test large files** — use `read_only=True`/`write_only=True` for performance
- **Recalculate after formula changes** — openpyxl does not evaluate formulas
- **Back up originals** — always save to a new filename when editing

## Financial Model Conventions

When building financial models:
- **Blue text**: Input assumptions (hardcoded values users can change)
- **Black text**: Formulas and calculations
- **Yellow fill**: Key assumptions needing attention
- **Use parentheses for negatives**: `(123)` not `-123`
- **Specify units in headers**: `"Revenue ($K)"` not just `"Revenue"`
- **Document sources**: Add cell comments for hardcoded values with data source
