Spaces:
Running
Running
Download PMPF_duplicate_scanner.py from Naphula/model_tools: direct link, hf CLI and curl.
- Browser
- Download file 5.94 kB
-
https://huggingface.co/spaces/Naphula/model_tools/resolve/main/PMPF_duplicate_scanner.py
- Command line
-
hf download hf://spaces/Naphula/model_tools/PMPF_duplicate_scanner.py
-
curl -L -o PMPF_duplicate_scanner.py https://huggingface.co/spaces/Naphula/model_tools/resolve/main/PMPF_duplicate_scanner.py
5.94 kB
| import os | |
| import glob | |
| import json | |
| import pandas as pd | |
| import numpy as np | |
| # ================= CONFIGURATION ================= | |
| DATASET_FOLDER = os.path.join(os.getcwd(), "dataset") | |
| # ================================================= | |
| class Colors: | |
| HEADER = '\033[95m' | |
| OKGREEN = '\033[92m' | |
| WARNING = '\033[93m' | |
| FAIL = '\033[91m' | |
| ENDC = '\033[0m' | |
| BOLD = '\033[1m' | |
| # Windows CMD color support fix | |
| os.system('color') | |
| def make_hashable(val): | |
| """Converts dialogue structures or lists into a stable string for hashing.""" | |
| if val is None: | |
| return "" | |
| if isinstance(val, (list, np.ndarray, dict)): | |
| try: | |
| return json.dumps(val, sort_keys=True) | |
| except Exception: | |
| return str(val) | |
| return str(val).strip() | |
| def load_file_to_df(filepath): | |
| """Loads various file formats and returns a unified DataFrame with row tracking.""" | |
| ext = os.path.splitext(filepath)[1].lower() | |
| filename = os.path.basename(filepath) | |
| df = pd.DataFrame() | |
| try: | |
| if ext in ['.txt', '.md']: | |
| with open(filepath, 'r', encoding='utf-8', errors='ignore') as f: | |
| lines = f.read().split("\n\n") # Split by double newline as rough sample block | |
| df = pd.DataFrame({'text': lines}) | |
| df['row_index'] = df.index + 1 | |
| elif ext == '.parquet': | |
| df = pd.read_parquet(filepath) | |
| df['row_index'] = df.index | |
| elif ext == '.csv': | |
| df = pd.read_csv(filepath) | |
| df['row_index'] = df.index + 2 # +2 accounts for header offset in CSV viewers | |
| elif ext in ['.json', '.jsonl']: | |
| try: | |
| df = pd.read_json(filepath, lines=True) | |
| except ValueError: | |
| df = pd.read_json(filepath) | |
| df['row_index'] = df.index | |
| if not df.empty: | |
| df['source_file'] = filename | |
| except Exception as e: | |
| print(f"{Colors.FAIL}Error reading {filename}: {e}{Colors.ENDC}") | |
| return df | |
| def main(): | |
| print(f"{Colors.HEADER}{'='*80}") | |
| print(" CROSS-FILE DATASET DUPLICATE TRACE SCANNER") | |
| print(f"{'='*80}{Colors.ENDC}") | |
| if not os.path.exists(DATASET_FOLDER): | |
| print(f"{Colors.FAIL}Error: Dataset folder '{DATASET_FOLDER}' not found.{Colors.ENDC}") | |
| return | |
| files = glob.glob(os.path.join(DATASET_FOLDER, "*.*")) | |
| if not files: | |
| print(f"{Colors.WARNING}No files found in '{DATASET_FOLDER}'.{Colors.ENDC}") | |
| return | |
| # 1. Load and aggregate all files | |
| all_data = [] | |
| print("Loading and indexing raw dataset files...") | |
| for f in files: | |
| df = load_file_to_df(f) | |
| if not df.empty: | |
| print(f" -> Indexed: {os.path.basename(f)} ({len(df)} rows)") | |
| all_data.append(df) | |
| if not all_data: | |
| print(f"{Colors.FAIL}No valid data could be parsed.{Colors.ENDC}") | |
| return | |
| # Combine everything into a global tracking DataFrame | |
| master_df = pd.concat(all_data, ignore_index=True) | |
| # Determine which column to audit | |
| target_column = None | |
| for col in ['conversations', 'messages', 'text']: | |
| if col in master_df.columns: | |
| target_column = col | |
| break | |
| if not target_column: | |
| # Fallback to the first non-meta column | |
| meta_cols = {'source_file', 'row_index'} | |
| valid_cols = [c for c in master_df.columns if c not in meta_cols] | |
| target_column = valid_cols[0] if valid_cols else master_df.columns[0] | |
| print(f"\nAuditing duplicates using key column: '{Colors.BOLD}{target_column}{Colors.ENDC}'") | |
| # Hash values for exact matching | |
| master_df['_hash'] = master_df[target_column].apply(make_hashable) | |
| # 2. Find duplicates (excluding empty rows) | |
| master_df = master_df[master_df['_hash'] != ""] | |
| # Group by the hash and find groups with size > 1 | |
| dup_groups = master_df.groupby('_hash').filter(lambda x: len(x) > 1) | |
| if dup_groups.empty: | |
| print(f"\n{Colors.OKGREEN}🎉 Scan complete! No duplicate records found across any of the files.{Colors.ENDC}") | |
| return | |
| # 3. Report detailed trace | |
| unique_dupe_hashes = dup_groups['_hash'].unique() | |
| total_dupes_count = len(dup_groups) - len(unique_dupe_hashes) | |
| print(f"\n{Colors.WARNING}⚠️ Found {len(unique_dupe_hashes)} unique texts that have duplicated copies ({total_dupes_count} redundant entries total).{Colors.ENDC}") | |
| print("="*80) | |
| print("DETAILED DUPLICATE TRACE REPORT:") | |
| print("="*80) | |
| # Show up to 15 duplicate groups to keep console readable | |
| max_groups_to_show = 15 | |
| for idx, h in enumerate(unique_dupe_hashes[:max_groups_to_show]): | |
| group = dup_groups[dup_groups['_hash'] == h] | |
| # Extract a clean text preview | |
| raw_val = group.iloc[0][target_column] | |
| if isinstance(raw_val, list): # ShareGPT format preview | |
| preview = f"[ShareGPT Conversation with {len(raw_val)} turns]" | |
| else: | |
| preview = str(raw_val).replace('\n', ' ')[:80] + "..." | |
| print(f"\n{Colors.BOLD}Group #{idx+1} | Duplicate Content Preview:{Colors.ENDC}") | |
| print(f" \"{Colors.OKGREEN}{preview}{Colors.ENDC}\"") | |
| print(f" {Colors.BOLD}Found in following locations:{Colors.ENDC}") | |
| for _, row in group.iterrows(): | |
| print(f" - File: {Colors.WARNING}{row['source_file']}{Colors.ENDC} | Row/Index: {row['row_index']}") | |
| if len(unique_dupe_hashes) > max_groups_to_show: | |
| print(f"\n... and {len(unique_dupe_hashes) - max_groups_to_show} more duplicate groups.") | |
| print(f"\n{Colors.HEADER}{'='*80}{Colors.ENDC}") | |
| if __name__ == "__main__": | |
| main() |