Skip to content

Commit 6941b31

Browse files
committed
Feat(viz): More plots on clutter
1 parent b52daf6 commit 6941b31

3 files changed

Lines changed: 530 additions & 0 deletions

File tree

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
#!/usr/bin/env python3
2+
"""Plot the cumulative total number of created files vs round."""
3+
4+
import argparse
5+
import json
6+
7+
import matplotlib.pyplot as plt
8+
import pandas as pd
9+
from matplotlib.font_manager import FontProperties
10+
11+
from codeclash.analysis.viz.scatter_codebase_organization import (
12+
ASSETS_SUBFOLDER,
13+
DATA_CACHE,
14+
)
15+
from codeclash.analysis.viz.utils import FONT_BOLD, MODEL_TO_COLOR, MODEL_TO_DISPLAY_NAME
16+
17+
18+
def calculate_file_counts_by_extension_at_round(data: list, target_round: int = 15) -> pd.DataFrame:
19+
"""Calculate total file counts by extension at a specific round."""
20+
results = []
21+
22+
for entry in data:
23+
player = entry["player"]
24+
tournament = entry["tournament"]
25+
file_history = entry["file_history"]
26+
27+
# Count files by extension that were created at or before target_round
28+
extension_counts = {}
29+
for filename, history in file_history.items():
30+
# Find creation round
31+
creation_round = None
32+
for round_num, op, _, _ in history:
33+
if op == "created":
34+
creation_round = round_num
35+
break
36+
37+
if creation_round is not None and creation_round <= target_round:
38+
# Extract extension
39+
if "." in filename:
40+
ext = filename.rsplit(".", 1)[1]
41+
else:
42+
ext = "no_extension"
43+
44+
extension_counts[ext] = extension_counts.get(ext, 0) + 1
45+
46+
# Store result
47+
result_row = {
48+
"player": player,
49+
"tournament": tournament,
50+
}
51+
result_row.update(extension_counts)
52+
results.append(result_row)
53+
54+
df = pd.DataFrame(results).fillna(0)
55+
56+
# Aggregate by player (mean across tournaments)
57+
extension_cols = [col for col in df.columns if col not in ["player", "tournament"]]
58+
59+
agg_dict = {col: "mean" for col in extension_cols}
60+
player_agg = df.groupby("player").agg(agg_dict).reset_index()
61+
62+
# Rename columns to count_<ext>
63+
rename_dict = {col: f"count_{col}" for col in extension_cols}
64+
65+
return player_agg.rename(columns=rename_dict)
66+
67+
68+
def calculate_cumulative_created_files_per_round(data: list, max_round: int = 15) -> pd.DataFrame:
69+
"""Calculate cumulative total number of created files per player per round."""
70+
results = []
71+
72+
for entry in data:
73+
player = entry["player"]
74+
tournament = entry["tournament"]
75+
file_history = entry["file_history"]
76+
77+
# Get all creation rounds
78+
creation_rounds = []
79+
for _, history in file_history.items():
80+
for round_num, op, _, _ in history:
81+
if op == "created":
82+
creation_rounds.append(round_num)
83+
84+
# Calculate cumulative counts per round (up to max_round)
85+
for round_num in range(max_round + 1):
86+
cumulative_count = sum(1 for r in creation_rounds if r <= round_num)
87+
results.append(
88+
{"player": player, "tournament": tournament, "round": round_num, "total_files": cumulative_count}
89+
)
90+
91+
return pd.DataFrame(results)
92+
93+
94+
def filter_outlier_tournaments_by_total_files_99p(created_files_df: pd.DataFrame) -> pd.DataFrame:
95+
"""Filter out player-tournament trajectories above the player's 99th percentile.
96+
97+
Uses the maximum cumulative total per player-tournament to compute the cutoff,
98+
mirroring the 99% filtering logic from the bar chart.
99+
"""
100+
max_totals = (
101+
created_files_df.groupby(["player", "tournament"], as_index=False)["total_files"]
102+
.max()
103+
.rename(columns={"total_files": "max_total_files"})
104+
)
105+
q99_per_player = max_totals.groupby("player")["max_total_files"].transform(lambda s: s.quantile(0.99))
106+
valid_pairs = max_totals[max_totals["max_total_files"] <= q99_per_player][["player", "tournament"]]
107+
return created_files_df.merge(valid_pairs, on=["player", "tournament"], how="inner")
108+
109+
110+
def plot_total_created_files_over_rounds(created_files_df: pd.DataFrame):
111+
"""Line plot showing cumulative total created files over rounds per model.
112+
113+
- X: Round number
114+
- Y: Total number of files created (cumulative, mean across tournaments)
115+
- One line per model, colored consistently
116+
"""
117+
# Aggregate by player and round (mean across all tournaments)
118+
print("------")
119+
print(created_files_df.groupby(["player"]).agg({"total_files": "max"}).reset_index())
120+
print("------")
121+
agg_created = (
122+
created_files_df
123+
# .query("total_files < 10_000") # remove outliers
124+
.groupby(["player", "round"])
125+
.agg({"total_files": "mean"})
126+
.reset_index()
127+
)
128+
129+
# Figure styling to match other viz
130+
plt.figure(figsize=(6, 6))
131+
132+
# Plot one line per model with consistent color & legend label
133+
seen_labels = set()
134+
for player in sorted(agg_created["player"].unique()):
135+
player_data = agg_created[agg_created["player"] == player]
136+
color = MODEL_TO_COLOR.get(player, "#333333")
137+
label = MODEL_TO_DISPLAY_NAME.get(player, player)
138+
139+
# Avoid duplicate legend entries
140+
plot_label = label if label not in seen_labels else None
141+
if plot_label:
142+
seen_labels.add(label)
143+
144+
plt.plot(
145+
player_data["round"],
146+
player_data["total_files"],
147+
color=color,
148+
linewidth=2.5,
149+
label=plot_label,
150+
alpha=0.9,
151+
)
152+
153+
# Axes labels
154+
plt.xlabel("Round", fontproperties=FONT_BOLD, fontsize=18)
155+
plt.ylabel("Total created files", fontproperties=FONT_BOLD, fontsize=18)
156+
plt.xticks(fontproperties=FONT_BOLD, fontsize=14)
157+
plt.yticks(fontproperties=FONT_BOLD, fontsize=14)
158+
159+
# Grid & legend
160+
plt.grid(True, alpha=0.3)
161+
legend_font = FontProperties(fname=FONT_BOLD.get_file(), size=14)
162+
plt.legend(loc="best", prop=legend_font)
163+
164+
plt.tight_layout()
165+
OUTPUT_FILE = ASSETS_SUBFOLDER / "line_chart_total_created_files_vs_round.pdf"
166+
plt.savefig(OUTPUT_FILE, bbox_inches="tight")
167+
print(f"Saved total created files plot to {OUTPUT_FILE}")
168+
169+
170+
def main():
171+
data = []
172+
with open(DATA_CACHE) as f:
173+
print("Loading data from", DATA_CACHE)
174+
data = [json.loads(line) for line in f]
175+
print(f"Found {len(data)} player-tournament entries in cache.")
176+
177+
print("\n=== Calculating File Counts by Extension at Round 15 ===")
178+
extension_counts_df = calculate_file_counts_by_extension_at_round(data, target_round=15)
179+
print(extension_counts_df)
180+
output_csv = ASSETS_SUBFOLDER / "file_counts_by_extension_round15.csv"
181+
extension_counts_df.to_csv(output_csv, index=False)
182+
print(f"Saved file counts by extension to {output_csv}")
183+
184+
print("\n=== Calculating Cumulative Created Files Per Round ===")
185+
created_files_df = calculate_cumulative_created_files_per_round(data)
186+
print(created_files_df.head(20))
187+
# Apply 99% per-player filtering (mirrors bar chart logic)
188+
created_files_df = filter_outlier_tournaments_by_total_files_99p(created_files_df)
189+
190+
print("\n=== Plotting Total Created Files Over Rounds ===")
191+
plot_total_created_files_over_rounds(created_files_df)
192+
193+
194+
if __name__ == "__main__":
195+
parser = argparse.ArgumentParser(description="Plot cumulative total created files vs round")
196+
args = parser.parse_args()
197+
main()

0 commit comments

Comments
 (0)