Data Transformation

PySUS includes tools for aggregating, renaming, linking, masking, and optimizing SUS datasets without leaving Python.

Aggregation

Aggregate SUS data by state, age group, or time period:

from pysus import (
    aggregate_by_state,
    aggregate_by_age_group,
    aggregate_by_period,
)

# Count cases by state
by_state = aggregate_by_state(df, value_col="ID_AGRAVO", agg_func="count")

# Sum hospitalizations by age group
by_age = aggregate_by_age_group(
    df,
    age_col="IDADE",
    value_col="DIAG_PRINC",
    bins=[0, 5, 15, 30, 50, 70, 120],
)

# Monthly notifications
by_month = aggregate_by_period(
    df,
    date_col="DT_NOTIFIC",
    value_col="ID_AGRAVO",
    freq="M",       # "M"onthly, "Q"uarterly, "Y"early
)

aggregate_by_period expects date columns in YYYYMMDD format (the standard across SINAN, SIM, SINASC, SIH, and SIA).

Column Aliasing and Renaming

SUS datasets use Portuguese column names that change across form editions. rename_columns and get_aliases handle this:

from pysus import rename_columns, get_aliases

# See what a column was called in previous editions
aliases = get_aliases("sinan", "DT_NOTIFIC")
# ["DT_NOTIFIC", "DT_NOT_N"]

# Rename with historical aliases (all old names → canonical)
df = rename_columns(df, database="sinan")

# Or use a custom mapping
df = rename_columns(df, mapping={
    "DT_NOTIFIC": "notification_date",
    "DT_SIN_PRI": "symptom_onset_date",
})

Cross-Dataset Linking

Link two SUS datasets on shared columns (municipality code, CPF, etc.):

from pysus import get_linking_keys, link_datasets

# Discover what columns two datasets share
keys = get_linking_keys("sinan", "sim")
# ["CID_MUNIC", "DT_NOTIFIC", ...]

# Merge on those keys
merged = link_datasets(notifications, deaths, on=keys, how="left")

link_datasets handles column name conflicts by adding _left and _right suffixes.

Sensitive Data Masking

Protect sensitive columns (CPF, names, addresses) before sharing:

from pysus import mask_data, unmask_data

# Encrypt sensitive columns (auto-detects CPF/NOME patterns)
masked_df, key = mask_data(df, method="encrypt")

# Or target specific columns
masked_df, key = mask_data(
    df,
    columns=["CPF", "NOME", "ENDereco"],
    method="hash",     # "encrypt", "hash", or "redact"
)

# Reverse later with the key
original_df = unmask_data(masked_df, columns=["CPF", "NOME"], key=key)

Three masking methods:

  • "encrypt" — reversible AES encryption (default)

  • "hash" — one-way SHA-256 (irreversible)

  • "redact" — replace with *** (irreversible, simplest)

Memory Optimization

SUS datasets are large. Optimize memory before analysis:

from pysus import optimize_memory, set_precision

# Auto-downcast int/float to smallest sufficient type
df = optimize_memory(df)

# Set specific numeric precision
df = set_precision(df, precision="float32")

Typical memory reduction: 30-60% for datasets with mixed int/float columns.

Unit Detection

Detect units of measurement in numeric columns:

from pysus import detect_units

units = detect_units(df)
for u in units:
    print(f"{u.column}: {u.unit} (confidence: {u.confidence:.0%})")

detect_units uses column names, value ranges, and any available metadata to infer units (e.g., "kg", "mg/dL", "years").

Streaming Large Files

For files too large to fit in memory, process them in chunks:

from pysus import stream_parquet

for chunk in stream_parquet("large_file.parquet", chunk_size=50000):
    # Process each chunk
    results = chunk.groupby("UF").size()

stream_parquet yields DataFrames of chunk_size rows, reading only the specified columns if given:

for chunk in stream_parquet(
    "SIHSR2401.parquet",
    chunk_size=10000,
    columns=["UF", "IDADE", "DIAG_PRINC"],
):
    process(chunk)