Files
labs/Lab-5/Lab-5-Solution/01_Malware_Dataset_Audit_Solution.ipynb
T

7.5 KiB

Module 5 — Lab 1: Auditing a Malware Feature Dataset

Topics: provenance, schema, imbalance, missing values, leakage

Solution version: Run the cells in order, compare the outputs with your work, and read the suggested answers.

Learning objectives

After completing this lab, you will be able to:

  • load a prepared table of static and dynamic malware features safely;
  • distinguish identifiers, provenance fields, features, and the binary label;
  • audit class imbalance, source composition, missing values, infinite values, and duplicates;
  • identify columns that must not enter a predictive model;
  • document dataset limitations before modeling.

Safety boundary: This practical uses numeric feature tables only. Do not download, open, or execute malware samples.

In [ ]:
from pathlib import Path
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

DATA_PATH = Path("data/malware_features.csv")

if not DATA_PATH.exists():
    raise FileNotFoundError(
        "Missing data/malware_features.csv. Follow data/README.md, place the three "
        "official UCI CSV files under data/raw/, and run: python data/prepare_dataset.py"
    )

1. Load the prepared table

The preparation utility adds two non-feature columns: source records provenance and label uses 0 = benign, 1 = malware.

In [ ]:
df = pd.read_csv(DATA_PATH, low_memory=False)
required_columns = {"source", "label"}
if not required_columns.issubset(df.columns):
    raise ValueError(f"Missing required columns: {required_columns - set(df.columns)}")

feature_cols = [column for column in df.columns if column not in required_columns]
print("Dataset shape:", df.shape)
print("Number of candidate features:", len(feature_cols))
display(df.head())

2. Audit labels and provenance

source is useful for detecting collection bias, but it must never be used as a predictor. In this dataset, collection source and class are closely related.

In [ ]:
class_counts = df["label"].value_counts().sort_index()
class_proportions = df["label"].value_counts(normalize=True).sort_index()
source_by_label = pd.crosstab(df["source"], df["label"], margins=True)

print("Class counts (0 = benign, 1 = malware):")
display(pd.DataFrame({"count": class_counts, "proportion": class_proportions}))
print("Source by label:")
display(source_by_label)

3. Audit data quality and leakage risks

Missing values will be imputed inside a training pipeline in later labs. Imputing before the split would allow test-set statistics to influence training.

In [ ]:
numeric_features = df[feature_cols].apply(pd.to_numeric, errors="coerce")
infinite_count = int(np.isinf(numeric_features.to_numpy(dtype=float)).sum())
missing_summary = numeric_features.isna().mean().sort_values(ascending=False).rename("missing_rate")
duplicate_count = int(df.duplicated().sum())
constant_features = numeric_features.nunique(dropna=True).loc[lambda values: values <= 1].index.tolist()

suspicious_tokens = ("label", "class", "target", "source", "filename", "hash", "md5", "sha")
suspicious_columns = [
    column for column in feature_cols
    if any(token in column.lower() for token in suspicious_tokens)
]

print("Exact duplicate rows:", duplicate_count)
print("Infinite numeric values:", infinite_count)
print("Constant candidate features:", len(constant_features))
print("Suspicious feature names:", suspicious_columns[:20])
display(missing_summary.head(10).to_frame())

4. Write the dataset risk statement

Record three facts before modeling:

  1. The positive class is the majority class.
  2. Benign and malware rows come from different source collections.
  3. A random split can measure interpolation within this historical table, but not performance on future malware campaigns.

Self-check

In [ ]:
assert {"source", "label"}.issubset(df.columns)
assert len(feature_cols) > 0
assert set(df["label"].dropna().unique()) == {0, 1}
assert set(feature_cols).isdisjoint({"source", "label"})
assert class_counts.loc[1] > class_counts.loc[0]
assert len(missing_summary) == len(feature_cols)
assert infinite_count == 0
print("✓ Lab 1 completed")

Suggested answers

  1. Why exclude source? The source is strongly associated with the label. A model could learn the collection process instead of malware behavior.
  2. Why delay imputation? The imputer must learn medians from training data only; otherwise test information leaks into training.
  3. Why is a random split limited? Samples from the same historical collections appear on both sides of the split, so the test set does not represent future drift or a new collection process.
In [ ]:
In [ ]: