File size: 12,400 Bytes
3abf967
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
"""
inspect_data.py - Comprehensive Data Inspection Script for TruthLens AI.

This script inspects FEVER, LIAR, and SciFact datasets locally:
- Validates file integrity and parses formats
- Calculates dataset sizes, columns, and data types
- Computes label distributions and percentages
- Computes claim length statistics
- Analyzes evidence availability and representation
- Detects missing/empty values and malformed rows
- Detects duplicates and cross-split leakage
- Prints sample records from each dataset
"""

import os
import sys
import json
import pandas as pd
import numpy as np

# Add src directory to path if running directly
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))

from fever_loader import load_fever_raw, get_fever_statistics
from liar_loader import load_all_liar_splits, get_liar_statistics, LIAR_COLUMNS
from scifact_loader import (
    load_scifact_corpus,
    load_scifact_claims,
    reconstruct_scifact_triples,
    get_scifact_statistics,
)


def print_header(title: str):
    print("\n" + "=" * 80)
    print(f" {title.upper()}")
    print("=" * 80)


def print_sub_header(title: str):
    print("\n" + "-" * 60)
    print(f" {title}")
    print("-" * 60)


def inspect_fever(raw_dir: str):
    print_header("1. FEVER DATASET INSPECTION")
    fever_path = os.path.join(raw_dir, "fever", "train.jsonl")
    
    if not os.path.exists(fever_path):
        print(f"File not found: {fever_path}")
        return
        
    print(f"File path: {fever_path}")
    print(f"File size: {os.path.getsize(fever_path):,} bytes ({os.path.getsize(fever_path) / (1024*1024):.2f} MB)")
    print(f"Format: JSON Lines (.jsonl)")
    
    df_fever, malformed = load_fever_raw(fever_path)
    stats = get_fever_statistics(df_fever)
    
    print(f"Total rows parsed: {stats['total_examples']:,}")
    print(f"Malformed rows: {malformed}")
    print(f"Columns: {stats['columns']}")
    print(f"Data types:\n{df_fever.dtypes.to_dict()}")
    
    print_sub_header("FEVER Label Distribution")
    for label, count in stats["label_counts"].items():
        pct = (count / stats["total_examples"]) * 100
        print(f"  {label:<18}: {count:>7,} ({pct:>5.2f}%)")
        
    print_sub_header("FEVER Verifiable Flag Distribution")
    for ver, count in stats["verifiable_counts"].items():
        pct = (count / stats["total_examples"]) * 100
        print(f"  {ver:<18}: {count:>7,} ({pct:>5.2f}%)")
        
    print_sub_header("FEVER Missing Values")
    for col, nulls in stats["missing_values"].items():
        print(f"  {col:<15}: {nulls} missing")
        
    print_sub_header("FEVER Duplicates & Annotation Consistency")
    print(f"  Unique claims: {stats['unique_claims']:,}")
    print(f"  Duplicate claim rows: {stats['duplicate_claim_rows']:,}")
    print(f"  Claims with conflicting labels across annotations: {stats['conflicting_claim_annotations']:,}")
    
    print_sub_header("FEVER Claim Length Statistics")
    w = stats["claim_word_length"]
    print(f"  Word count  - Min: {w['min']}, Max: {w['max']}, Mean: {w['mean']}, Median: {w['median']}, Std: {w['std']}")
    c = stats["claim_char_length"]
    print(f"  Char count  - Min: {c['min']}, Max: {c['max']}, Mean: {c['mean']}, Median: {c['median']}")
    
    print_sub_header("FEVER Evidence Availability & Structure")
    ev = stats["evidence_stats"]
    print(f"  Claims with evidence references    : {ev['claims_with_evidence']:,} ({ev['claims_with_evidence']/stats['total_examples']*100:.1f}%)")
    print(f"  Claims without evidence references : {ev['claims_without_evidence']:,} ({ev['claims_without_evidence']/stats['total_examples']*100:.1f}%)")
    print(f"  Mean evidence sentences per claim  : {ev['mean_sentences_per_claim']}")
    print(f"  Max evidence sentences for a claim : {ev['max_sentences_per_claim']}")
    print(f"  Evidence representation format     : 3-level nested list [Sets -> Sentences -> [annot_id, ev_id, wiki_page, sent_id]]")
    print(f"  IMPORTANT NOTE: Wikipedia raw text is NOT included inside train.jsonl (only title + sentence index).")
    
    print_sub_header("FEVER Sample Records (one per label)")
    for target_label in ["SUPPORTS", "REFUTES", "NOT ENOUGH INFO"]:
        sample = df_fever[df_fever["label"] == target_label].iloc[0]
        print(f"\n  [Label: {target_label}]")
        print(f"    ID       : {sample['id']}")
        print(f"    Claim    : {sample['claim']}")
        print(f"    Evidence : {sample['evidence'][:2]}")


def inspect_liar(raw_dir: str):
    print_header("2. LIAR DATASET INSPECTION")
    liar_dir = os.path.join(raw_dir, "liar")
    splits = load_all_liar_splits(liar_dir)
    
    if not splits:
        print(f"No LIAR splits found in: {liar_dir}")
        return
        
    print(f"Directory: {liar_dir}")
    print(f"Format: Tab-Separated Values (TSV, headerless, 14 columns)")
    print(f"Splits found: {list(splits.keys())}")
    
    total_liar_rows = sum(len(df) for df in splits.values())
    print(f"Total LIAR examples across all splits: {total_liar_rows:,}")
    
    for split_name, df_split in splits.items():
        print_sub_header(f"LIAR Split: {split_name.upper()}")
        fpath = os.path.join(liar_dir, f"{split_name}.tsv")
        fsize = os.path.getsize(fpath) if os.path.exists(fpath) else 0
        stats = get_liar_statistics(df_split, split_name)
        
        print(f"  Rows: {len(df_split):,} | Size: {fsize:,} bytes | Columns: {len(df_split.columns)}")
        print(f"  Label distribution in {split_name}:")
        for label, count in stats["label_counts"].items():
            pct = (count / len(df_split)) * 100
            print(f"    {label:<15}: {count:>5} ({pct:>5.2f}%)")
            
        w = stats["statement_word_length"]
        print(f"  Statement words - Min: {w['min']}, Max: {w['max']}, Mean: {w['mean']}, Median: {w['median']}")
        print(f"  Duplicate statements in split: {stats['duplicate_statements']}")
        
        # Missing values
        empty_cols = {k: v for k, v in stats["missing_or_empty_values"].items() if v > 0}
        if empty_cols:
            print(f"  Empty/Missing fields: {empty_cols}")
            
    # Cross-split leakage in LIAR
    if "train" in splits and "valid" in splits and "test" in splits:
        print_sub_header("LIAR Cross-Split Statement Leakage")
        tr_stmts = set(splits["train"]["statement"].str.strip().str.lower())
        val_stmts = set(splits["valid"]["statement"].str.strip().str.lower())
        te_stmts = set(splits["test"]["statement"].str.strip().str.lower())
        
        tr_val_leak = tr_stmts.intersection(val_stmts)
        tr_te_leak = tr_stmts.intersection(te_stmts)
        val_te_leak = val_stmts.intersection(te_stmts)
        
        print(f"  Train & Valid overlapping statements: {len(tr_val_leak)}")
        print(f"  Train & Test overlapping statements : {len(tr_te_leak)}")
        print(f"  Valid & Test overlapping statements : {len(val_te_leak)}")
        if tr_val_leak:
            print(f"  Sample leakage (train/valid): {list(tr_val_leak)[:2]}")
            
    print_sub_header("LIAR Metadata & Evidence Availability")
    print(f"  Evidence availability : NO factual evidence / document text is provided in LIAR TSVs.")
    print(f"  Available metadata    : Speaker, party, job, state, context/venue, 5 credit-history counts.")
    print(f"  Suitability           : Useful for speaker credibility & statement classification, but")
    print(f"                          CANNOT directly train Claim+Evidence verification without external retrieval.")
    
    print_sub_header("LIAR Sample Records (one per label)")
    df_train = splits.get("train", pd.DataFrame())
    if not df_train.empty:
        for lbl in ["pants-fire", "false", "barely-true", "half-true", "mostly-true", "true"]:
            subset = df_train[df_train["label"] == lbl]
            if not subset.empty:
                s = subset.iloc[0]
                print(f"\n  [Label: {lbl}]")
                print(f"    Speaker   : {s['speaker']} ({s['party_affiliation']}, {s['state_info']})")
                print(f"    Context   : {s['context']}")
                print(f"    Statement : {s['statement']}")


def inspect_scifact(raw_dir: str):
    print_header("3. SCIFACT DATASET INSPECTION")
    scifact_dir = os.path.join(raw_dir, "scifact")
    
    corpus_path = os.path.join(scifact_dir, "corpus.jsonl")
    claims_train_p = os.path.join(scifact_dir, "claims_train.jsonl")
    claims_dev_p = os.path.join(scifact_dir, "claims_dev.jsonl")
    claims_test_p = os.path.join(scifact_dir, "claims_test.jsonl")
    
    corpus = load_scifact_corpus(corpus_path)
    claims_tr = load_scifact_claims(claims_train_p)
    claims_dv = load_scifact_claims(claims_dev_p)
    claims_te = load_scifact_claims(claims_test_p)
    
    print(f"Directory: {scifact_dir}")
    print(f"Corpus documents loaded: {len(corpus):,} research paper abstracts")
    
    # Corpus sentence analysis
    abstract_lens = [len(doc.get("abstract", [])) for doc in corpus.values()]
    total_sentences = sum(abstract_lens)
    print(f"Total corpus sentences: {total_sentences:,}")
    print(f"Sentences per abstract: Min={min(abstract_lens)}, Max={max(abstract_lens)}, Mean={np.mean(abstract_lens):.2f}")
    
    # Claims analysis
    for name, c_list in [("claims_train", claims_tr), ("claims_dev", claims_dv), ("claims_test", claims_te)]:
        print_sub_header(f"SciFact Split: {name.upper()}")
        stats = get_scifact_statistics(c_list, split_name=name)
        print(f"  Total claims: {stats['total_claims']}")
        print(f"  Claims with evidence: {stats['claims_with_evidence']} ({stats['claims_with_evidence']/stats['total_claims']*100:.1f}%)")
        print(f"  Claims without evidence (NEI): {stats['claims_without_evidence_nei']} ({stats['claims_without_evidence_nei']/stats['total_claims']*100:.1f}%)")
        if stats["evidence_label_counts"]:
            print(f"  Evidence rationale labels: {stats['evidence_label_counts']}")
        w = stats["claim_word_length"]
        print(f"  Claim words: Min={w['min']}, Max={w['max']}, Mean={w['mean']}, Median={w['median']}")
        
    # Reconstructed triples demonstration
    triples_train = reconstruct_scifact_triples(claims_tr, corpus, include_nei=True)
    print_sub_header("SciFact Reconstructed Triples (Train)")
    print(f"  Total reconstructed claim-evidence pairs: {len(triples_train):,}")
    print(f"  Label distribution:\n{triples_train['label'].value_counts().to_dict()}")
    
    # Cross-split claim & doc overlap
    print_sub_header("SciFact Cross-Split Overlap & Leakage")
    tr_claims = set(c["claim"].strip().lower() for c in claims_tr)
    dv_claims = set(c["claim"].strip().lower() for c in claims_dv)
    te_claims = set(c["claim"].strip().lower() for c in claims_te)
    
    print(f"  Train & Dev overlapping claims: {len(tr_claims.intersection(dv_claims))}")
    print(f"  Train & Test overlapping claims: {len(tr_claims.intersection(te_claims))}")
    
    tr_docs = set(d for c in claims_tr for d in c.get("cited_doc_ids", []))
    dv_docs = set(d for c in claims_dv for d in c.get("cited_doc_ids", []))
    print(f"  Train cited documents: {len(tr_docs):,}")
    print(f"  Dev cited documents: {len(dv_docs):,}")
    print(f"  Train & Dev cited doc overlap: {len(tr_docs.intersection(dv_docs)):,} documents (potential corpus leakage)")
    
    print_sub_header("SciFact Sample Reconstructed Grounded Triples")
    for lbl in ["SUPPORT", "CONTRADICT", "NOT_ENOUGH_INFO"]:
        sub = triples_train[triples_train["label"] == lbl]
        if not sub.empty:
            row = sub.iloc[0]
            print(f"\n  [Label: {lbl}]")
            print(f"    Claim ID : {row['claim_id']}")
            print(f"    Claim    : {row['claim']}")
            print(f"    Doc ID   : {row['doc_id']} | Title: {row['doc_title']}")
            print(f"    Evidence : {row['evidence_text'][:160]}...")


def main():
    raw_base_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "data", "raw")
    print("TruthLens AI - Full Dataset Inspection Starting...")
    print(f"Raw data base directory: {raw_base_dir}")
    
    inspect_fever(raw_base_dir)
    inspect_liar(raw_base_dir)
    inspect_scifact(raw_base_dir)
    
    print_header("INSPECTION SUMMARY COMPLETED")


if __name__ == "__main__":
    main()