stanceeval2026 / code /src /data.py
zaher-m's picture
Add files using upload-large-folder tool
7e9cfd1 verified
Raw
History Blame Contribute Delete
4.67 kB
"""Load the CSVs, clean up the text, and pair each tweet with its target."""
import re
import pandas as pd
import torch
from torch.utils.data import Dataset
LABEL2ID = {"Against": 0, "Favor": 1, "None": 2}
ID2LABEL = {v: k for k, v in LABEL2ID.items()}
_DIACRITICS = re.compile(r"[ؐ-ًؚ-ٰٟـ]")
_NON_ARABIC = re.compile(r"[^؀-ۿ0-9\s]+")
_URL = re.compile(r"https?://\S+|www\.\S+")
_MENTION = re.compile(r"@\w+")
_MULTI_SPACE = re.compile(r"\s+")
_REPEAT = re.compile(r"(.)\1{2,}")
# Short target descriptions used when the target side is expanded.
TARGET_DESCRIPTIONS = {
"Covid Vaccine": "لقاح فيروس كورونا كوفيد 19",
"Digital Transformation": "التحول الرقمي في الخدمات والمجتمع",
"Women empowerment": "تمكين المرأة وحقوقها في المجتمع",
"Women Driving": "قيادة المرأة للسيارة",
"Ecars": "السيارات الكهربائية",
"Trimester": "نظام الفصول الدراسية الثلاثة في العام الدراسي",
}
# More informative, neutral glosses that spell out what the target is and
# the axis of debate around it (used with --rich_desc).
TARGET_DESCRIPTIONS_RICH = {
"Covid Vaccine": "لقاح فيروس كورونا كوفيد 19 وأخذه للوقاية من المرض",
"Digital Transformation": (
"التحول الرقمي: رقمنة الخدمات والمعاملات الحكومية والمجتمعية"
),
"Women empowerment": "تمكين المرأة وتوسيع دورها وحقوقها في المجتمع",
"Women Driving": "قيادة المرأة للسيارة",
"Ecars": "السيارات الكهربائية التي تعمل بالبطارية بدلاً من الوقود",
"Trimester": (
"نظام الترمات: تقسيم العام الدراسي إلى ثلاثة فصول بدلاً من فصلين"
),
}
def preprocess(text, mode="preserve"):
"""Normalize tweet text.
mode='strip' removes non-Arabic characters (emoji, latin).
mode='preserve' keeps emoji/latin, drops only urls, mentions,
diacritics, repeated characters, and hashtag marks.
"""
text = str(text)
text = _URL.sub(" ", text)
text = _MENTION.sub(" ", text)
text = _DIACRITICS.sub("", text)
text = _REPEAT.sub(r"\1\1", text)
if mode == "strip":
text = _NON_ARABIC.sub(" ", text)
else:
text = text.replace("#", " ").replace("_", " ")
return _MULTI_SPACE.sub(" ", text).strip()
def target_side(target, use_description=False):
target = target.strip()
if use_description:
return TARGET_DESCRIPTIONS.get(target, target)
return target
def load_split(csv_path, prep_mode="preserve", has_labels=True):
"""Load a CSV into a dataframe with text, target and optional stance."""
df = pd.read_csv(csv_path, keep_default_na=False, encoding="utf-8-sig")
df.columns = df.columns.astype(str).str.strip()
if "text" not in df.columns and "tweet_text" in df.columns:
df = df.rename(columns={"tweet_text": "text"})
df["text"] = df["text"].astype(str).str.strip()
df["target"] = df["target"].astype(str).str.strip()
if has_labels:
df["stance"] = df["stance"].astype(str).str.strip()
df = df[
(df["text"] != "")
& (df["target"] != "")
& (df["stance"] != "")
].copy()
bad = sorted(set(df["stance"]) - set(LABEL2ID))
if bad:
raise ValueError(f"Unknown stance labels in {csv_path}: {bad}")
df["label"] = df["stance"].map(LABEL2ID).astype(int)
df["text_clean"] = df["text"].apply(lambda t: preprocess(t, prep_mode))
return df.reset_index(drop=True)
class StanceDataset(Dataset):
def __init__(self, df, tokenizer, max_len=128, use_description=False,
has_labels=True):
self.df = df.reset_index(drop=True)
self.tok = tokenizer
self.max_len = max_len
self.use_description = use_description
self.has_labels = has_labels
def __len__(self):
return len(self.df)
def __getitem__(self, idx):
row = self.df.iloc[idx]
enc = self.tok(
target_side(row["target"], self.use_description),
row["text_clean"],
truncation=True,
padding="max_length",
max_length=self.max_len,
return_tensors="pt",
)
item = {k: v.squeeze(0) for k, v in enc.items()}
if self.has_labels:
item["labels"] = torch.tensor(int(row["label"]), dtype=torch.long)
return item