← Back to Table of Contents
Chapter 3

Data Bias Detection

Most bias in AI systems originates from biased training data. This chapter covers techniques for detecting bias in datasets before they poison your models. 弘益人間 - Clean data leads to fair AI.

Understanding Data Bias

Data bias is systematic error in the data collection, sampling, or labeling process that results in misrepresentation of the true population. Unlike random noise, data bias creates consistent patterns that ML models will learn and amplify.

Types of Data Bias

1. Selection Bias

Selection bias occurs when the data collection process systematically excludes or underrepresents certain groups. This is one of the most common and pernicious forms of data bias.

# Detect Selection Bias
import pandas as pd
import numpy as np

def detect_selection_bias(df, population_stats, protected_attr):
    """
    Compare dataset distribution to known population statistics
    """
    dataset_dist = df[protected_attr].value_counts(normalize=True)

    print("Selection Bias Analysis")
    print("=" * 60)
    print(f"\n{'Group':<20} {'Dataset %':<15} {'Population %':<15} {'Bias'}")
    print("-" * 60)

    for group in population_stats.keys():
        dataset_pct = dataset_dist.get(group, 0) * 100
        population_pct = population_stats[group] * 100
        bias = dataset_pct - population_pct

        status = "⚠️" if abs(bias) > 5 else "✓"
        print(f"{group:<20} {dataset_pct:<15.2f} {population_pct:<15.2f} {bias:+.2f}% {status}")

    return dataset_dist

# Example
population = {'Group_A': 0.60, 'Group_B': 0.30, 'Group_C': 0.10}
# detect_selection_bias(dataset, population, 'demographic')

2. Measurement Bias

Measurement bias arises when features are systematically measured differently across groups or when measurement tools have different accuracy for different populations.

# Detect Measurement Bias
def analyze_measurement_quality(df, feature, protected_attr):
    """
    Analyze measurement consistency across groups
    """
    groups = df[protected_attr].unique()

    print(f"Measurement Quality Analysis: {feature}")
    print("=" * 60)

    for group in groups:
        group_data = df[df[protected_attr] == group][feature]

        # Check for patterns suggesting measurement issues
        missing_rate = group_data.isna().sum() / len(group_data)
        outlier_rate = len(group_data[np.abs(group_data - group_data.mean()) > 3*group_data.std()]) / len(group_data)

        print(f"\n{group}:")
        print(f"  Missing rate: {missing_rate:.3f}")
        print(f"  Outlier rate: {outlier_rate:.3f}")
        print(f"  Mean: {group_data.mean():.3f}")
        print(f"  Std: {group_data.std():.3f}")

        if missing_rate > 0.05:
            print(f"  ⚠️ High missing rate may indicate measurement bias")

3. Label Bias

Label bias occurs when the labeling process is inconsistent or biased. This is particularly problematic in supervised learning where labels are the ground truth.

# Detect Label Bias
def detect_label_bias(df, label_col, protected_attr, feature_cols):
    """
    Look for cases where labels differ despite similar features
    """
    from sklearn.neighbors import NearestNeighbors

    groups = df[protected_attr].unique()

    # Find similar instances across groups
    X = df[feature_cols].values
    nbrs = NearestNeighbors(n_neighbors=5).fit(X)

    disagreements = 0
    total_pairs = 0

    for i in range(len(df)):
        distances, indices = nbrs.kneighbors([X[i]])

        for idx in indices[0][1:]:  # Skip self
            if df.iloc[i][protected_attr] != df.iloc[idx][protected_attr]:
                total_pairs += 1
                if df.iloc[i][label_col] != df.iloc[idx][label_col]:
                    disagreements += 1

    disagreement_rate = disagreements / total_pairs if total_pairs > 0 else 0
    print(f"Label disagreement rate for similar cross-group pairs: {disagreement_rate:.3f}")

    if disagreement_rate > 0.3:
        print("⚠️ High disagreement suggests potential label bias")

    return disagreement_rate

Dataset Auditing Techniques

Statistical Distribution Analysis

Comprehensive analysis of how data is distributed across protected groups.

# Comprehensive Dataset Audit
def audit_dataset(df, protected_attributes, target):
    """
    Perform comprehensive bias audit of dataset
    """
    print("DATASET BIAS AUDIT REPORT")
    print("=" * 70)

    # 1. Sample Size Analysis
    print("\n1. SAMPLE SIZE BY GROUP")
    print("-" * 70)
    for attr in protected_attributes:
        counts = df[attr].value_counts()
        print(f"\n{attr}:")
        for group, count in counts.items():
            pct = count / len(df) * 100
            warning = " ⚠️ UNDERREPRESENTED" if pct < 10 else ""
            print(f"  {group}: {count} ({pct:.2f}%){warning}")

    # 2. Label Distribution by Group
    print("\n2. LABEL DISTRIBUTION BY GROUP")
    print("-" * 70)
    for attr in protected_attributes:
        print(f"\n{attr}:")
        cross_tab = pd.crosstab(df[attr], df[target], normalize='index') * 100
        print(cross_tab.round(2))

        # Check for significant disparities
        positive_rates = cross_tab[1] if 1 in cross_tab.columns else cross_tab.iloc[:, 1]
        if positive_rates.max() - positive_rates.min() > 20:
            print("  ⚠️ Significant label imbalance detected across groups")

    # 3. Missing Data Analysis
    print("\n3. MISSING DATA BY GROUP")
    print("-" * 70)
    for attr in protected_attributes:
        print(f"\n{attr}:")
        for group in df[attr].unique():
            group_df = df[df[attr] == group]
            missing_pct = (group_df.isna().sum() / len(group_df) * 100).mean()
            print(f"  {group}: {missing_pct:.2f}% average missing")

    # 4. Feature Correlation with Protected Attributes
    print("\n4. FEATURE CORRELATION WITH PROTECTED ATTRIBUTES")
    print("-" * 70)
    numeric_cols = df.select_dtypes(include=[np.number]).columns
    for attr in protected_attributes:
        if attr in numeric_cols:
            continue
        print(f"\n{attr}:")
        # One-hot encode for correlation
        encoded = pd.get_dummies(df[attr], prefix=attr)
        for col in numeric_cols:
            if col != target:
                corr = df[col].corr(encoded.iloc[:, 0])
                if abs(corr) > 0.3:
                    print(f"  {col}: {corr:.3f} ⚠️ HIGH CORRELATION")

    print("\n" + "=" * 70)
    print("AUDIT COMPLETE")
    print("Review warnings above for potential bias issues")
    print("=" * 70)

Proxy Discrimination Detection

Even when protected attributes are excluded, proxy features can enable discrimination.

# Detect Proxy Features
def detect_proxy_features(df, protected_attr, feature_cols, threshold=0.5):
    """
    Identify features that may serve as proxies for protected attributes
    """
    from sklearn.ensemble import RandomForestClassifier
    from sklearn.model_selection import cross_val_score

    print("PROXY FEATURE DETECTION")
    print("=" * 60)

    X = df[feature_cols]
    y = df[protected_attr]

    # Train model to predict protected attribute from features
    clf = RandomForestClassifier(n_estimators=100, random_state=42)
    scores = cross_val_score(clf, X, y, cv=5)
    mean_score = scores.mean()

    print(f"Can predict {protected_attr} from features: {mean_score:.3f} accuracy")

    if mean_score > threshold:
        print("⚠️ WARNING: Features may contain proxies for protected attribute")

        # Get feature importance
        clf.fit(X, y)
        importance = pd.DataFrame({
            'feature': feature_cols,
            'importance': clf.feature_importances_
        }).sort_values('importance', ascending=False)

        print("\nMost predictive features (potential proxies):")
        print(importance.head(10))

    return mean_score

# Example usage
# detect_proxy_features(df, 'race', ['zipcode', 'income', 'education'])

Temporal Bias Detection

Data collected over time may reflect changing biases or become outdated.

# Temporal Bias Analysis
def analyze_temporal_bias(df, timestamp_col, protected_attr, target):
    """
    Analyze how bias evolves over time
    """
    df['year'] = pd.to_datetime(df[timestamp_col]).dt.year

    print("TEMPORAL BIAS ANALYSIS")
    print("=" * 60)

    for group in df[protected_attr].unique():
        group_df = df[df[protected_attr] == group]

        yearly_stats = group_df.groupby('year').agg({
            target: ['mean', 'count']
        }).round(3)

        print(f"\n{group}:")
        print(yearly_stats)

    # Check for trends
    print("\n⚠️ Look for:")
    print("  - Changing label distributions over time")
    print("  - Varying representation across years")
    print("  - Sudden shifts that may indicate data drift")

Intersectional Bias

Bias often compounds across multiple protected attributes. Intersectional analysis examines combinations of attributes.

# Intersectional Bias Analysis
def intersectional_analysis(df, protected_attrs, target):
    """
    Analyze bias across intersections of multiple protected attributes
    """
    print("INTERSECTIONAL BIAS ANALYSIS")
    print("=" * 70)

    # Create intersection groups
    df['intersection'] = df[protected_attrs].apply(
        lambda x: '_'.join(x.astype(str)), axis=1
    )

    results = df.groupby('intersection').agg({
        target: ['mean', 'count']
    }).round(3)

    results.columns = ['positive_rate', 'count']
    results = results.sort_values('positive_rate')

    print(results)

    # Identify most disadvantaged intersections
    min_rate = results['positive_rate'].min()
    max_rate = results['positive_rate'].max()
    disparity = max_rate - min_rate

    print(f"\nPositive rate range: {min_rate:.3f} to {max_rate:.3f}")
    print(f"Disparity: {disparity:.3f}")

    if disparity > 0.2:
        print("⚠️ Significant intersectional bias detected")

        print("\nMost disadvantaged groups:")
        print(results.head(3))

    return results

Data Quality Checks

Completeness

Consistency

Validity

Best Practices for Data Collection

弘益人間 Data Principles

Creating a Data Bias Report

# Generate Comprehensive Data Bias Report
def generate_data_bias_report(df, protected_attrs, target, feature_cols):
    """
    Generate comprehensive data bias assessment report
    """
    report = {
        'timestamp': pd.Timestamp.now(),
        'total_samples': len(df),
        'issues': []
    }

    print("\n" + "=" * 70)
    print("DATA BIAS DETECTION REPORT")
    print("=" * 70)
    print(f"Generated: {report['timestamp']}")
    print(f"Total Samples: {report['total_samples']}")

    # Run all checks
    audit_dataset(df, protected_attrs, target)

    for attr in protected_attrs:
        proxy_score = detect_proxy_features(df, attr, feature_cols)
        if proxy_score > 0.5:
            report['issues'].append(f"Proxy features detected for {attr}")

    # Intersectional analysis
    if len(protected_attrs) > 1:
        intersectional_analysis(df, protected_attrs, target)

    print("\n" + "=" * 70)
    print("RECOMMENDATIONS")
    print("=" * 70)
    print("1. Address underrepresented groups through additional data collection")
    print("2. Review and standardize labeling procedures")
    print("3. Investigate high-correlation features for proxy discrimination")
    print("4. Consider stratified sampling for future data collection")
    print("5. Implement continuous monitoring of data quality")
    print("\n弘익人間 - Fair data is the foundation of fair AI")
    print("=" * 70)

    return report

Chapter Summary

Review Questions

  1. What is the difference between selection bias and sampling bias? Provide examples of each.
  2. How can you detect if a feature is serving as a proxy for a protected attribute?
  3. Why is label bias particularly problematic in supervised learning? How can it be detected?
  4. Describe a scenario where measurement bias could affect a facial recognition system.
  5. What is intersectional bias and why is it important to analyze?
  6. If a dataset has 90% representation from one demographic group, what problems might this cause?
  7. How would you determine if your training data is representative of your deployment population?
  8. What role does temporal analysis play in bias detection? What can change over time?
  9. Design a data collection strategy for a hiring AI that follows 弘익人間 principles.
  10. What should be included in a comprehensive data bias audit report?