truthlens-ml-api / src /liar_loader.py
Param0304's picture
Deploy TruthLens ML API to Hugging Face Space
3abf967
Raw History Blame Contribute Delete
5.14 kB
"""
liar_loader.py - LIAR Dataset Loader and Parser for TruthLens AI.
This module handles loading, parsing, and analyzing the LIAR benchmark dataset
(Wang, ACL 2017) across train, valid, and test splits. It robustly parses the
14 tab-delimited columns and computes statistics on statements, speakers,
metadata, and 6-class truth labels.
"""
import csv
import os
from typing import Dict, List, Any, Optional, Tuple
import pandas as pd
import numpy as np
LIAR_COLUMNS = [
"id",
"label",
"statement",
"subject",
"speaker",
"speaker_job_title",
"state_info",
"party_affiliation",
"barely_true_counts",
"false_counts",
"half_true_counts",
"mostly_true_counts",
"pants_on_fire_counts",
"context",
]
NUMERIC_COUNT_COLS = [
"barely_true_counts",
"false_counts",
"half_true_counts",
"mostly_true_counts",
"pants_on_fire_counts",
]
# Standard 6-point scale defined by PolitiFact and LIAR
LIAR_6_LABELS = [
"pants-fire",
"false",
"barely-true",
"half-true",
"mostly-true",
"true",
]
def load_liar_split(filepath: str) -> Tuple[pd.DataFrame, int]:
"""
Loads a single LIAR TSV split file.
Uses csv.QUOTE_NONE to ensure unescaped quotes inside speech statements
do not cause row misalignment or multi-line consumption.
Args:
filepath: Path to train.tsv, valid.tsv, or test.tsv.
Returns:
Tuple of (DataFrame of parsed records, count of malformed lines).
"""
if not os.path.exists(filepath):
raise FileNotFoundError(f"LIAR file not found: {filepath}")
malformed_count = 0
valid_rows = []
with open(filepath, "r", encoding="utf-8", errors="replace") as f:
reader = csv.reader(f, delimiter="\t", quoting=csv.QUOTE_NONE)
for line_idx, row in enumerate(reader, start=1):
if len(row) == 14:
valid_rows.append(row)
elif len(row) > 14:
# Tab within context or statement; merge trailing elements into context
merged_row = row[:13] + ["\t".join(row[13:])]
valid_rows.append(merged_row)
malformed_count += 1
else:
malformed_count += 1
df = pd.DataFrame(valid_rows, columns=LIAR_COLUMNS)
# Cast count columns to numeric
for col in NUMERIC_COUNT_COLS:
df[col] = pd.to_numeric(df[col], errors="coerce").fillna(0).astype(int)
return df, malformed_count
def load_all_liar_splits(data_dir: str) -> Dict[str, pd.DataFrame]:
"""
Loads all available LIAR splits from a directory (train, valid, test).
"""
splits = {}
for split_name, filename in [("train", "train.tsv"), ("valid", "valid.tsv"), ("test", "test.tsv")]:
full_path = os.path.join(data_dir, filename)
if os.path.exists(full_path):
df, _ = load_liar_split(full_path)
splits[split_name] = df
return splits
def get_liar_statistics(df: pd.DataFrame, split_name: str = "dataset") -> Dict[str, Any]:
"""
Calculates detailed statistics for a LIAR DataFrame.
"""
total_records = len(df)
if total_records == 0:
return {"total_records": 0}
statement_words = df["statement"].astype(str).apply(lambda s: len(s.split()))
statement_chars = df["statement"].astype(str).apply(len)
# Missing / empty string counts
missing_counts = {}
for col in df.columns:
null_cnt = int(df[col].isnull().sum())
empty_cnt = int((df[col].astype(str).str.strip() == "").sum())
missing_counts[col] = null_cnt + empty_cnt
# Duplicates
unique_stmts = df["statement"].nunique()
duplicate_stmts = total_records - unique_stmts
# Label counts
label_counts = df["label"].value_counts().to_dict()
# Speaker & party metrics
top_speakers = df["speaker"].value_counts().head(10).to_dict()
top_parties = df["party_affiliation"].value_counts().head(5).to_dict()
top_subjects = df["subject"].value_counts().head(10).to_dict()
return {
"split": split_name,
"total_records": total_records,
"columns": list(df.columns),
"label_counts": label_counts,
"missing_or_empty_values": missing_counts,
"unique_statements": unique_stmts,
"duplicate_statements": duplicate_stmts,
"statement_word_length": {
"min": int(statement_words.min()),
"max": int(statement_words.max()),
"mean": float(round(statement_words.mean(), 2)),
"median": float(round(statement_words.median(), 2)),
"std": float(round(statement_words.std(), 2)),
},
"statement_char_length": {
"min": int(statement_chars.min()),
"max": int(statement_chars.max()),
"mean": float(round(statement_chars.mean(), 2)),
"median": float(round(statement_chars.median(), 2)),
},
"top_speakers": top_speakers,
"top_parties": top_parties,
"top_subjects": top_subjects,
}