help@rskworld.in +91 93305 39277
RSK World
  • Home
  • Development
    • Web Development
    • Mobile Apps
    • Software
    • Games
    • Project
  • Technologies
    • Data Science
    • AI Development
    • Cloud Development
    • Blockchain
    • Cyber Security
    • Dev Tools
    • Testing Tools
  • Blog
  • About
  • Contact

Theme Settings

Color Scheme
Display Options
Font Size
100%
Back to Project
RSK World
text-classification
/
scripts
RSK World
text-classification
Text Classification Dataset - NLP + Multi-Class Classification + Machine Learning
scripts
  • __init__.py2.3 KB
  • active_learning.py26.8 KB
  • api_server.py12.7 KB
  • batch_processor.py16.4 KB
  • data_augmentation.py18.2 KB
  • data_quality.py20 KB
  • deep_learning.py24.2 KB
  • hyperparameter_tuning.py22.5 KB
  • model_explainability.py17.9 KB
  • preprocessing.py8.7 KB
  • train_classifier.py13.8 KB
  • train_transformers.py12.5 KB
  • visualizations.py19 KB
data_preprocessing.pydata_augmentation.pydata_quality.py
scripts/data_augmentation.py
Raw Download
Find: Go to:
"""
================================================================================
Text Classification Dataset - Advanced Data Augmentation Module
================================================================================
Project: Text Classification Dataset
Category: Text Data / NLP

Author: Molla Samser
Designer & Tester: Rima Khatun
Website: https://rskworld.in
Email: help@rskworld.in | support@rskworld.in
Phone: +91 93305 39277

Copyright (c) 2026 RSK World - All Rights Reserved
Content used for educational purposes only.

Features:
- Synonym Replacement
- Random Insertion
- Random Swap
- Random Deletion
- Back Translation (simulated)
- Contextual Word Embeddings Augmentation

Created: December 2026
================================================================================
"""

import random
import re
from typing import List, Tuple, Optional
from collections import defaultdict

# Project information
__author__ = "Molla Samser"
__website__ = "https://rskworld.in"
__email__ = "help@rskworld.in"


class TextAugmenter:
    """
    Advanced text augmentation for NLP tasks.
    
    Techniques:
    1. Synonym Replacement (SR)
    2. Random Insertion (RI)
    3. Random Swap (RS)
    4. Random Deletion (RD)
    5. Back Translation (BT) - simulated
    6. Character-level augmentation
    
    Author: Molla Samser | RSK World (https://rskworld.in)
    """
    
    # Simple synonym dictionary for demonstration
    SYNONYMS = {
        'good': ['great', 'excellent', 'wonderful', 'fantastic', 'superb'],
        'bad': ['poor', 'terrible', 'awful', 'horrible', 'dreadful'],
        'big': ['large', 'huge', 'enormous', 'massive', 'giant'],
        'small': ['tiny', 'little', 'miniature', 'compact', 'petite'],
        'new': ['novel', 'fresh', 'recent', 'modern', 'innovative'],
        'old': ['ancient', 'aged', 'vintage', 'classic', 'traditional'],
        'fast': ['quick', 'rapid', 'swift', 'speedy', 'hasty'],
        'slow': ['gradual', 'leisurely', 'unhurried', 'sluggish', 'delayed'],
        'important': ['significant', 'crucial', 'vital', 'essential', 'critical'],
        'announces': ['reveals', 'declares', 'states', 'proclaims', 'discloses'],
        'launches': ['introduces', 'releases', 'unveils', 'debuts', 'presents'],
        'develops': ['creates', 'builds', 'designs', 'produces', 'constructs'],
        'discovers': ['finds', 'uncovers', 'detects', 'identifies', 'locates'],
        'achieves': ['accomplishes', 'attains', 'reaches', 'gains', 'secures'],
        'wins': ['triumphs', 'conquers', 'prevails', 'succeeds', 'captures'],
        'breaks': ['shatters', 'surpasses', 'exceeds', 'beats', 'tops'],
        'shows': ['demonstrates', 'displays', 'exhibits', 'reveals', 'indicates'],
        'says': ['states', 'declares', 'mentions', 'reports', 'claims'],
        'technology': ['tech', 'innovation', 'advancement', 'development'],
        'company': ['firm', 'corporation', 'enterprise', 'business', 'organization'],
        'market': ['industry', 'sector', 'field', 'arena', 'domain'],
        'research': ['study', 'investigation', 'analysis', 'examination', 'inquiry'],
        'scientist': ['researcher', 'expert', 'specialist', 'scholar', 'analyst'],
        'government': ['administration', 'authorities', 'regime', 'state', 'officials'],
        'economy': ['market', 'finances', 'commerce', 'trade', 'business'],
        'revolutionary': ['groundbreaking', 'innovative', 'pioneering', 'transformative'],
        'historic': ['landmark', 'significant', 'momentous', 'unprecedented', 'notable'],
    }
    
    def __init__(
        self,
        alpha_sr: float = 0.1,
        alpha_ri: float = 0.1,
        alpha_rs: float = 0.1,
        alpha_rd: float = 0.1,
        num_aug: int = 4,
        random_state: Optional[int] = None
    ):
        """
        Initialize the TextAugmenter.
        
        Args:
            alpha_sr: Probability for synonym replacement
            alpha_ri: Probability for random insertion
            alpha_rs: Probability for random swap
            alpha_rd: Probability for random deletion
            num_aug: Number of augmented samples per original
            random_state: Random seed for reproducibility
        """
        self.alpha_sr = alpha_sr
        self.alpha_ri = alpha_ri
        self.alpha_rs = alpha_rs
        self.alpha_rd = alpha_rd
        self.num_aug = num_aug
        
        if random_state is not None:
            random.seed(random_state)
        
        # Build reverse synonym lookup
        self.word_to_synonyms = defaultdict(list)
        for word, syns in self.SYNONYMS.items():
            self.word_to_synonyms[word.lower()] = [s.lower() for s in syns]
            for syn in syns:
                self.word_to_synonyms[syn.lower()].append(word.lower())
    
    def get_synonyms(self, word: str) -> List[str]:
        """Get synonyms for a word."""
        return self.word_to_synonyms.get(word.lower(), [])
    
    def synonym_replacement(self, words: List[str], n: int) -> List[str]:
        """
        Replace n random words with their synonyms.
        
        Args:
            words: List of words
            n: Number of words to replace
            
        Returns:
            Augmented word list
        """
        new_words = words.copy()
        random_word_list = list(set([w for w in words if self.get_synonyms(w)]))
        random.shuffle(random_word_list)
        
        num_replaced = 0
        for random_word in random_word_list:
            synonyms = self.get_synonyms(random_word)
            if synonyms:
                synonym = random.choice(synonyms)
                new_words = [synonym if w.lower() == random_word.lower() else w for w in new_words]
                num_replaced += 1
            if num_replaced >= n:
                break
        
        return new_words
    
    def random_insertion(self, words: List[str], n: int) -> List[str]:
        """
        Randomly insert n synonyms into the sentence.
        
        Args:
            words: List of words
            n: Number of words to insert
            
        Returns:
            Augmented word list
        """
        new_words = words.copy()
        
        for _ in range(n):
            self._add_word(new_words)
        
        return new_words
    
    def _add_word(self, words: List[str]):
        """Add a synonym of a random word at a random position."""
        if not words:
            return
        
        synonyms = []
        counter = 0
        while not synonyms:
            random_word = words[random.randint(0, len(words) - 1)]
            synonyms = self.get_synonyms(random_word)
            counter += 1
            if counter >= 10:
                return
        
        random_synonym = random.choice(synonyms)
        random_idx = random.randint(0, len(words) - 1)
        words.insert(random_idx, random_synonym)
    
    def random_swap(self, words: List[str], n: int) -> List[str]:
        """
        Randomly swap n pairs of words.
        
        Args:
            words: List of words
            n: Number of swaps
            
        Returns:
            Augmented word list
        """
        new_words = words.copy()
        
        for _ in range(n):
            new_words = self._swap_word(new_words)
        
        return new_words
    
    def _swap_word(self, words: List[str]) -> List[str]:
        """Swap two random words."""
        if len(words) < 2:
            return words
        
        new_words = words.copy()
        idx1, idx2 = random.sample(range(len(new_words)), 2)
        new_words[idx1], new_words[idx2] = new_words[idx2], new_words[idx1]
        
        return new_words
    
    def random_deletion(self, words: List[str], p: float) -> List[str]:
        """
        Randomly delete words with probability p.
        
        Args:
            words: List of words
            p: Probability of deletion
            
        Returns:
            Augmented word list
        """
        if len(words) == 1:
            return words
        
        new_words = [w for w in words if random.random() > p]
        
        if not new_words:
            return [random.choice(words)]
        
        return new_words
    
    def character_swap(self, text: str, p: float = 0.01) -> str:
        """
        Randomly swap adjacent characters (simulates typos).
        
        Args:
            text: Input text
            p: Probability of swap per character
            
        Returns:
            Augmented text
        """
        chars = list(text)
        
        for i in range(len(chars) - 1):
            if random.random() < p and chars[i].isalpha() and chars[i + 1].isalpha():
                chars[i], chars[i + 1] = chars[i + 1], chars[i]
        
        return ''.join(chars)
    
    def keyboard_augment(self, text: str, p: float = 0.01) -> str:
        """
        Simulate keyboard typing errors.
        
        Args:
            text: Input text
            p: Probability of error per character
            
        Returns:
            Augmented text
        """
        keyboard_neighbors = {
            'a': 'sqwz', 'b': 'vghn', 'c': 'xdfv', 'd': 'serfcx',
            'e': 'wrsdf', 'f': 'drtgvc', 'g': 'ftyhbv', 'h': 'gyujnb',
            'i': 'ujklo', 'j': 'huiknm', 'k': 'jiolm', 'l': 'kop',
            'm': 'njk', 'n': 'bhjm', 'o': 'iklp', 'p': 'ol',
            'q': 'wa', 'r': 'edft', 's': 'awedxz', 't': 'rfgy',
            'u': 'yhjki', 'v': 'cfgb', 'w': 'qase', 'x': 'zsdc',
            'y': 'tghu', 'z': 'asx'
        }
        
        chars = list(text.lower())
        
        for i, char in enumerate(chars):
            if char in keyboard_neighbors and random.random() < p:
                chars[i] = random.choice(keyboard_neighbors[char])
        
        return ''.join(chars)
    
    def augment(self, text: str) -> List[str]:
        """
        Apply all augmentation techniques to generate multiple versions.
        
        Args:
            text: Input text
            
        Returns:
            List of augmented texts
        """
        words = text.split()
        num_words = len(words)
        
        augmented_texts = []
        
        for _ in range(self.num_aug):
            aug_text = None
            
            # Randomly choose augmentation technique
            technique = random.choice(['sr', 'ri', 'rs', 'rd', 'char', 'kb'])
            
            if technique == 'sr':
                n = max(1, int(self.alpha_sr * num_words))
                aug_words = self.synonym_replacement(words, n)
                aug_text = ' '.join(aug_words)
            
            elif technique == 'ri':
                n = max(1, int(self.alpha_ri * num_words))
                aug_words = self.random_insertion(words, n)
                aug_text = ' '.join(aug_words)
            
            elif technique == 'rs':
                n = max(1, int(self.alpha_rs * num_words))
                aug_words = self.random_swap(words, n)
                aug_text = ' '.join(aug_words)
            
            elif technique == 'rd':
                aug_words = self.random_deletion(words, self.alpha_rd)
                aug_text = ' '.join(aug_words)
            
            elif technique == 'char':
                aug_text = self.character_swap(text)
            
            elif technique == 'kb':
                aug_text = self.keyboard_augment(text)
            
            if aug_text and aug_text != text:
                augmented_texts.append(aug_text)
        
        return augmented_texts
    
    def augment_dataset(
        self,
        texts: List[str],
        labels: List[int],
        augment_per_sample: int = 2
    ) -> Tuple[List[str], List[int]]:
        """
        Augment an entire dataset.
        
        Args:
            texts: List of original texts
            labels: List of labels
            augment_per_sample: Augmentations per sample
            
        Returns:
            Tuple of (augmented_texts, augmented_labels)
        """
        self.num_aug = augment_per_sample
        
        all_texts = list(texts)
        all_labels = list(labels)
        
        for text, label in zip(texts, labels):
            aug_texts = self.augment(text)
            all_texts.extend(aug_texts)
            all_labels.extend([label] * len(aug_texts))
        
        return all_texts, all_labels


class BackTranslator:
    """
    Simulated back-translation augmentation.
    Uses word variations to simulate translation effects.
    
    Author: Molla Samser | RSK World (https://rskworld.in)
    """
    
    VARIATIONS = {
        'the': ['a', 'this', 'that'],
        'is': ['was', 'becomes', 'remains'],
        'are': ['were', 'become', 'remain'],
        'has': ['had', 'possesses', 'holds'],
        'have': ['had', 'possess', 'hold'],
        'will': ['would', 'shall', 'might'],
        'can': ['could', 'may', 'might'],
        'very': ['extremely', 'highly', 'quite'],
        'said': ['stated', 'mentioned', 'declared'],
        'made': ['created', 'produced', 'developed'],
    }
    
    def __init__(self, variation_prob: float = 0.3):
        self.variation_prob = variation_prob
    
    def back_translate(self, text: str) -> str:
        """
        Simulate back-translation by applying word variations.
        
        Args:
            text: Input text
            
        Returns:
            Simulated back-translated text
        """
        words = text.split()
        new_words = []
        
        for word in words:
            lower_word = word.lower()
            if lower_word in self.VARIATIONS and random.random() < self.variation_prob:
                variation = random.choice(self.VARIATIONS[lower_word])
                # Preserve capitalization
                if word[0].isupper():
                    variation = variation.capitalize()
                new_words.append(variation)
            else:
                new_words.append(word)
        
        return ' '.join(new_words)


class MixupAugmenter:
    """
    Text mixup augmentation for soft label training.
    
    Author: Molla Samser | RSK World (https://rskworld.in)
    """
    
    def __init__(self, alpha: float = 0.2):
        """
        Initialize mixup augmenter.
        
        Args:
            alpha: Beta distribution parameter for mixing
        """
        self.alpha = alpha
    
    def mixup_texts(
        self,
        text1: str,
        text2: str,
        label1: int,
        label2: int,
        num_classes: int
    ) -> Tuple[str, List[float]]:
        """
        Mix two texts and their labels.
        
        Args:
            text1: First text
            text2: Second text
            label1: First label
            label2: Second label
            num_classes: Total number of classes
            
        Returns:
            Tuple of (mixed_text, soft_labels)
        """
        # Get mixing coefficient
        lam = random.betavariate(self.alpha, self.alpha)
        
        # Mix texts by interleaving sentences/words
        words1 = text1.split()
        words2 = text2.split()
        
        mixed_words = []
        max_len = max(len(words1), len(words2))
        
        for i in range(max_len):
            if random.random() < lam:
                if i < len(words1):
                    mixed_words.append(words1[i])
            else:
                if i < len(words2):
                    mixed_words.append(words2[i])
        
        mixed_text = ' '.join(mixed_words) if mixed_words else text1
        
        # Create soft labels
        soft_labels = [0.0] * num_classes
        soft_labels[label1] = lam
        soft_labels[label2] = 1 - lam
        
        return mixed_text, soft_labels


def augment_csv_dataset(
    input_path: str,
    output_path: str,
    augment_per_sample: int = 2,
    random_state: int = 42
):
    """
    Augment a CSV dataset and save the result.
    
    Args:
        input_path: Path to input CSV
        output_path: Path to output CSV
        augment_per_sample: Number of augmentations per sample
        random_state: Random seed
        
    Author: Molla Samser | RSK World (https://rskworld.in)
    """
    import pandas as pd
    
    print(f"\n{'='*60}")
    print("Text Data Augmentation - RSK World")
    print(f"Author: {__author__} | Website: {__website__}")
    print(f"{'='*60}\n")
    
    # Load dataset
    df = pd.read_csv(input_path, comment='#')
    original_size = len(df)
    print(f"Original dataset size: {original_size}")
    
    # Initialize augmenter
    augmenter = TextAugmenter(
        num_aug=augment_per_sample,
        random_state=random_state
    )
    
    # Augment
    aug_texts, aug_labels = augmenter.augment_dataset(
        df['text'].tolist(),
        df['label'].tolist(),
        augment_per_sample
    )
    
    # Create augmented dataframe
    aug_df = pd.DataFrame({
        'id': range(1, len(aug_texts) + 1),
        'text': aug_texts,
        'category': [df[df['label'] == l]['category'].iloc[0] for l in aug_labels],
        'label': aug_labels
    })
    
    # Save
    aug_df.to_csv(output_path, index=False)
    
    print(f"Augmented dataset size: {len(aug_df)}")
    print(f"Increase: {len(aug_df) - original_size} samples ({((len(aug_df)/original_size)-1)*100:.1f}%)")
    print(f"Saved to: {output_path}")


if __name__ == "__main__":
    # Demo
    print(f"\n{'='*60}")
    print("Text Augmentation Demo - RSK World")
    print(f"Author: {__author__} | Website: {__website__}")
    print(f"{'='*60}\n")
    
    augmenter = TextAugmenter(num_aug=5, random_state=42)
    
    sample_text = "Apple announces revolutionary new iPhone featuring advanced AI capabilities."
    
    print(f"Original: {sample_text}\n")
    print("Augmented versions:")
    print("-" * 50)
    
    for i, aug_text in enumerate(augmenter.augment(sample_text), 1):
        print(f"{i}. {aug_text}")
    
    print(f"\n{'='*60}")
    print("Augmentation Demo Complete!")

563 lines•18.2 KB
python
scripts/data_quality.py
Raw Download
Find: Go to:
"""
================================================================================
Text Classification Dataset - Data Quality Analyzer
================================================================================
Project: Text Classification Dataset
Category: Text Data / NLP

Author: Molla Samser
Designer & Tester: Rima Khatun
Website: https://rskworld.in
Email: help@rskworld.in | support@rskworld.in
Phone: +91 93305 39277

Copyright (c) 2026 RSK World - All Rights Reserved
Content used for educational purposes only.

Features:
- Duplicate Detection
- Missing Value Analysis
- Class Imbalance Detection
- Text Quality Metrics
- Outlier Detection
- Language Detection
- Noise Detection
- Data Completeness Scoring
- Automated Recommendations

Created: December 2026
================================================================================
"""

import re
import string
from typing import Dict, List, Tuple, Optional, Set
from collections import Counter
from datetime import datetime

import numpy as np
import pandas as pd

# Project information
__author__ = "Molla Samser"
__website__ = "https://rskworld.in"
__email__ = "help@rskworld.in"

# Category mapping
CATEGORIES = {
    0: 'Technology', 1: 'Sports', 2: 'Politics',
    3: 'Entertainment', 4: 'Business', 5: 'Science'
}


class DataQualityAnalyzer:
    """
    Comprehensive data quality analyzer for text classification datasets.
    
    Analyzes:
    - Data completeness
    - Duplicate records
    - Class balance
    - Text quality metrics
    - Potential label errors
    - Outliers
    
    Author: Molla Samser | RSK World (https://rskworld.in)
    """
    
    def __init__(self, verbose: bool = True):
        """
        Initialize the analyzer.
        
        Args:
            verbose: Print detailed analysis
        """
        self.verbose = verbose
        self.report = {}
        self.issues = []
        self.warnings = []
        self.recommendations = []
    
    def analyze(
        self,
        df: pd.DataFrame,
        text_column: str = 'text',
        label_column: str = 'label'
    ) -> Dict:
        """
        Perform comprehensive data quality analysis.
        
        Args:
            df: DataFrame to analyze
            text_column: Name of text column
            label_column: Name of label column
            
        Returns:
            Comprehensive quality report
        """
        if self.verbose:
            print(f"\n{'='*60}")
            print("Data Quality Analysis - RSK World")
            print(f"Author: {__author__} | Website: {__website__}")
            print(f"{'='*60}\n")
        
        # Basic info
        self._analyze_basic_info(df, text_column, label_column)
        
        # Missing values
        self._analyze_missing_values(df, text_column, label_column)
        
        # Duplicates
        self._analyze_duplicates(df, text_column)
        
        # Class balance
        self._analyze_class_balance(df, label_column)
        
        # Text quality
        self._analyze_text_quality(df, text_column)
        
        # Outliers
        self._analyze_outliers(df, text_column)
        
        # Potential label errors
        self._analyze_potential_label_errors(df, text_column, label_column)
        
        # Generate recommendations
        self._generate_recommendations()
        
        # Calculate overall score
        self._calculate_quality_score()
        
        # Print summary
        if self.verbose:
            self._print_summary()
        
        return self.report
    
    def _analyze_basic_info(self, df: pd.DataFrame, text_col: str, label_col: str):
        """Analyze basic dataset information."""
        self.report['basic_info'] = {
            'total_samples': len(df),
            'columns': list(df.columns),
            'text_column': text_col,
            'label_column': label_col,
            'memory_usage_mb': df.memory_usage(deep=True).sum() / 1024**2
        }
        
        if self.verbose:
            print("1. Basic Information")
            print("-" * 40)
            print(f"   Total samples: {len(df):,}")
            print(f"   Columns: {list(df.columns)}")
            print(f"   Memory usage: {self.report['basic_info']['memory_usage_mb']:.2f} MB\n")
    
    def _analyze_missing_values(self, df: pd.DataFrame, text_col: str, label_col: str):
        """Analyze missing values."""
        missing_text = df[text_col].isna().sum()
        missing_label = df[label_col].isna().sum()
        empty_text = (df[text_col].str.strip() == '').sum() if df[text_col].dtype == 'object' else 0
        
        self.report['missing_values'] = {
            'missing_text': int(missing_text),
            'missing_labels': int(missing_label),
            'empty_text': int(empty_text),
            'missing_percentage': float((missing_text + missing_label) / (len(df) * 2) * 100)
        }
        
        if missing_text > 0 or missing_label > 0:
            self.issues.append(f"Found {missing_text + missing_label} missing values")
        if empty_text > 0:
            self.warnings.append(f"Found {empty_text} empty text entries")
        
        if self.verbose:
            print("2. Missing Values Analysis")
            print("-" * 40)
            print(f"   Missing text: {missing_text}")
            print(f"   Missing labels: {missing_label}")
            print(f"   Empty text: {empty_text}\n")
    
    def _analyze_duplicates(self, df: pd.DataFrame, text_col: str):
        """Analyze duplicate records."""
        # Exact duplicates
        exact_duplicates = df.duplicated().sum()
        
        # Text duplicates (same text, possibly different labels)
        text_duplicates = df[text_col].duplicated().sum()
        
        # Near duplicates (simplified check)
        normalized_texts = df[text_col].str.lower().str.strip()
        normalized_duplicates = normalized_texts.duplicated().sum()
        
        self.report['duplicates'] = {
            'exact_duplicates': int(exact_duplicates),
            'text_duplicates': int(text_duplicates),
            'normalized_duplicates': int(normalized_duplicates),
            'duplicate_percentage': float(text_duplicates / len(df) * 100)
        }
        
        if text_duplicates > 0:
            self.warnings.append(f"Found {text_duplicates} duplicate texts")
        
        if self.verbose:
            print("3. Duplicate Analysis")
            print("-" * 40)
            print(f"   Exact duplicates: {exact_duplicates}")
            print(f"   Text duplicates: {text_duplicates}")
            print(f"   After normalization: {normalized_duplicates}\n")
    
    def _analyze_class_balance(self, df: pd.DataFrame, label_col: str):
        """Analyze class distribution."""
        class_counts = df[label_col].value_counts().sort_index()
        total = len(df)
        
        # Calculate imbalance ratio
        max_class = class_counts.max()
        min_class = class_counts.min()
        imbalance_ratio = max_class / min_class if min_class > 0 else float('inf')
        
        # Calculate entropy (higher = more balanced)
        proportions = class_counts / total
        entropy = -sum(p * np.log2(p) for p in proportions if p > 0)
        max_entropy = np.log2(len(class_counts))
        normalized_entropy = entropy / max_entropy if max_entropy > 0 else 0
        
        self.report['class_balance'] = {
            'class_distribution': class_counts.to_dict(),
            'class_percentages': {k: float(v/total*100) for k, v in class_counts.items()},
            'imbalance_ratio': float(imbalance_ratio),
            'entropy': float(entropy),
            'normalized_entropy': float(normalized_entropy),
            'is_balanced': imbalance_ratio < 2.0
        }
        
        if imbalance_ratio > 3.0:
            self.issues.append(f"Severe class imbalance (ratio: {imbalance_ratio:.1f}:1)")
        elif imbalance_ratio > 2.0:
            self.warnings.append(f"Moderate class imbalance (ratio: {imbalance_ratio:.1f}:1)")
        
        if self.verbose:
            print("4. Class Balance Analysis")
            print("-" * 40)
            for label, count in class_counts.items():
                cat_name = CATEGORIES.get(label, f'Class {label}')
                print(f"   {cat_name}: {count} ({count/total*100:.1f}%)")
            print(f"   Imbalance ratio: {imbalance_ratio:.2f}:1")
            print(f"   Balance score: {normalized_entropy*100:.1f}%\n")
    
    def _analyze_text_quality(self, df: pd.DataFrame, text_col: str):
        """Analyze text quality metrics."""
        texts = df[text_col].dropna()
        
        # Length statistics
        word_counts = texts.str.split().str.len()
        char_counts = texts.str.len()
        
        # Special character ratio
        special_char_ratios = texts.apply(
            lambda x: len(re.findall(r'[^a-zA-Z0-9\s]', str(x))) / max(len(str(x)), 1)
        )
        
        # URL count
        url_pattern = r'https?://\S+|www\.\S+'
        url_counts = texts.apply(lambda x: len(re.findall(url_pattern, str(x))))
        
        # Uppercase ratio
        uppercase_ratios = texts.apply(
            lambda x: sum(1 for c in str(x) if c.isupper()) / max(len(str(x)), 1)
        )
        
        self.report['text_quality'] = {
            'word_count': {
                'mean': float(word_counts.mean()),
                'std': float(word_counts.std()),
                'min': int(word_counts.min()),
                'max': int(word_counts.max()),
                'median': float(word_counts.median())
            },
            'char_count': {
                'mean': float(char_counts.mean()),
                'std': float(char_counts.std()),
                'min': int(char_counts.min()),
                'max': int(char_counts.max())
            },
            'special_char_ratio': {
                'mean': float(special_char_ratios.mean()),
                'max': float(special_char_ratios.max())
            },
            'texts_with_urls': int(url_counts.gt(0).sum()),
            'avg_uppercase_ratio': float(uppercase_ratios.mean()),
            'very_short_texts': int(word_counts.lt(5).sum()),
            'very_long_texts': int(word_counts.gt(100).sum())
        }
        
        very_short = word_counts.lt(5).sum()
        if very_short > len(df) * 0.1:
            self.warnings.append(f"{very_short} texts have fewer than 5 words")
        
        if self.verbose:
            print("5. Text Quality Metrics")
            print("-" * 40)
            print(f"   Average word count: {word_counts.mean():.1f}")
            print(f"   Word count std: {word_counts.std():.1f}")
            print(f"   Min/Max words: {word_counts.min()} / {word_counts.max()}")
            print(f"   Texts with URLs: {url_counts.gt(0).sum()}")
            print(f"   Very short texts (<5 words): {very_short}\n")
    
    def _analyze_outliers(self, df: pd.DataFrame, text_col: str):
        """Detect outlier texts."""
        texts = df[text_col].dropna()
        word_counts = texts.str.split().str.len()
        
        # IQR method for outliers
        Q1 = word_counts.quantile(0.25)
        Q3 = word_counts.quantile(0.75)
        IQR = Q3 - Q1
        
        lower_bound = Q1 - 1.5 * IQR
        upper_bound = Q3 + 1.5 * IQR
        
        outliers_low = word_counts.lt(lower_bound).sum()
        outliers_high = word_counts.gt(upper_bound).sum()
        
        self.report['outliers'] = {
            'lower_bound': float(max(0, lower_bound)),
            'upper_bound': float(upper_bound),
            'outliers_low': int(outliers_low),
            'outliers_high': int(outliers_high),
            'total_outliers': int(outliers_low + outliers_high),
            'outlier_percentage': float((outliers_low + outliers_high) / len(df) * 100)
        }
        
        if outliers_high > len(df) * 0.05:
            self.warnings.append(f"{outliers_high} texts are unusually long")
        
        if self.verbose:
            print("6. Outlier Detection")
            print("-" * 40)
            print(f"   Normal range: {max(0, lower_bound):.0f} - {upper_bound:.0f} words")
            print(f"   Outliers (too short): {outliers_low}")
            print(f"   Outliers (too long): {outliers_high}\n")
    
    def _analyze_potential_label_errors(self, df: pd.DataFrame, text_col: str, label_col: str):
        """Detect potential label errors using keyword analysis."""
        # Category keywords
        keywords = {
            0: ['apple', 'google', 'microsoft', 'ai', 'tech', 'software', 'app'],
            1: ['football', 'basketball', 'tennis', 'game', 'team', 'player', 'win'],
            2: ['government', 'president', 'congress', 'election', 'vote', 'policy'],
            3: ['movie', 'film', 'music', 'actor', 'netflix', 'entertainment'],
            4: ['stock', 'market', 'business', 'company', 'economy', 'profit'],
            5: ['scientist', 'research', 'nasa', 'space', 'climate', 'discovery']
        }
        
        potential_errors = []
        
        for idx, row in df.iterrows():
            text = str(row[text_col]).lower()
            label = row[label_col]
            
            # Count keyword matches for each category
            scores = {}
            for cat, kws in keywords.items():
                scores[cat] = sum(1 for kw in kws if kw in text)
            
            # Check if another category has more matches
            if scores:
                best_match = max(scores, key=scores.get)
                if scores[best_match] > 2 and best_match != label and scores.get(label, 0) == 0:
                    potential_errors.append({
                        'index': idx,
                        'current_label': int(label),
                        'suggested_label': int(best_match),
                        'confidence': scores[best_match]
                    })
        
        self.report['potential_label_errors'] = {
            'count': len(potential_errors),
            'percentage': float(len(potential_errors) / len(df) * 100),
            'examples': potential_errors[:10]  # First 10
        }
        
        if len(potential_errors) > len(df) * 0.05:
            self.warnings.append(f"Found {len(potential_errors)} potential label errors")
        
        if self.verbose:
            print("7. Potential Label Errors")
            print("-" * 40)
            print(f"   Potential errors found: {len(potential_errors)}")
            print(f"   Error rate: {len(potential_errors)/len(df)*100:.1f}%\n")
    
    def _generate_recommendations(self):
        """Generate recommendations based on analysis."""
        r = self.report
        
        # Missing values
        if r['missing_values']['missing_text'] > 0 or r['missing_values']['empty_text'] > 0:
            self.recommendations.append("Remove or impute missing/empty text values")
        
        # Duplicates
        if r['duplicates']['text_duplicates'] > 0:
            self.recommendations.append("Consider removing duplicate texts to prevent data leakage")
        
        # Class imbalance
        if r['class_balance']['imbalance_ratio'] > 2.0:
            self.recommendations.append("Apply class balancing techniques (oversampling, undersampling, or class weights)")
        
        # Text quality
        if r['text_quality']['very_short_texts'] > 10:
            self.recommendations.append("Review very short texts - they may lack sufficient information")
        
        # Outliers
        if r['outliers']['total_outliers'] > 10:
            self.recommendations.append("Review outlier texts - consider truncation or removal")
        
        # Label errors
        if r['potential_label_errors']['count'] > 5:
            self.recommendations.append("Review flagged potential label errors for correction")
        
        self.report['recommendations'] = self.recommendations
    
    def _calculate_quality_score(self):
        """Calculate overall data quality score."""
        r = self.report
        
        scores = []
        
        # Missing value score (0-100)
        missing_score = 100 - min(100, r['missing_values']['missing_percentage'] * 10)
        scores.append(('Completeness', missing_score))
        
        # Duplicate score (0-100)
        dup_score = 100 - min(100, r['duplicates']['duplicate_percentage'] * 5)
        scores.append(('Uniqueness', dup_score))
        
        # Balance score (0-100)
        balance_score = r['class_balance']['normalized_entropy'] * 100
        scores.append(('Balance', balance_score))
        
        # Outlier score (0-100)
        outlier_score = 100 - min(100, r['outliers']['outlier_percentage'] * 5)
        scores.append(('Consistency', outlier_score))
        
        # Label quality score (0-100)
        label_score = 100 - min(100, r['potential_label_errors']['percentage'] * 10)
        scores.append(('Label Quality', label_score))
        
        overall_score = sum(s[1] for s in scores) / len(scores)
        
        self.report['quality_scores'] = {
            'dimensions': dict(scores),
            'overall_score': float(overall_score),
            'grade': self._score_to_grade(overall_score)
        }
    
    def _score_to_grade(self, score: float) -> str:
        """Convert score to letter grade."""
        if score >= 90: return 'A'
        elif score >= 80: return 'B'
        elif score >= 70: return 'C'
        elif score >= 60: return 'D'
        else: return 'F'
    
    def _print_summary(self):
        """Print analysis summary."""
        print("=" * 60)
        print("QUALITY SCORE SUMMARY")
        print("=" * 60)
        
        scores = self.report['quality_scores']
        for dim, score in scores['dimensions'].items():
            bar = 'ā–ˆ' * int(score // 10) + 'ā–‘' * (10 - int(score // 10))
            print(f"   {dim:15} [{bar}] {score:.0f}%")
        
        print("-" * 60)
        print(f"   Overall Score: {scores['overall_score']:.1f}% (Grade: {scores['grade']})")
        
        if self.issues:
            print(f"\nšŸ”“ Issues ({len(self.issues)}):")
            for issue in self.issues:
                print(f"   • {issue}")
        
        if self.warnings:
            print(f"\n🟔 Warnings ({len(self.warnings)}):")
            for warning in self.warnings:
                print(f"   • {warning}")
        
        if self.recommendations:
            print(f"\nšŸ’” Recommendations ({len(self.recommendations)}):")
            for rec in self.recommendations:
                print(f"   • {rec}")
        
        print(f"\n{'='*60}")
        print(f"Analysis complete! | Author: {__author__}")
        print(f"{'='*60}")
    
    def export_report(self, output_path: str = 'quality_report.json'):
        """Export report to JSON file."""
        import json
        
        export_data = {
            'metadata': {
                'author': __author__,
                'website': __website__,
                'generated_at': datetime.now().isoformat()
            },
            'report': self.report,
            'issues': self.issues,
            'warnings': self.warnings
        }
        
        with open(output_path, 'w') as f:
            json.dump(export_data, f, indent=2, default=str)
        
        if self.verbose:
            print(f"Report exported to: {output_path}")


def analyze_dataset(csv_path: str) -> Dict:
    """
    Quick function to analyze a CSV dataset.
    
    Args:
        csv_path: Path to CSV file
        
    Returns:
        Quality report dictionary
    """
    df = pd.read_csv(csv_path, comment='#')
    analyzer = DataQualityAnalyzer()
    return analyzer.analyze(df)


if __name__ == "__main__":
    # Demo
    try:
        report = analyze_dataset('../data/csv/train.csv')
    except FileNotFoundError:
        print("Dataset not found. Please ensure train.csv exists in ../data/csv/")

539 lines•20 KB
python
šŸš€ Support RSK World

Subscribe to our YouTube channel for latest tutorials & updates!



Click subscribe & support our work ā¤ļø

About RSK World

Founded by Molla Samser, with Designer & Tester Rima Khatun, RSK World is your one-stop destination for free programming resources, source code, and development tools.

Founder: Molla Samser
Designer & Tester: Rima Khatun

Development

  • Game Development
  • Web Development
  • Mobile Development
  • AI Development
  • Development Tools

Legal

  • Terms & Conditions
  • Privacy Policy
  • Disclaimer

Contact Info

Nutanhat, Mongolkote
Purba Burdwan, West Bengal
India, 713147

+91 93305 39277

hello@rskworld.in
support@rskworld.in

© 2026 RSK World. All rights reserved.

Content used for educational purposes only. View Disclaimer