HCAI-Lab/w2-consensus-deepdive-unlearning-artifacts / social-data-attribution-w2 /scripts /analysis /rq4_figures.py
| #!/usr/bin/env python3 | |
| from __future__ import annotations | |
| """RQ4 paper figures: lexical feature heatmaps and profile comparison. | |
| Generates publication-quality Plotly figures following the existing | |
| distribution_report style conventions. Outputs PNG, PDF, and HTML. | |
| Usage: | |
| python scripts/analysis/rq4_figures.py \ | |
| --features data/outputs/rq4_bin_features.csv \ | |
| --scores-dir artifacts/influence_bin_scores \ | |
| --output-dir artifacts/paper_figures | |
| """ | |
| import argparse | |
| import csv | |
| import sys | |
| from pathlib import Path | |
| import numpy as np | |
| import pandas as pd | |
| import plotly.graph_objects as go | |
| from plotly.subplots import make_subplots | |
| sys.path.insert(0, str(Path(__file__).resolve().parent.parent.parent / "src")) | |
| from dolma.constants import FORMATS, TOPICS | |
| from dolma.distribution_report.data_loader import short_label | |
| from dolma.distribution_report.style import ( | |
| COLOR_EMPTY_CELL, | |
| COLOR_PRIMARY, | |
| DIVERGING_COLORSCALE, | |
| DPI_SCALE, | |
| FIGURE_HEIGHT_HEATMAP_PX, | |
| FIGURE_HEIGHT_SIDE_BY_SIDE_PX, | |
| FIGURE_WIDTH_HEATMAP_PX, | |
| FIGURE_WIDTH_SIDE_BY_SIDE_PX, | |
| paper_layout, | |
| plotly_font, | |
| save_figure, | |
| ) | |
| FORMATS_ORDER = list(FORMATS) | |
| TOPICS_ORDER = list(TOPICS) | |
| # Local alias so existing call sites stay readable; delegates to the | |
| # canonical short_label registry for visual consistency with Fig 4 and | |
| # the rest of the distribution_report pipeline. | |
| def display_label(label: str) -> str: | |
| return short_label(label) | |
| def pivot_feature(df: pd.DataFrame, col: str) -> np.ndarray: | |
| matrix = df.pivot(index="bin_topic", columns="bin_format", values=col).fillna(0) | |
| matrix = matrix.reindex(index=TOPICS_ORDER, columns=FORMATS_ORDER, fill_value=0) | |
| return matrix.values.astype(float) | |
| def zscore_influence(df: pd.DataFrame) -> pd.DataFrame: | |
| """Z-score standardize mean_score within a single benchmark (over 576 bins).""" | |
| out = df.copy() | |
| mu = out["mean_score"].mean() | |
| sigma = out["mean_score"].std() | |
| out["mean_score"] = (out["mean_score"] - mu) / sigma | |
| return out | |
| def fig_feature_heatmap( | |
| df: pd.DataFrame, | |
| value_col: str, | |
| title: str, | |
| colorbar_title: str, | |
| output_dir: Path, | |
| filename: str, | |
| formats: tuple[str, ...], | |
| ) -> None: | |
| values = pivot_feature(df, value_col) | |
| x_labels = [display_label(f) for f in FORMATS_ORDER] | |
| y_labels = [display_label(t) for t in TOPICS_ORDER] | |
| fig = go.Figure( | |
| go.Heatmap( | |
| z=values, | |
| x=x_labels, | |
| y=y_labels, | |
| colorscale="Viridis", | |
| zmin=0, | |
| xgap=1, | |
| ygap=1, | |
| colorbar=dict(title=colorbar_title, | |
| tickfont=plotly_font("COLORBAR_TICK")), | |
| ) | |
| ) | |
| fig.update_layout( | |
| **paper_layout(title, plot_bgcolor=COLOR_EMPTY_CELL), | |
| xaxis=dict(tickangle=45, tickfont=plotly_font("TICK")), | |
| yaxis=dict(tickfont=plotly_font("TICK")), | |
| width=FIGURE_WIDTH_HEATMAP_PX, | |
| height=FIGURE_HEIGHT_HEATMAP_PX, | |
| ) | |
| save_figure(fig, output_dir, filename, formats, | |
| FIGURE_WIDTH_HEATMAP_PX, FIGURE_HEIGHT_HEATMAP_PX) | |
| print(f" {filename}") | |
| def fig_influence_vs_feature( | |
| features_df: pd.DataFrame, | |
| influence_df: pd.DataFrame, | |
| feature_col: str, | |
| feature_title: str, | |
| feature_colorbar: str, | |
| output_dir: Path, | |
| filename: str, | |
| formats: tuple[str, ...], | |
| ) -> None: | |
| x_labels = [display_label(f) for f in FORMATS_ORDER] | |
| y_labels = [display_label(t) for t in TOPICS_ORDER] | |
| infl_z = zscore_influence(influence_df) | |
| infl = infl_z.rename(columns={ | |
| "topic_label": "bin_topic", "format_label": "bin_format", | |
| }) | |
| infl_matrix = infl.pivot( | |
| index="bin_topic", columns="bin_format", values="mean_score" | |
| ).fillna(0).reindex( | |
| index=TOPICS_ORDER, columns=FORMATS_ORDER, fill_value=0 | |
| ).values.astype(float) | |
| feat_matrix = pivot_feature(features_df, feature_col) | |
| finite = infl_matrix[np.isfinite(infl_matrix)] | |
| abs_max = float(max(np.percentile(np.abs(finite), 95), 1e-8)) | |
| w, h = 1800, 950 | |
| fig = make_subplots( | |
| rows=1, cols=2, | |
| subplot_titles=[ | |
| "SocialIQA: Signed Influence (z-score)", | |
| feature_title, | |
| ], | |
| horizontal_spacing=0.14, | |
| ) | |
| fig.add_trace( | |
| go.Heatmap( | |
| z=infl_matrix, x=x_labels, y=y_labels, | |
| colorscale=DIVERGING_COLORSCALE, | |
| zmid=0, zmin=-abs_max, zmax=abs_max, | |
| xgap=1, ygap=1, | |
| colorbar=dict( | |
| title=dict(text="z-score", side="right"), | |
| x=0.42, len=0.75, thickness=12, | |
| ), | |
| ), | |
| row=1, col=1, | |
| ) | |
| fig.add_trace( | |
| go.Heatmap( | |
| z=feat_matrix, x=x_labels, y=y_labels, | |
| colorscale="Viridis", zmin=0, | |
| xgap=1, ygap=1, | |
| colorbar=dict( | |
| title=dict(text=feature_colorbar, side="right"), | |
| x=1.02, len=0.75, thickness=12, | |
| ), | |
| ), | |
| row=1, col=2, | |
| ) | |
| layout = paper_layout("", plot_bgcolor=COLOR_EMPTY_CELL) | |
| layout["margin"] = dict(l=140, r=80, t=60, b=140) | |
| fig.update_layout(**layout, width=w, height=h) | |
| fig.update_annotations(font=plotly_font("SUBPLOT_TITLE")) | |
| fig.update_xaxes(tickangle=45, tickfont=plotly_font("TICK")) | |
| fig.update_yaxes(tickfont=plotly_font("TICK")) | |
| save_figure(fig, output_dir, filename, formats, w, h) | |
| print(f" {filename}") | |
| def fig_profile_comparison( | |
| features_df: pd.DataFrame, | |
| output_dir: Path, | |
| filename: str, | |
| formats: tuple[str, ...], | |
| ) -> None: | |
| interp_bins = [ | |
| ("literature", "customer_support"), | |
| ("social_life", "q_a_forum"), | |
| ("social_life", "about_pers"), | |
| ] | |
| tech_bins = [ | |
| ("industrial", "documentation"), | |
| ("health", "documentation"), | |
| ("industrial", "structured_data"), | |
| ("industrial", "legal_notices"), | |
| ("fashion_and_beauty", "documentation"), | |
| ("home_and_hobbies", "documentation"), | |
| ("science_math_and_technology", "documentation"), | |
| ] | |
| def weighted_avg(subset: pd.DataFrame, col: str) -> float: | |
| tw = subset["total_words"].sum() | |
| if tw == 0: | |
| return 0.0 | |
| return float((subset[col] * subset["total_words"]).sum() / tw) | |
| interp = features_df[features_df.apply( | |
| lambda r: (r["bin_topic"], r["bin_format"]) in interp_bins, axis=1 | |
| )] | |
| tech = features_df[features_df.apply( | |
| lambda r: (r["bin_topic"], r["bin_format"]) in tech_bins, axis=1 | |
| )] | |
| metrics = ["first_person_per_1k", "second_person_per_1k", | |
| "third_person_per_1k", "mental_state_per_1k"] | |
| metric_labels = ["1st person", "2nd person", "3rd person", "Mental state"] | |
| corpus_vals = [weighted_avg(features_df, m) for m in metrics] | |
| interp_vals = [weighted_avg(interp, m) for m in metrics] | |
| tech_vals = [weighted_avg(tech, m) for m in metrics] | |
| colors = ["#2c7bb6", "#d7191c", "#999999"] | |
| fig = go.Figure() | |
| fig.add_trace(go.Bar( | |
| name="Interpersonal (3 bins)", | |
| x=metric_labels, y=interp_vals, | |
| marker_color=colors[0], | |
| )) | |
| fig.add_trace(go.Bar( | |
| name="Technical (7 bins)", | |
| x=metric_labels, y=tech_vals, | |
| marker_color=colors[1], | |
| )) | |
| fig.add_trace(go.Bar( | |
| name="Corpus average", | |
| x=metric_labels, y=corpus_vals, | |
| marker_color=colors[2], | |
| )) | |
| w, h = 550, 380 | |
| layout = paper_layout("") | |
| layout["margin"] = dict(l=50, r=10, t=10, b=40) | |
| fig.update_layout( | |
| **layout, | |
| barmode="group", | |
| yaxis=dict(title=dict(text="Count per 1,000 words", | |
| font=plotly_font("AXIS_TITLE")), | |
| tickfont=plotly_font("TICK")), | |
| xaxis=dict(tickfont=plotly_font("TICK")), | |
| legend=dict( | |
| x=0.35, y=0.98, | |
| font=plotly_font("LEGEND"), | |
| bgcolor="rgba(255,255,255,0.8)", | |
| borderwidth=0, | |
| ), | |
| width=w, height=h, | |
| bargap=0.15, bargroupgap=0.05, | |
| ) | |
| save_figure(fig, output_dir, filename, formats, w, h) | |
| print(f" {filename}") | |
| def write_contrastive_table( | |
| features_df: pd.DataFrame, | |
| socialiqa_df: pd.DataFrame, | |
| gsm8k_df: pd.DataFrame, | |
| output_dir: Path, | |
| top_n: int = 15, | |
| ) -> None: | |
| siq_z = zscore_influence(socialiqa_df) | |
| gsm_z = zscore_influence(gsm8k_df) | |
| siq = siq_z.rename(columns={ | |
| "topic_label": "bin_topic", "format_label": "bin_format", | |
| "mean_score": "siq_score", | |
| })[["bin_topic", "bin_format", "siq_score"]] | |
| gsm = gsm_z.rename(columns={ | |
| "topic_label": "bin_topic", "format_label": "bin_format", | |
| "mean_score": "gsm_score", | |
| })[["bin_topic", "bin_format", "gsm_score"]] | |
| merged = features_df.merge(siq, on=["bin_topic", "bin_format"], how="left") | |
| merged = merged.merge(gsm, on=["bin_topic", "bin_format"], how="left") | |
| merged["diff"] = merged["siq_score"] - merged["gsm_score"] | |
| top = merged.nlargest(top_n, "diff") | |
| out_path = output_dir / "table_rq4_contrastive_characterized.tex" | |
| with open(out_path, "w") as f: | |
| f.write("\\begin{tabular}{rlrrrr}\n") | |
| f.write("\\toprule\n") | |
| f.write(" & Bin & \\shortstack{Mean\\\\words} " | |
| "& \\shortstack{1st per.\\\\per 1k} " | |
| "& \\shortstack{Mental st.\\\\per 1k} " | |
| "& $\\Delta$ \\\\\n") | |
| f.write("\\midrule\n") | |
| for i, (_, row) in enumerate(top.iterrows(), 1): | |
| label = f"{display_label(row['bin_topic'])} / {display_label(row['bin_format'])}" | |
| label_tex = label.replace("&", "\\&") | |
| f.write( | |
| f"{i} & {label_tex} " | |
| f"& {row['mean_doc_words']:.0f} " | |
| f"& {row['first_person_per_1k']:.1f} " | |
| f"& {row['mental_state_per_1k']:.2f} " | |
| f"& {row['diff']:.2f} \\\\\n" | |
| ) | |
| f.write("\\bottomrule\n") | |
| f.write("\\end{tabular}\n") | |
| print(f" {out_path.name}") | |
| def main(): | |
| parser = argparse.ArgumentParser(description="RQ4 paper figures") | |
| parser.add_argument("--features", required=True) | |
| parser.add_argument("--scores-dir", required=True) | |
| parser.add_argument("--output-dir", required=True) | |
| parser.add_argument("--format", default="png,pdf,html", | |
| help="Comma-separated output formats") | |
| args = parser.parse_args() | |
| out = Path(args.output_dir) | |
| out.mkdir(parents=True, exist_ok=True) | |
| fmts = tuple(args.format.split(",")) | |
| features = pd.read_csv(args.features) | |
| scores_dir = Path(args.scores_dir) | |
| socialiqa = pd.read_csv(scores_dir / "queries_socialiqa_bin_scores.csv") | |
| gsm8k = pd.read_csv(scores_dir / "queries_gsm8k_bin_scores.csv") | |
| print("Generating RQ4 figures...") | |
| fig_feature_heatmap( | |
| features, "mental_state_per_1k", | |
| "Mental-State Verb Density (per 1,000 words)", | |
| "per 1k words", | |
| out, "fig_rq4_mental_state_heatmap", fmts, | |
| ) | |
| fig_feature_heatmap( | |
| features, "first_person_per_1k", | |
| "First-Person Pronoun Density (per 1,000 words)", | |
| "per 1k words", | |
| out, "fig_rq4_first_person_heatmap", fmts, | |
| ) | |
| fig_influence_vs_feature( | |
| features, socialiqa, | |
| "mental_state_per_1k", | |
| "Mental-State Verb Density", | |
| "per 1k words", | |
| out, "fig_rq4_influence_vs_mental_state", fmts, | |
| ) | |
| fig_profile_comparison( | |
| features, out, "fig_rq4_profile_comparison", fmts, | |
| ) | |
| write_contrastive_table(features, socialiqa, gsm8k, out) | |
| print("Done.") | |
| if __name__ == "__main__": | |
| main() | |
Xet Storage Details
- Size:
- 11.7 kB
- Xet hash:
- 9ee1cdb13036c31be2fc5f41920f59da2d31a610f1cfb727ddc6da3a18e5ac27
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.