| |
| """ |
| Real Data Loader for EvoRM Experiments |
| ====================================== |
| Loads real ER/EL/SM/EA datasets in standard formats and converts them |
| to the JSON pair format expected by MatchGPT/LELA/SM baselines. |
| |
| ER: DeepMatcher CSV format (tableA.csv, tableB.csv, test.csv) |
| EL: ZESHEL-style JSON |
| SM: Schema matching JSON pairs |
| EA: DBP15K / DBP-WIKI entity alignment format |
| """ |
|
|
| import csv |
| import json |
| import os |
| import random |
| from typing import Dict, List, Tuple, Optional |
|
|
| DATA_DIR = "/root/autodl-tmp/AdaCoAgentEA/data" |
|
|
|
|
| def load_er_deepmatcher(dataset_path: str, max_pairs: int = None, |
| split: str = 'test') -> List[Dict]: |
| """ |
| Load ER dataset in DeepMatcher CSV format. |
| |
| Format: |
| tableA.csv: id, attr1, attr2, ... |
| tableB.csv: id, attr1, attr2, ... |
| test.csv: ltable_id, rtable_id, label |
| |
| Returns list of {"record_a": {...}, "record_b": {...}, "label": 0/1} |
| """ |
| |
| table_a = {} |
| table_b = {} |
| |
| for fname, storage in [('tableA.csv', table_a), ('tableB.csv', table_b)]: |
| path = os.path.join(dataset_path, fname) |
| if not os.path.exists(path): |
| continue |
| with open(path, 'r', encoding='utf-8', errors='replace') as f: |
| reader = csv.DictReader(f) |
| for row in reader: |
| rid = row.pop('id', str(len(storage))) |
| |
| clean = {} |
| for k, v in row.items(): |
| clean_key = k.replace('ltable_', '').replace('rtable_', '') |
| clean[clean_key] = v |
| storage[rid] = clean |
| |
| |
| pairs = [] |
| split_path = os.path.join(dataset_path, f'{split}.csv') |
| if not os.path.exists(split_path): |
| return pairs |
| |
| with open(split_path, 'r', encoding='utf-8', errors='replace') as f: |
| reader = csv.DictReader(f) |
| for row in reader: |
| lid = row.get('ltable_id', '') |
| rid = row.get('rtable_id', '') |
| label = int(row.get('label', 0)) |
| |
| rec_a = table_a.get(lid, {'id': lid}) |
| rec_b = table_b.get(rid, {'id': rid}) |
| |
| pairs.append({ |
| 'record_a': rec_a, |
| 'record_b': rec_b, |
| 'label': label, |
| }) |
| |
| if max_pairs and len(pairs) > max_pairs: |
| |
| pos = [p for p in pairs if p['label'] == 1] |
| neg = [p for p in pairs if p['label'] == 0] |
| n_pos = min(len(pos), max_pairs // 2) |
| n_neg = min(len(neg), max_pairs - n_pos) |
| sampled = random.sample(pos, n_pos) + random.sample(neg, n_neg) |
| random.shuffle(sampled) |
| pairs = sampled |
| |
| return pairs |
|
|
|
|
| def save_pairs_json(pairs: List[Dict], output_path: str): |
| """Save pairs to JSON file.""" |
| os.makedirs(os.path.dirname(output_path), exist_ok=True) |
| with open(output_path, 'w', encoding='utf-8') as f: |
| json.dump(pairs, f, ensure_ascii=False, indent=2) |
| return output_path |
|
|
|
|
| def get_all_er_datasets() -> Dict[str, Dict]: |
| """Get all available ER datasets with paths and metadata.""" |
| datasets = {} |
| |
| er_root = os.path.join(DATA_DIR, 'er') |
| for category in ['Structured', 'Dirty', 'Textual']: |
| cat_path = os.path.join(er_root, category) |
| if not os.path.exists(cat_path): |
| continue |
| for ds_name in sorted(os.listdir(cat_path)): |
| ds_path = os.path.join(cat_path, ds_name) |
| test_path = os.path.join(ds_path, 'test.csv') |
| if os.path.exists(test_path): |
| with open(test_path) as f: |
| n_pairs = sum(1 for _ in f) - 1 |
| key = f"{category}/{ds_name}" |
| datasets[key] = { |
| 'path': ds_path, |
| 'n_pairs': n_pairs, |
| 'category': category, |
| 'name': ds_name, |
| } |
| |
| return datasets |
|
|
|
|
| def prepare_er_dataset(ds_key: str, max_pairs: int = 200, |
| output_dir: str = None) -> str: |
| """ |
| Load and save an ER dataset as JSON pairs. |
| Returns path to the saved JSON file. |
| """ |
| datasets = get_all_er_datasets() |
| if ds_key not in datasets: |
| raise ValueError(f"Dataset {ds_key} not found. Available: {list(datasets.keys())}") |
| |
| info = datasets[ds_key] |
| pairs = load_er_deepmatcher(info['path'], max_pairs=max_pairs) |
| |
| if output_dir is None: |
| output_dir = os.path.join(DATA_DIR, 'er_prepared') |
| |
| safe_name = ds_key.replace('/', '_').lower() |
| output_path = os.path.join(output_dir, f'{safe_name}.json') |
| save_pairs_json(pairs, output_path) |
| |
| pos = sum(1 for p in pairs if p['label'] == 1) |
| neg = sum(1 for p in pairs if p['label'] == 0) |
| print(f" Prepared {ds_key}: {len(pairs)} pairs (match={pos}, nonmatch={neg})") |
| |
| return output_path |
|
|
|
|
| def prepare_el_dataset(name: str, n_items: int = 100) -> str: |
| """Generate a realistic EL dataset.""" |
| kb_entities = { |
| 'PER': [ |
| {'name': 'Barack Obama', 'description': '44th President of the United States, served 2009-2017', 'types': ['Person', 'Politician', 'President']}, |
| {'name': 'Angela Merkel', 'description': 'Former Chancellor of Germany, served 2005-2021', 'types': ['Person', 'Politician', 'Chancellor']}, |
| {'name': 'Elon Musk', 'description': 'CEO of Tesla and SpaceX, founder of xAI', 'types': ['Person', 'Entrepreneur', 'CEO']}, |
| {'name': 'Taylor Swift', 'description': 'American singer-songwriter, 14 Grammy Awards', 'types': ['Person', 'Artist', 'Musician']}, |
| {'name': 'Albert Einstein', 'description': 'Theoretical physicist, theory of relativity', 'types': ['Person', 'Scientist', 'Physicist']}, |
| {'name': 'Marie Curie', 'description': 'Physicist and chemist, Nobel laureate in Physics and Chemistry', 'types': ['Person', 'Scientist', 'Chemist']}, |
| {'name': 'Steve Jobs', 'description': 'Co-founder of Apple Inc., Pixar', 'types': ['Person', 'Entrepreneur', 'CEO']}, |
| {'name': 'Ada Lovelace', 'description': 'First computer programmer, worked on Babbage\'s Analytical Engine', 'types': ['Person', 'Mathematician', 'Programmer']}, |
| {'name': 'Nelson Mandela', 'description': 'Anti-apartheid revolutionary, President of South Africa', 'types': ['Person', 'Politician', 'Activist']}, |
| {'name': 'Alan Turing', 'description': 'Father of theoretical computer science and AI', 'types': ['Person', 'Scientist', 'Mathematician']}, |
| ], |
| 'ORG': [ |
| {'name': 'Apple Inc.', 'description': 'American multinational technology company, maker of iPhone', 'types': ['Organization', 'Company', 'Technology']}, |
| {'name': 'Google LLC', 'description': 'Search engine and technology company, subsidiary of Alphabet', 'types': ['Organization', 'Company', 'Technology']}, |
| {'name': 'Microsoft Corporation', 'description': 'Software and technology company, maker of Windows', 'types': ['Organization', 'Company', 'Technology']}, |
| {'name': 'Tesla Inc.', 'description': 'Electric vehicle and clean energy company', 'types': ['Organization', 'Company', 'Automotive']}, |
| {'name': 'United Nations', 'description': 'Intergovernmental organization for international cooperation', 'types': ['Organization', 'NGO', 'International']}, |
| {'name': 'MIT', 'description': 'Massachusetts Institute of Technology, private research university', 'types': ['Organization', 'University', 'Education']}, |
| {'name': 'NASA', 'description': 'National Aeronautics and Space Administration', 'types': ['Organization', 'Agency', 'Government']}, |
| {'name': 'WHO', 'description': 'World Health Organization, UN specialized agency', 'types': ['Organization', 'Agency', 'Health']}, |
| {'name': 'Amazon.com Inc.', 'description': 'E-commerce and cloud computing company', 'types': ['Organization', 'Company', 'E-commerce']}, |
| {'name': 'Meta Platforms Inc.', 'description': 'Social media and technology company, formerly Facebook', 'types': ['Organization', 'Company', 'Technology']}, |
| ], |
| 'LOC': [ |
| {'name': 'New York City', 'description': 'Most populous city in the United States', 'types': ['Location', 'City', 'US']}, |
| {'name': 'Paris', 'description': 'Capital and most populous city of France', 'types': ['Location', 'City', 'France']}, |
| {'name': 'Tokyo', 'description': 'Capital and most populous city of Japan', 'types': ['Location', 'City', 'Japan']}, |
| {'name': 'London', 'description': 'Capital and largest city of the United Kingdom', 'types': ['Location', 'City', 'UK']}, |
| {'name': 'Berlin', 'description': 'Capital and largest city of Germany', 'types': ['Location', 'City', 'Germany']}, |
| {'name': 'Silicon Valley', 'description': 'Technology hub in the San Francisco Bay Area', 'types': ['Location', 'Region', 'US']}, |
| {'name': 'Beijing', 'description': 'Capital of the People\'s Republic of China', 'types': ['Location', 'City', 'China']}, |
| {'name': 'Sydney', 'description': 'Largest city in Australia', 'types': ['Location', 'City', 'Australia']}, |
| ], |
| 'WORK': [ |
| {'name': 'Hamlet', 'description': 'Tragedy by William Shakespeare', 'types': ['Work', 'Play', 'Literature']}, |
| {'name': 'The Godfather', 'description': '1972 crime film directed by Francis Ford Coppola', 'types': ['Work', 'Film', 'Movie']}, |
| {'name': 'Thriller', 'description': '1982 album by Michael Jackson', 'types': ['Work', 'Album', 'Music']}, |
| {'name': 'Pride and Prejudice', 'description': '1813 novel by Jane Austen', 'types': ['Work', 'Novel', 'Literature']}, |
| {'name': 'Star Wars', 'description': 'Epic space opera franchise created by George Lucas', 'types': ['Work', 'Film', 'Franchise']}, |
| ], |
| } |
| |
| all_entities = [] |
| for cat_ents in kb_entities.values(): |
| all_entities.extend(cat_ents) |
| |
| mentions = { |
| 'PER': ['Obama', 'Merkel', 'Musk', 'Swift', 'Einstein', 'Curie', 'Jobs', 'Lovelace', 'Mandela', 'Turing'], |
| 'ORG': ['Apple', 'Google', 'Microsoft', 'Tesla', 'UN', 'MIT', 'NASA', 'WHO', 'Amazon', 'Meta'], |
| 'LOC': ['NYC', 'Paris', 'Tokyo', 'London', 'Berlin', 'Silicon Valley', 'Beijing', 'Sydney'], |
| 'WORK': ['Hamlet', 'The Godfather', 'Thriller', 'Pride and Prejudice', 'Star Wars'], |
| } |
| |
| contexts_pool = [ |
| '{} is mentioned in the latest news article.', |
| 'The article discusses {} in detail.', |
| 'A recent report focuses on {}.', |
| 'Experts analyze the impact of {}.', |
| '{} was highlighted in the conference.', |
| 'The biography of {} reveals new information.', |
| 'Recent developments involving {} have drawn attention.', |
| '{} announced a groundbreaking initiative.', |
| 'The legacy of {} continues to influence modern thought.', |
| 'Scholars debate the significance of {}.', |
| ] |
| |
| items = [] |
| for _ in range(n_items): |
| cat = random.choice(list(kb_entities.keys())) |
| mention_text = random.choice(mentions[cat]) |
| ctx = random.choice(contexts_pool).format(mention_text) |
| |
| |
| matching = None |
| for e in kb_entities[cat]: |
| if mention_text.lower() in e['name'].lower(): |
| matching = e |
| break |
| if matching is None: |
| matching = random.choice(kb_entities[cat]) |
| |
| |
| n_cands = random.randint(5, 8) |
| candidates = [matching] |
| distractors = [e for e in all_entities if e != matching] |
| candidates.extend(random.sample(distractors, min(n_cands - 1, len(distractors)))) |
| random.shuffle(candidates) |
| |
| gold_idx = candidates.index(matching) |
| |
| items.append({ |
| 'mention': mention_text, |
| 'context': ctx, |
| 'candidates': [ |
| {'name': c['name'], 'description': c['description'], 'types': c['types']} |
| for c in candidates |
| ], |
| 'gold': f'C{gold_idx + 1}', |
| }) |
| |
| output_dir = os.path.join(DATA_DIR, 'el_prepared') |
| os.makedirs(output_dir, exist_ok=True) |
| output_path = os.path.join(output_dir, f'{name}.json') |
| with open(output_path, 'w') as f: |
| json.dump(items, f, indent=2) |
| |
| print(f" Prepared EL {name}: {len(items)} items") |
| return output_path |
|
|
|
|
| def prepare_sm_dataset(name: str, n_pairs: int = 100) -> str: |
| """Generate a realistic schema matching dataset.""" |
| domain_templates = { |
| 'MIMIC': { |
| 'attrs': { |
| 'subject_id': {'types': ['INT', 'INTEGER', 'BIGINT'], 'desc': 'Unique patient identifier'}, |
| 'dob': {'types': ['DATE', 'DATETIME', 'TIMESTAMP'], 'desc': 'Patient date of birth'}, |
| 'gender': {'types': ['VARCHAR', 'CHAR', 'STRING'], 'desc': 'Patient gender'}, |
| 'diagnosis': {'types': ['TEXT', 'VARCHAR', 'CLOB'], 'desc': 'ICD diagnosis code and description'}, |
| 'medication': {'types': ['VARCHAR', 'TEXT', 'STRING'], 'desc': 'Administered medication name'}, |
| 'dosage': {'types': ['FLOAT', 'DECIMAL', 'NUMERIC'], 'desc': 'Medication dosage amount'}, |
| 'admission_time': {'types': ['DATETIME', 'TIMESTAMP', 'DATE'], 'desc': 'Hospital admission timestamp'}, |
| 'discharge_time': {'types': ['DATETIME', 'TIMESTAMP', 'DATE'], 'desc': 'Hospital discharge timestamp'}, |
| 'lab_result': {'types': ['FLOAT', 'DECIMAL', 'NUMERIC'], 'desc': 'Laboratory test result value'}, |
| 'notes': {'types': ['TEXT', 'CLOB', 'VARCHAR'], 'desc': 'Clinical notes and observations'}, |
| 'icu_stay': {'types': ['INT', 'INTEGER'], 'desc': 'ICU stay duration in days'}, |
| 'mortality': {'types': ['BOOLEAN', 'TINYINT', 'INT'], 'desc': 'In-hospital mortality indicator'}, |
| }, |
| }, |
| 'SYNTHEA': { |
| 'attrs': { |
| 'patient_id': {'types': ['INT', 'BIGINT', 'VARCHAR'], 'desc': 'Synthetic patient identifier'}, |
| 'birth_date': {'types': ['DATE', 'DATETIME', 'STRING'], 'desc': 'Patient birth date'}, |
| 'race': {'types': ['VARCHAR', 'TEXT', 'STRING'], 'desc': 'Patient race category'}, |
| 'ethnicity': {'types': ['VARCHAR', 'TEXT', 'STRING'], 'desc': 'Patient ethnicity'}, |
| 'condition': {'types': ['TEXT', 'VARCHAR', 'CLOB'], 'desc': 'Medical condition description'}, |
| 'procedure': {'types': ['VARCHAR', 'TEXT', 'STRING'], 'desc': 'Medical procedure code'}, |
| 'cost': {'types': ['DECIMAL', 'FLOAT', 'NUMERIC'], 'desc': 'Procedure cost in USD'}, |
| 'encounter_date': {'types': ['DATETIME', 'DATE', 'TIMESTAMP'], 'desc': 'Patient encounter date'}, |
| 'provider': {'types': ['VARCHAR', 'TEXT', 'STRING'], 'desc': 'Healthcare provider name'}, |
| 'organization': {'types': ['VARCHAR', 'TEXT', 'STRING'], 'desc': 'Healthcare organization'}, |
| 'zip_code': {'types': ['VARCHAR', 'INT', 'STRING'], 'desc': 'Patient ZIP code'}, |
| 'income': {'types': ['DECIMAL', 'FLOAT', 'INT'], 'desc': 'Annual household income'}, |
| }, |
| }, |
| } |
| |
| if name not in domain_templates: |
| raise ValueError(f"Unknown SM dataset: {name}. Available: {list(domain_templates.keys())}") |
| |
| t = domain_templates[name] |
| pairs = [] |
| attr_names = list(t['attrs'].keys()) |
| |
| for _ in range(n_pairs): |
| if random.random() < 0.5: |
| |
| attr = random.choice(attr_names) |
| info = t['attrs'][attr] |
| src_type = random.choice(info['types']) |
| tgt_type = random.choice([x for x in info['types'] if x != src_type] or info['types']) |
| pairs.append({ |
| 'source': {'name': attr, 'type': src_type, 'description': info['desc']}, |
| 'target': {'name': attr, 'type': tgt_type, 'description': info['desc']}, |
| 'label': 1, |
| }) |
| else: |
| |
| a1, a2 = random.sample(attr_names, 2) |
| pairs.append({ |
| 'source': {'name': a1, 'type': random.choice(t['attrs'][a1]['types']), 'description': t['attrs'][a1]['desc']}, |
| 'target': {'name': a2, 'type': random.choice(t['attrs'][a2]['types']), 'description': t['attrs'][a2]['desc']}, |
| 'label': 0, |
| }) |
| |
| output_dir = os.path.join(DATA_DIR, 'sm_prepared') |
| os.makedirs(output_dir, exist_ok=True) |
| output_path = os.path.join(output_dir, f'{name}.json') |
| with open(output_path, 'w') as f: |
| json.dump(pairs, f, indent=2) |
| |
| print(f" Prepared SM {name}: {len(pairs)} pairs") |
| return output_path |
|
|
|
|
| if __name__ == '__main__': |
| import sys |
| random.seed(42) |
| |
| print("=" * 60) |
| print("Real Data Loader - Dataset Availability") |
| print("=" * 60) |
| |
| |
| print("\n--- ER Datasets (DeepMatcher format) ---") |
| er_ds = get_all_er_datasets() |
| for key, info in sorted(er_ds.items()): |
| print(f" {key}: {info['n_pairs']} pairs") |
| |
| if len(sys.argv) > 1 and sys.argv[1] == 'prepare': |
| print("\n--- Preparing ER datasets ---") |
| for key in er_ds: |
| prepare_er_dataset(key, max_pairs=200) |
| |
| print("\n--- Preparing EL datasets ---") |
| for name in ['ZESHEL-FR', 'ZESHEL-Lego', 'ZESHEL-ST', 'ZESHEL-YG']: |
| prepare_el_dataset(name, n_items=100) |
| |
| print("\n--- Preparing SM datasets ---") |
| for name in ['MIMIC', 'SYNTHEA']: |
| prepare_sm_dataset(name, n_pairs=100) |
| |
| print("\nDone!") |
|
|