Repository: unitaryfund/ucc
Pull request #254: Change plot resolution to per release in plot over time scripts
Author: login: Misty-W
Created: 2025-02-24T23:01:28Z | Merged: 2025-03-13T00:02:59Z

Fixes unitaryfoundation/ucc#200.

Change plot resolution to per compiler release in plot over time scripts, so the plots will look like the following:
![image](https://github.com/user-attachments/assets/9a29a5db-eedd-48c2-b95f-00b445020fa7)
![image](https://github.com/user-attachments/assets/6203dd07-2b5c-4bf2-9e0e-510e9d8e8382)

Note: I pasted (instead of pushed) these plots because they'd have to be removed before merge anyway.


Diff:
diff --git a/benchmarks/scripts/common.py b/benchmarks/scripts/common.py
index f0fe6430..bf8b46e5 100644
--- a/benchmarks/scripts/common.py
+++ b/benchmarks/scripts/common.py
@@ -3,6 +3,7 @@
 import os
 import pandas as pd
 import matplotlib
+import re
 from datetime import datetime
 from typing import List
 from cirq import optimize_for_target_gateset
@@ -525,3 +526,8 @@ def adjust_axes_to_fit_labels(
     # Set the new axis limits
     ax.set_xlim(x_min, x_max)
     ax.set_ylim(y_min, y_max)
+
+
+def extract_compiler_versions(header):
+    pattern = r"(\w+)=([\d\.]+)"
+    return dict(re.findall(pattern, header))
diff --git a/benchmarks/scripts/plot_avg_benchmarks_over_time.py b/benchmarks/scripts/plot_avg_benchmarks_over_time.py
index 7a97f69a..c523b23a 100644
--- a/benchmarks/scripts/plot_avg_benchmarks_over_time.py
+++ b/benchmarks/scripts/plot_avg_benchmarks_over_time.py
@@ -1,10 +1,13 @@
-from common import annotate_and_adjust, adjust_axes_to_fit_labels
-
+from common import (
+    annotate_and_adjust,
+    adjust_axes_to_fit_labels,
+    extract_compiler_versions,
+)
+from pkg_resources import parse_version
 import glob
 import pandas as pd
 import matplotlib.pyplot as plt
 import os
-import re
 
 
 ### Load data
@@ -16,11 +19,6 @@
 dataframes = []
 
 
-def extract_compiler_versions(header):
-    pattern = r"(\w+)=([\d\.]+)"
-    return dict(re.findall(pattern, header))
-
-
 print("Loading data files...")
 for file in csv_files:
     compiler_versions = {}
@@ -38,6 +36,8 @@ def extract_compiler_versions(header):
         # Remove pytket from compiler_versions for this datafile
         compiler_versions.pop("pytket")
     df["compiler_version"] = df["compiler"].map(compiler_versions)
+    # Scrub semver string of any extraneous period at end
+    df["compiler_version"] = df["compiler_version"].str.rstrip(".")
 
     dataframes.append(df)
 
@@ -49,8 +49,23 @@ def extract_compiler_versions(header):
 # Remove data from dates between 2025-02-07 through 2025-02-28 while #251 was being fixed
 df_dates = df_dates[
     ~((df_dates["date"] >= "2025-02-07") & (df_dates["date"] < "2025-02-28"))
+    & (df_dates["date"] != "2025-03-05")
 ]
 
+# Ensure 'date' is in datetime format
+df_dates["date"] = pd.to_datetime(df_dates["date"])
+
+# Get the earliest date for each compiler version
+new_version_dates = df_dates.groupby(["compiler", "compiler_version"])[
+    "date"
+].min()
+
+# Get all unique first occurrence dates
+unique_dates = new_version_dates.unique()
+
+# Filter on first occurrence of each compiler version based on the date
+df_dates = df_dates[df_dates["date"].isin(unique_dates)]
+
 # Find the average compiled ratio for each compiler on each date
 avg_compiled_ratio = (
     df_dates.groupby(["compiler", "date", "compiler_version"])[
@@ -60,6 +75,20 @@ def extract_compiler_versions(header):
     .reset_index()
     .sort_values("date")
 )
+# Sort the dataframe by package, date, and parsed version
+avg_compiled_ratio["parsed_version"] = avg_compiled_ratio[
+    "compiler_version"
+].apply(parse_version)
+avg_compiled_ratio = avg_compiled_ratio.sort_values(
+    by=["date", "compiler", "parsed_version"], ascending=[True, True, True]
+)
+avg_compiled_ratio = avg_compiled_ratio.sort_values(
+    by=["date", "compiler", "compiler_version"], ascending=[True, True, True]
+)
+# Keep only the highest version per (date, package)
+avg_compiled_ratio = avg_compiled_ratio.drop_duplicates(
+    subset=["date", "compiler"], keep="last"
+)
 
 # Find the average compile time for each compiler on each date
 avg_compile_time = (
@@ -69,6 +98,17 @@ def extract_compiler_versions(header):
     .sort_values("date")
 )
 
+# Sort the dataframe by package, date, and parsed version
+avg_compile_time["parsed_version"] = avg_compile_time[
+    "compiler_version"
+].apply(parse_version)
+avg_compile_time = avg_compile_time.sort_values(
+    by=["date", "compiler", "parsed_version"], ascending=[True, True, True]
+)
+# Keep only the highest version per (date, package)
+avg_compile_time = avg_compile_time.drop_duplicates(
+    subset=["date", "compiler"], keep="last"
+)
 
 ###### Plotting
 # Ensure colors are consistently assigned to each compiler
@@ -115,11 +155,10 @@ def extract_compiler_versions(header):
         compiled_ratio = row["compiled_ratio"]
 
         # Check if the version has changed
-        if current_version != last_version_seen[compiler]:
-            text = f"{compiler}={current_version}"
+        if row["parsed_version"] != last_version_seen[compiler]:
+            text = f"{current_version}"
             xy = (row["date"], compiled_ratio)
             color = color_map[compiler]
-
             # Add the annotation and adjust for overlap
             annotate_and_adjust(
                 ax=ax[0],
@@ -133,7 +172,7 @@ def extract_compiler_versions(header):
             )
             # plt.pause(0.1)
             # Update the last seen version for this compiler
-            last_version_seen[compiler] = current_version
+            last_version_seen[compiler] = row["parsed_version"]
 
 # Set y axis range to be slightly larger than data range
 adjust_axes_to_fit_labels(ax[0], y_scale=[1.1, 1.3])
@@ -176,7 +215,7 @@ def extract_compiler_versions(header):
 
         # Check if the version has changed
         if current_version != last_version_seen[compiler]:
-            text = f"{compiler}={current_version}"
+            text = f"{current_version}"
             xy = (row["date"], compile_time)
             color = color_map[compiler]
 
diff --git a/benchmarks/scripts/plot_expval_benchmarks_over_time.py b/benchmarks/scripts/plot_expval_benchmarks_over_time.py
index 02a52a00..744ae5a6 100644
--- a/benchmarks/scripts/plot_expval_benchmarks_over_time.py
+++ b/benchmarks/scripts/plot_expval_benchmarks_over_time.py
@@ -2,19 +2,24 @@
 import glob
 import pandas as pd
 import matplotlib.pyplot as plt
+from common import annotate_and_adjust, extract_compiler_versions
 
-# Step 1: Get the directory of the current script
+# Get the directory of the current script
 directory_of_this_file = os.path.dirname(os.path.abspath(__file__))
 
-# Step 2: Construct the correct path to the results folder
+# Construct the correct path to the results folder
 results_folder = os.path.join(directory_of_this_file, "../results")
 
-# Step 3: Use glob to find all CSV files in the results folder
+# Use glob to find all CSV files in the results folder
 csv_files = glob.glob(os.path.join(results_folder, "expval_*.csv"))
 
-# Step 4: Iterate through all CSV files and load them into dataframes with date column
+# Iterate through all CSV files and load them into dataframes with date column
 dfs = []  # List to store dataframes
 for file in csv_files:
+    # Extract the version from the file header
+    with open(file, "r") as f:
+        header_line = f.readline().strip()
+        compiler_versions = extract_compiler_versions(header_line)
     # Extract the date from the filename (assuming the date is after 'expval_' and before '.csv')
     date_label = str(file).split("_")[1].split(".")[0]
 
@@ -23,24 +28,36 @@
 
     # Add the extracted date as a new column in the dataframe
     df["date"] = date_label
+    df["compiler_version"] = df["compiler"].map(compiler_versions)
 
     # Append the dataframe to the list
     dfs.append(df)
 
-# Step 5: Concatenate all dataframes into one large dataframe
+# Concatenate all dataframes into one large dataframe
 df_all = pd.concat(dfs, ignore_index=True)
 
 # Convert the 'date' column to datetime
 df_all["date"] = pd.to_datetime(df_all["date"])
 
-# Step 6: Group by date and compiler, and calculate the average absolute error
+# Get the earliest date for each compiler version
+new_version_dates = df_all.groupby(["compiler", "compiler_version"])[
+    "date"
+].min()
+
+# Get all unique first occurrence dates
+unique_dates = new_version_dates.unique()
+
+# Filter on first occurrence of each compiler version based on the date
+df_all = df_all[df_all["date"].isin(unique_dates)]
+
+# Group by date and compiler, and calculate the average absolute error
 summary = (
-    df_all.groupby(["date", "compiler"])
+    df_all.groupby(["date", "compiler", "compiler_version"])
     .agg(avg_absolute_error=("absolute_error", "mean"))
     .reset_index()
 )
 
-# Step 7: Set up the figure
+# Set up the figure
 fig, ax = plt.subplots(figsize=(12, 6))
 
 # Set color map for different compilers
@@ -50,7 +67,8 @@
     compiler: colormap(i) for i, compiler in enumerate(unique_compilers)
 }
 
-# Step 8: Plot average absolute error over time
+
+# Plot average absolute error over time
 for compiler in unique_compilers:
     compiler_data = summary[summary["compiler"] == compiler]
 
@@ -62,7 +80,40 @@
         marker="o",
     )
 
-# Step 9: Customize plot
+# Customize plot
+last_version_seen = {compiler: None for compiler in unique_compilers}
+
+
+previous_bboxes = []
+
+for _, row in summary.iterrows():
+    # Get the version for this date
+    current_version = row["compiler_version"]
+    compiler = row["compiler"]
+    avg_absolute_error = row["avg_absolute_error"]
+
+    if current_version != last_version_seen[compiler]:
+        text = f"{current_version}"
+        xy = (row["date"], avg_absolute_error)
+        color = color_map[compiler]
+
+        # Add the annotation and adjust for overlap
+        annotate_and_adjust(
+            ax=ax,
+            text=text,
+            xy=xy,
+            color=color,
+            previous_bboxes=previous_bboxes,
+            offset=(0, 20),  # Initial offset
+            increment=2,  # Vertical adjustment step
+            max_attempts=15,
+        )
+        # plt.pause(0.1)
+        # Update the last seen version for this compiler
+        last_version_seen[compiler] = current_version
+
+
+# Customize plot
 ax.set_title("Average Absolute Error Over Time")
 ax.set_xlabel("Date")
 ax.set_ylabel("Average Absolute Error")


Reviews:
user: login: bachase
body: The comments on the logic for selecting and filtering which benchmark results end up included in the average performance on earliest version release are my main open questions.

**edit** To be clear, its only the date and version selection logic that I wanted to understand as part of this review. The discussion below is more food for thought for the future.

But stepping back, I think this may still be fragile or at least challenging given how the benchmark results are stored by the date of the run currently. What if some days have more benchmark runs than others? If so, will that be a fair comparison if some days average over many runs and others only a single run? 

As I (poorly) started to sketch out in #251, I think we should consider switching to associate benchmark results with the git hash (and corresponding git commit time) to ensure a singular mapping from set of compiler versions (as reflected in the lock file in the commit) to benchmark results. Then we can more cleanly pick out results when versions change and plot accordingly.  

That is clearly a larger scope than this change, but I suspect getting the plot we want will be hard using the current way we run and store benchmarks. Working around it might be possible, but the code will get more complicated and arguably worth testing to be sure all the cases are handled.


state: CHANGES_REQUESTED
author_association: COLLABORATOR
submitted_at: 2025-02-25T15:45:31Z
user: login: jordandsullivan
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-02-26T20:39:01Z
user: login: jordandsullivan
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-02-26T20:40:45Z
user: login: Misty-W
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-02-26T20:51:43Z
user: login: Misty-W
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-02-26T20:52:41Z
user: login: bachase
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-02-26T21:47:18Z
user: login: bachase
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-02-26T21:56:49Z
user: login: bachase
body: Generally 👍. 

I did notice when running locally for me, there's still a good bit of overlap with all the versions.

Have we tried an off-the-shelf tool like https://adjusttext.readthedocs.io/en/latest/index.html to manage this versus the `annotate_and_adjust` approach?

![image](https://github.com/user-attachments/assets/a3012359-2040-4b4d-b481-7a429202a4dc)

state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-11T18:35:50Z
user: login: Misty-W
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-12T19:58:02Z
user: login: Misty-W
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-12T19:58:08Z
user: login: bachase
body: LGTM and support removing (or not removing) those unintended versions.


state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-03-12T20:29:09Z
user: login: Misty-W
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-13T00:00:26Z

Review comments:
diff_hunk: @@ -62,17 +60,27 @@ def extract_compiler_versions(header):
 color_map = {
     compiler: colormap(i) for i, compiler in enumerate(unique_compilers)
 }
+unique_versions = sorted(df_dates["compiler_version"].unique())
+dates = []
+for version in unique_versions:
+    df_versions = df_dates[df_dates["compiler_version"] == version]
+    earliest_date = sorted(df_versions["date"].unique())[0]
+    dates.append(earliest_date)
 
 fig, ax = plt.subplots(2, 1, figsize=(8, 8), sharex=False, dpi=150)
 # Rotate x labels on axes 0
 plt.setp(ax[0].xaxis.get_majorticklabels(), rotation=45)
 
 
+filtered_avg_compiled_ratio = avg_compiled_ratio[
path: benchmarks/scripts/plot_avg_benchmarks_over_time.py
user: login: bachase
body: How are we thinking about which dates are included in the earlier average and this filter?

`avg_compiled_ratio` is calculated earlier as:
```python
avg_compiled_ratio = (
    df_dates.groupby(["compiler", "date", "compiler_version"])[
        "compiled_ratio"
    ]
    .mean()
    .reset_index()
    .sort_values("date")
)
```

If we happened to have run multiple benchmarks for that compiler on that date, where say some were for version 1.2.0 and then some for version 1.3.0, would this filter be showing results that average over both versions?

Would we also need to filter to only include/average results from that date where the latest compiler version was used?

created_at: 2025-02-25T15:23:34Z
author_association: COLLABORATOR
diff_hunk: @@ -62,17 +60,27 @@ def extract_compiler_versions(header):
 color_map = {
     compiler: colormap(i) for i, compiler in enumerate(unique_compilers)
 }
+unique_versions = sorted(df_dates["compiler_version"].unique())
+dates = []
+for version in unique_versions:
+    df_versions = df_dates[df_dates["compiler_version"] == version]
path: benchmarks/scripts/plot_avg_benchmarks_over_time.py
user: login: bachase
body: Is this `compiler_version` only the semver string? If so, is it possible that this might mix the same version of different packages? For example `cirq 1.2.0` and `qiskit 1.2.0`? If so, I think that might end up taking the earliest date across the union of those two.
created_at: 2025-02-25T15:33:02Z
author_association: COLLABORATOR
diff_hunk: @@ -33,9 +39,18 @@
 # Convert the 'date' column to datetime
 df_all["date"] = pd.to_datetime(df_all["date"])
 
+unique_versions = sorted(df_all["compiler_version"].unique())
path: benchmarks/scripts/plot_expval_benchmarks_over_time.py
user: login: bachase
body: Just to note similar questions as above on whether this is selecting the intended dates and versions.
created_at: 2025-02-25T15:35:52Z
author_association: COLLABORATOR
diff_hunk: @@ -151,8 +166,8 @@ def extract_compiler_versions(header):
     )
 
 # Add annotations for version changes
-for date in avg_compile_time["date"].unique():
-    date_data = avg_compile_time[avg_compile_time["date"] == date]
+for date in filtered_avg_compile_time["date"].unique():
+    date_data = filtered_avg_compile_time[avg_compile_time["date"] == date]
path: benchmarks/scripts/plot_avg_benchmarks_over_time.py
user: login: bachase
body: I think this is mixing dataframes, consider:
```suggestion
    date_data = filtered_avg_compile_time[filtered_avg_compile_time["date"] == date]
```
created_at: 2025-02-25T15:37:13Z
author_association: COLLABORATOR
diff_hunk: @@ -62,17 +60,27 @@ def extract_compiler_versions(header):
 color_map = {
     compiler: colormap(i) for i, compiler in enumerate(unique_compilers)
 }
+unique_versions = sorted(df_dates["compiler_version"].unique())
+dates = []
+for version in unique_versions:
+    df_versions = df_dates[df_dates["compiler_version"] == version]
+    earliest_date = sorted(df_versions["date"].unique())[0]
+    dates.append(earliest_date)
 
 fig, ax = plt.subplots(2, 1, figsize=(8, 8), sharex=False, dpi=150)
 # Rotate x labels on axes 0
 plt.setp(ax[0].xaxis.get_majorticklabels(), rotation=45)
 
 
+filtered_avg_compiled_ratio = avg_compiled_ratio[
path: benchmarks/scripts/plot_avg_benchmarks_over_time.py
user: login: jordandsullivan
body: Because this groups by the date, compiler name, and compiler version, it would not average two different versions together. On the plot I think it would just show them as different datapoints in the same color on the same date and label them correspondingly.
created_at: 2025-02-26T20:39:01Z
author_association: COLLABORATOR
diff_hunk: @@ -33,9 +39,18 @@
 # Convert the 'date' column to datetime
 df_all["date"] = pd.to_datetime(df_all["date"])
 
+unique_versions = sorted(df_all["compiler_version"].unique())
path: benchmarks/scripts/plot_expval_benchmarks_over_time.py
user: login: jordandsullivan
body: Good catch, I added a print statement to check, and these are just the numbers, not including the compiler names:
`Here are unique_versions ['0.1.0', '0.1.1', '0.2.0', '0.4.0', '0.4.2', '1.2.0', '1.3.0', '1.3.1', '1.3.2', '1.3.3', '1.35.0', '1.36.0', '1.37.0', '1.39.0', '1.4.0', '1.4.1', '1.40.0', '1.5.0.']`
created_at: 2025-02-26T20:40:45Z
author_association: COLLABORATOR
diff_hunk: @@ -33,9 +39,18 @@
 # Convert the 'date' column to datetime
 df_all["date"] = pd.to_datetime(df_all["date"])
 
+unique_versions = sorted(df_all["compiler_version"].unique())
path: benchmarks/scripts/plot_expval_benchmarks_over_time.py
user: login: Misty-W
body: I'm aware these are only the numbers, but the actual filtering is done on the date each new version appears in the dataset:
```
dates = []
for version in unique_versions:
    df_versions = df_dates[df_dates["compiler_version"] == version]
    earliest_date = sorted(df_versions["date"].unique())[0]
    dates.append(earliest_date)

filtered_avg_compiled_ratio = avg_compiled_ratio[avg_compiled_ratio["date"].isin(dates)]
```
created_at: 2025-02-26T20:51:43Z
author_association: COLLABORATOR
diff_hunk: @@ -62,17 +60,27 @@ def extract_compiler_versions(header):
 color_map = {
     compiler: colormap(i) for i, compiler in enumerate(unique_compilers)
 }
+unique_versions = sorted(df_dates["compiler_version"].unique())
+dates = []
+for version in unique_versions:
+    df_versions = df_dates[df_dates["compiler_version"] == version]
+    earliest_date = sorted(df_versions["date"].unique())[0]
+    dates.append(earliest_date)
 
 fig, ax = plt.subplots(2, 1, figsize=(8, 8), sharex=False, dpi=150)
 # Rotate x labels on axes 0
 plt.setp(ax[0].xaxis.get_majorticklabels(), rotation=45)
 
 
+filtered_avg_compiled_ratio = avg_compiled_ratio[
path: benchmarks/scripts/plot_avg_benchmarks_over_time.py
user: login: Misty-W
body: That's correct @jordandsullivan .
created_at: 2025-02-26T20:52:41Z
author_association: COLLABORATOR
diff_hunk: @@ -62,17 +60,27 @@ def extract_compiler_versions(header):
 color_map = {
     compiler: colormap(i) for i, compiler in enumerate(unique_compilers)
 }
+unique_versions = sorted(df_dates["compiler_version"].unique())
+dates = []
+for version in unique_versions:
+    df_versions = df_dates[df_dates["compiler_version"] == version]
+    earliest_date = sorted(df_versions["date"].unique())[0]
+    dates.append(earliest_date)
 
 fig, ax = plt.subplots(2, 1, figsize=(8, 8), sharex=False, dpi=150)
 # Rotate x labels on axes 0
 plt.setp(ax[0].xaxis.get_majorticklabels(), rotation=45)
 
 
+filtered_avg_compiled_ratio = avg_compiled_ratio[
path: benchmarks/scripts/plot_avg_benchmarks_over_time.py
user: login: bachase
body: Got it. Thank you both.

If we had multiple datapoints, would we want to only plot the "latest version" dot for that date? Just wondering if it might get cluttered otherwise. 
created_at: 2025-02-26T21:47:18Z
author_association: COLLABORATOR
diff_hunk: @@ -33,9 +39,18 @@
 # Convert the 'date' column to datetime
 df_all["date"] = pd.to_datetime(df_all["date"])
 
+unique_versions = sorted(df_all["compiler_version"].unique())
path: benchmarks/scripts/plot_expval_benchmarks_over_time.py
user: login: bachase
body: Here's a toy example to consider (ignoring the actual data columns)
```csv
date, compiler, compiler_version
2025-01-01,cirq,1.2.0
2025-01-02,qiskit,1.2.0
2025-01-10,cirq,1.3.0
```

I think this might select only `2025-01-1` and `2025-01-10` as earliest dates, as the cirq version 1.2.0 would be earlier than the qiskit 1.2.0. Then the qiskit results for 1.2.0 wouldn't show up until much later? (In this parallel universe where their versions are close and overlapping).
created_at: 2025-02-26T21:56:49Z
author_association: COLLABORATOR
diff_hunk: @@ -116,10 +128,11 @@ def extract_compiler_versions(header):
 
         # Check if the version has changed
         if current_version != last_version_seen[compiler]:
-            text = f"{compiler}={current_version}"
+            text = f"{current_version}"
             xy = (row["date"], compiled_ratio)
             color = color_map[compiler]
-
+            renderer = fig.canvas.get_renderer()
+            previous_bboxes.append(ax[0].get_tightbbox(renderer))
path: benchmarks/scripts/plot_avg_benchmarks_over_time.py
user: login: bachase
body: Can you share more on what this line is doing (adding the current tightbbox)? Is there a need to do it in the average compile time plot too?
created_at: 2025-03-11T18:27:58Z
author_association: COLLABORATOR
diff_hunk: @@ -62,7 +80,54 @@
         marker="o",
     )
 
-# Step 9: Customize plot
+# Customize plot
+last_version_seen = {compiler: None for compiler in unique_compilers}
+
+# Find the latest version for each package
+latest_versions = summary.groupby("compiler")["compiler_version"].max()
+# Filter to keep only rows with the latest version
+avg_absolute_error_latest_versions = summary[
+    summary["compiler_version"].isin(latest_versions)
+]
+#  Find the first appearance date of each latest version
+first_appearance_dates = (
+    avg_absolute_error_latest_versions.groupby(
+        ["compiler", "compiler_version", "avg_absolute_error"]
+    )["date"]
+    .min()
+    .reset_index()
+)
path: benchmarks/scripts/plot_expval_benchmarks_over_time.py
user: login: bachase
body: Are these lines still needed?
created_at: 2025-03-11T18:32:33Z
author_association: COLLABORATOR
diff_hunk: @@ -116,10 +128,11 @@ def extract_compiler_versions(header):
 
         # Check if the version has changed
         if current_version != last_version_seen[compiler]:
-            text = f"{compiler}={current_version}"
+            text = f"{current_version}"
             xy = (row["date"], compiled_ratio)
             color = color_map[compiler]
-
+            renderer = fig.canvas.get_renderer()
+            previous_bboxes.append(ax[0].get_tightbbox(renderer))
path: benchmarks/scripts/plot_avg_benchmarks_over_time.py
user: login: Misty-W
body: It was a failed experiment to get the labels to render more nicely. I've removed it.
created_at: 2025-03-12T19:58:02Z
author_association: COLLABORATOR
diff_hunk: @@ -62,7 +80,54 @@
         marker="o",
     )
 
-# Step 9: Customize plot
+# Customize plot
+last_version_seen = {compiler: None for compiler in unique_compilers}
+
+# Find the latest version for each package
+latest_versions = summary.groupby("compiler")["compiler_version"].max()
+# Filter to keep only rows with the latest version
+avg_absolute_error_latest_versions = summary[
+    summary["compiler_version"].isin(latest_versions)
+]
+#  Find the first appearance date of each latest version
+first_appearance_dates = (
+    avg_absolute_error_latest_versions.groupby(
+        ["compiler", "compiler_version", "avg_absolute_error"]
+    )["date"]
+    .min()
+    .reset_index()
+)
path: benchmarks/scripts/plot_expval_benchmarks_over_time.py
user: login: Misty-W
body: Good catch, no longer needed and removed.
created_at: 2025-03-12T19:58:08Z
author_association: COLLABORATOR
diff_hunk: @@ -60,6 +72,14 @@ def extract_compiler_versions(header):
     .reset_index()
     .sort_values("date")
 )
+# Sort the dataframe by package, date, and version
+avg_compiled_ratio = avg_compiled_ratio.sort_values(
path: benchmarks/scripts/plot_avg_benchmarks_over_time.py
user: login: bachase
body: I think lexigraphically sorting for the `compiler_version` strings might not work correctly, e.g.

```python
>>> "0.10.0" < "0.2.0"
True
```

Google search suggests using the [packaging](https://packaging.pypa.io/en/latest/version.html#packaging.version.Version) module; surprised there isn't something in the standard library.
```python
>>> from packaging.version import parse
>>> parse("0.10.0") < parse("0.2.0")
False
```
But I think ok to treat as a separate PR/fixup.
created_at: 2025-03-12T20:29:00Z
author_association: COLLABORATOR
diff_hunk: @@ -60,6 +72,14 @@ def extract_compiler_versions(header):
     .reset_index()
     .sort_values("date")
 )
+# Sort the dataframe by package, date, and version
+avg_compiled_ratio = avg_compiled_ratio.sort_values(
path: benchmarks/scripts/plot_avg_benchmarks_over_time.py
user: login: Misty-W
body: Thanks for flagging this. The standard library does have `parse_version`,  although it is deprecated. For this PR, I decided it best to use `parse_version`, so at least we're sorting correctly, and then do a follow up PR to replace the deprecated method with the method from `packaging`.
created_at: 2025-03-13T00:00:26Z
author_association: COLLABORATOR

PR comments:
user: login: bachase
created_at: 2025-02-25T15:03:29Z
body: For the absolute error plot, does each dot still represent a new version of that software package? If so, would we want it to have the label as well?
author_association: COLLABORATOR
user: login: Misty-W
created_at: 2025-03-11T07:09:02Z
body: I changed the dataframe `groupby` method to group both by compiler and version number, so now it only plots points with dates corresponding to a new compiler version used in the benchmarks.

I've also changed the version labels so that they only show the latest version, to avoid crowding the plot with labels.

![image](https://github.com/user-attachments/assets/d7b0f793-7777-44f5-b1c9-8c1fba6e8734)

author_association: COLLABORATOR
user: login: jordandsullivan
created_at: 2025-03-11T16:07:44Z
body: Suggest keeping the versions, but removing the name of the compiler from the label, since it is already present in the legend. 
author_association: COLLABORATOR
user: login: jordandsullivan
created_at: 2025-03-11T21:13:42Z
body: 
> Have we tried an off-the-shelf tool like https://adjusttext.readthedocs.io/en/latest/index.html to manage this versus the `annotate_and_adjust` approach?
> 
There's an open issue for this: https://github.com/unitaryfund/ucc/issues/271
author_association: COLLABORATOR
user: login: Misty-W
created_at: 2025-03-12T20:09:45Z
body: I filtered the data so that there's at most one point per compiler per date. There's still a bit of overlap at the end because of the coincidence of our benchmarking cycles with new package releases.

You may notice that a couple versions are out of order, but that comes from the data files themselves, since some were run with older packages. @bachase, @jordandsullivan should we filter those out also?

![image](https://github.com/user-attachments/assets/ce487352-730f-4538-986f-2fc330535f51)

author_association: COLLABORATOR
user: login: bachase
created_at: 2025-03-12T20:23:16Z
body: > should we filter those out also?

I'm supportive, especially as it was unintended/not reflecting a meaningful change.
author_association: COLLABORATOR
user: login: Misty-W
created_at: 2025-03-13T00:02:49Z
body: After filtering out the data from March 5, we have more sensibly labeled plots:

![image](https://github.com/user-attachments/assets/5c3b9fc7-d64e-4bac-bd97-28d9f9faf63b)

author_association: COLLABORATOR