Skip to content

Commit 5047ecc

Browse files
committed
Feat(viz): More plots
1 parent f2c756d commit 5047ecc

3 files changed

Lines changed: 458 additions & 0 deletions

File tree

Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
from pathlib import Path
2+
3+
import matplotlib.pyplot as plt
4+
import pandas as pd
5+
from matplotlib.ticker import AutoMinorLocator
6+
7+
from codeclash.analysis.viz.utils import FONT_BOLD, MODEL_TO_DISPLAY_NAME
8+
9+
# Load data
10+
df = pd.read_parquet(Path(__file__).parent / "aggregated_results.parquet")
11+
12+
# Map model names early to merge models that map to the same display name
13+
df["model_name"] = df["model_name"].map(lambda x: MODEL_TO_DISPLAY_NAME.get(x, x) if isinstance(x, str) else x)
14+
15+
# Define action category mappings
16+
action_category_mapping = {
17+
"Navigate/search": ["search", "navigate"],
18+
"Read source": ["read.source.new", "read.source.old"],
19+
"Read docs/logs": ["read.docs.new", "read.docs.old", "read.logs.new", "read.logs.old"],
20+
"Modify main": ["write.source.main.create", "write.source.main.modify_old", "write.source.main.modify_new"],
21+
"Write docs": ["write.docs.create", "write.docs.modify_new", "write.docs.modify_old"],
22+
"Write analysis/tests": [
23+
"write.source.analysis.create",
24+
"write.source.tests.create",
25+
"write.source.analysis.modify_new",
26+
"write.source.tests.modify_new",
27+
"write.source.analysis.modify_old",
28+
"write.source.tests.modify_old",
29+
],
30+
"Execute unittests": ["execute.unittest.in_mem", "execute.unittest.new", "execute.unittest.old"],
31+
"Execute analysis": ["execute.analysis.new", "execute.analysis.old", "execute.analysis.in_mem"],
32+
"Execute game": [
33+
"execute.game.setup.new",
34+
"execute.game.setup.old",
35+
"execute.game.new",
36+
"execute.game.old",
37+
"execute.game.setup.in_mem",
38+
"execute.game.in_mem",
39+
],
40+
}
41+
42+
action_category_mapping2 = {
43+
"Read": action_category_mapping["Navigate/search"]
44+
+ action_category_mapping["Read source"]
45+
+ action_category_mapping["Read docs/logs"],
46+
"Modify main": action_category_mapping["Modify main"],
47+
"Unittests": action_category_mapping["Execute unittests"]
48+
+ [
49+
"write.source.tests.create",
50+
"write.source.tests.modify_new",
51+
"write.source.tests.modify_old",
52+
],
53+
"Analysis": action_category_mapping["Execute analysis"]
54+
+ [
55+
"write.source.analysis.create",
56+
"write.source.analysis.modify_new",
57+
"write.source.analysis.modify_old",
58+
],
59+
"Simulations": action_category_mapping["Execute game"]
60+
+ [
61+
"execute.game.setup.new",
62+
"execute.game.setup.old",
63+
"execute.game.new",
64+
"execute.game.old",
65+
"execute.game.setup.in_mem",
66+
"execute.game.in_mem",
67+
],
68+
}
69+
70+
prefix = "c_"
71+
models = sorted([m for m in df["model_name"].unique() if isinstance(m, str)], reverse=True)
72+
73+
# Get all mapped actions for MISC calculation
74+
all_mapped_actions = set()
75+
for actions in action_category_mapping2.values():
76+
all_mapped_actions.update(actions)
77+
78+
# Collect data for each model
79+
category_order = list(action_category_mapping2.keys()) + ["MISC"]
80+
early_data = []
81+
late_data = []
82+
83+
for model in models:
84+
# Early rounds (≤7)
85+
early_model_df = df[(df["model_name"] == model) & (df["round_number"] <= 7)]
86+
early_category_values = []
87+
88+
for category in category_order[:-1]:
89+
total = 0
90+
for action in action_category_mapping2[category]:
91+
col = f"{prefix}{action}"
92+
if col in df.columns:
93+
total += early_model_df[col].mean()
94+
early_category_values.append(total)
95+
96+
# Get MISC value
97+
misc_total = 0
98+
for col in df.columns:
99+
if col.startswith(prefix):
100+
action = col.removeprefix(prefix)
101+
if action not in all_mapped_actions:
102+
misc_total += early_model_df[col].mean()
103+
early_category_values.append(misc_total)
104+
early_data.append(early_category_values)
105+
106+
# Late rounds (≥8)
107+
late_model_df = df[(df["model_name"] == model) & (df["round_number"] >= 8)]
108+
late_category_values = []
109+
110+
for category in category_order[:-1]:
111+
total = 0
112+
for action in action_category_mapping2[category]:
113+
col = f"{prefix}{action}"
114+
if col in df.columns:
115+
total += late_model_df[col].mean()
116+
late_category_values.append(total)
117+
118+
# Get MISC value
119+
misc_total = 0
120+
for col in df.columns:
121+
if col.startswith(prefix):
122+
action = col.removeprefix(prefix)
123+
if action not in all_mapped_actions:
124+
misc_total += late_model_df[col].mean()
125+
late_category_values.append(misc_total)
126+
late_data.append(late_category_values)
127+
128+
# Create horizontal stacked bar chart
129+
fig, ax = plt.subplots(figsize=(12, 8))
130+
131+
# Create y positions: two bars per model (touching), with gaps between models
132+
gap = 0.3
133+
y_positions = []
134+
model_positions = []
135+
for i, model in enumerate(models):
136+
base = i * (2 + gap)
137+
y_positions.extend([base, base + 1])
138+
model_positions.append(base + 0.5)
139+
140+
# Define colors for each category
141+
colors = plt.cm.tab10(range(len(category_order)))
142+
143+
# Plot late rounds (bottom bar) and early rounds (top bar) for each model
144+
all_data = []
145+
for early, late in zip(early_data, late_data):
146+
all_data.extend([late, early])
147+
148+
# Starting position for each stack
149+
left = [0] * len(y_positions)
150+
151+
for cat_idx, category in enumerate(category_order):
152+
values = [all_data[pos_idx][cat_idx] for pos_idx in range(len(y_positions))]
153+
ax.barh(y_positions, values, left=left, label=category, alpha=0.8, color=colors[cat_idx], height=1.0)
154+
155+
# Add value labels for significant values
156+
for i, (y_pos, val) in enumerate(zip(y_positions, values)):
157+
x_pos = left[i] + val / 2
158+
if val >= 1.0:
159+
ax.text(
160+
x_pos,
161+
y_pos,
162+
f"{val:.1f}",
163+
fontsize=11,
164+
ha="center",
165+
va="center",
166+
color="white",
167+
fontweight="bold",
168+
fontproperties=FONT_BOLD,
169+
)
170+
elif val >= 0.5:
171+
ax.text(
172+
x_pos,
173+
y_pos,
174+
f"{val:.1f}",
175+
fontsize=10,
176+
ha="center",
177+
va="center",
178+
color="white",
179+
fontweight="bold",
180+
fontproperties=FONT_BOLD,
181+
)
182+
183+
left = [left[i] + values[i] for i in range(len(y_positions))]
184+
185+
# Add total bar length numbers at the end of each bar
186+
for y_pos, total in zip(y_positions, left):
187+
ax.text(
188+
total + 0.5,
189+
y_pos,
190+
f"{total:.1f}",
191+
fontsize=12,
192+
ha="left",
193+
va="center",
194+
color="black",
195+
fontweight="bold",
196+
fontproperties=FONT_BOLD,
197+
)
198+
199+
# Add round labels on the left side of the plot
200+
for i, (y_late, y_early) in enumerate(zip(y_positions[::2], y_positions[1::2])):
201+
ax.text(-0.2, y_late, "round ≥8", fontsize=11, ha="right", va="center", color="gray", fontproperties=FONT_BOLD)
202+
ax.text(-0.2, y_early, "round ≤7", fontsize=11, ha="right", va="center", color="gray", fontproperties=FONT_BOLD)
203+
204+
legend_font = FONT_BOLD.copy()
205+
legend_font.set_size(15)
206+
ax.legend(loc="upper center", bbox_to_anchor=(0.5, 1.05), frameon=False, prop=legend_font, ncol=6)
207+
ax.set_yticks(model_positions)
208+
ax.set_yticklabels(models, fontsize=14, fontproperties=FONT_BOLD)
209+
ax.set_xlabel("Mean Action Count", fontsize=15, fontproperties=FONT_BOLD)
210+
211+
# Add minor ticks to x-axis
212+
ax.xaxis.set_minor_locator(AutoMinorLocator())
213+
ax.tick_params(axis="x", which="minor", length=3)
214+
ax.tick_params(axis="x", which="major", length=6)
215+
216+
# Remove top and right spines
217+
ax.spines["top"].set_visible(False)
218+
ax.spines["right"].set_visible(False)
219+
ax.spines["left"].set_visible(True)
220+
221+
# Set tick label fonts
222+
for label in ax.get_xticklabels():
223+
label.set_fontproperties(FONT_BOLD)
224+
label.set_fontsize(14)
225+
for label in ax.get_yticklabels():
226+
label.set_fontproperties(FONT_BOLD)
227+
228+
plt.tight_layout()
229+
230+
# Save to PDF
231+
output_path = Path(__file__).parent / "action_categories_by_model.pdf"
232+
plt.savefig(output_path, format="pdf", bbox_inches="tight")
233+
print(f"Plot saved to {output_path}")
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
from pathlib import Path
2+
3+
import matplotlib.pyplot as plt
4+
import pandas as pd
5+
from matplotlib.ticker import AutoMinorLocator
6+
7+
from codeclash.analysis.viz.utils import FONT_BOLD, MODEL_TO_DISPLAY_NAME
8+
9+
10+
def main():
11+
# Load data
12+
df = pd.read_parquet(Path(__file__).parent / "aggregated_results.parquet")
13+
df["model_name"] = df["model_name"].str.split("/").str[1]
14+
df = df.query("model_name != opponent_model_name").copy()
15+
16+
# Get unique models
17+
models = sorted(
18+
[m for m in df["model_name"].unique() if isinstance(m, str)], key=lambda m: MODEL_TO_DISPLAY_NAME.get(m, m)
19+
)
20+
21+
# Calculate number of rows needed (2 columns)
22+
n_cols = 2
23+
n_rows = (len(models) + n_cols - 1) // n_cols
24+
25+
# Create figure with subplots
26+
fig, axes = plt.subplots(n_rows, n_cols, figsize=(12, 3.5 * n_rows))
27+
axes = axes.flatten() if n_rows > 1 else [axes] if n_cols == 1 else axes
28+
29+
# Category order and colors
30+
category_order = ["feature", "change", "fix", "tweak", "none"]
31+
colors = ["#2E86AB", "#A23B72", "#F18F01", "#C73E1D", "#6A994E"]
32+
33+
# Plot for each model
34+
for idx, model in enumerate(models):
35+
ax = axes[idx]
36+
37+
# Filter data for this model
38+
model_df = df[df["model_name"] == model]
39+
40+
# Get category counts by round
41+
pivot_data = model_df.groupby(["round_number", "edit_category"]).size().unstack(fill_value=0)
42+
43+
# Calculate percentages
44+
pivot_pct = pivot_data.div(pivot_data.sum(axis=1), axis=0) * 100
45+
46+
# Only include categories that exist in the data
47+
available_categories = [cat for cat in category_order if cat in pivot_pct.columns]
48+
pivot_pct = pivot_pct[available_categories]
49+
50+
# Get corresponding colors for available categories
51+
category_colors = [colors[category_order.index(cat)] for cat in available_categories]
52+
53+
# Create stacked area chart
54+
stackplot_kwargs = {"colors": category_colors, "alpha": 0.85}
55+
if idx == 0:
56+
stackplot_kwargs["labels"] = pivot_pct.columns
57+
58+
ax.stackplot(pivot_pct.index, [pivot_pct[col] for col in pivot_pct.columns], **stackplot_kwargs)
59+
60+
# Add markers to show data points more clearly
61+
# Calculate cumulative values for plotting markers at boundaries
62+
cumsum = pivot_pct.cumsum(axis=1)
63+
for i, col in enumerate(pivot_pct.columns):
64+
y_values = cumsum[col]
65+
ax.plot(
66+
pivot_pct.index,
67+
y_values,
68+
marker="o",
69+
markersize=4,
70+
markerfacecolor="white",
71+
markeredgecolor=category_colors[i],
72+
markeredgewidth=1.5,
73+
linestyle="",
74+
zorder=10,
75+
)
76+
77+
# Get display name
78+
display_name = MODEL_TO_DISPLAY_NAME.get(model, model)
79+
80+
# Styling
81+
ax.set_xlabel("Round Number", fontproperties=FONT_BOLD, fontsize=14)
82+
ax.set_ylabel("Percentage (%)", fontproperties=FONT_BOLD, fontsize=14)
83+
ax.set_title(display_name, fontproperties=FONT_BOLD, fontsize=16, pad=10)
84+
ax.set_xlim(1, 15)
85+
ax.set_ylim(0, 100)
86+
ax.grid(True, alpha=0.3)
87+
88+
# Add minor ticks to y-axis
89+
ax.yaxis.set_minor_locator(AutoMinorLocator())
90+
91+
# Make tick labels bold
92+
for label in ax.get_xticklabels() + ax.get_yticklabels():
93+
label.set_fontproperties(FONT_BOLD)
94+
label.set_fontsize(12)
95+
96+
# Remove spines
97+
ax.spines["top"].set_visible(False)
98+
ax.spines["right"].set_visible(False)
99+
100+
# Hide unused subplots
101+
for idx in range(len(models), len(axes)):
102+
axes[idx].set_visible(False)
103+
104+
# Create legend at the top with 5 columns
105+
handles, labels = axes[0].get_legend_handles_labels()
106+
# Reverse to match the stacking order (bottom to top)
107+
handles = handles[::-1]
108+
labels = [label.capitalize() for label in labels[::-1]]
109+
110+
legend_font = FONT_BOLD.copy()
111+
legend_font.set_size(15)
112+
113+
fig.legend(handles, labels, loc="upper center", bbox_to_anchor=(0.5, 1.0), ncol=5, frameon=False, prop=legend_font)
114+
115+
plt.tight_layout()
116+
plt.subplots_adjust(top=0.93)
117+
118+
# Save
119+
output_path = Path(__file__).parent / "edit_type_stacked.pdf"
120+
plt.savefig(output_path, bbox_inches="tight", dpi=300)
121+
print(f"Saved to {output_path}")
122+
plt.close()
123+
124+
125+
if __name__ == "__main__":
126+
main()

0 commit comments

Comments
 (0)