!pip install nucleus-cdk==0.5.0rc2 | tail -n2

# Surface a failed install here, rather than as a confusing ModuleNotFoundError
# in the import cell below.
import importlib.metadata as md
assert md.version("nucleus-cdk") == "0.5.0rc2", f"got {md.version('nucleus-cdk')}"
Requirement already satisfied: urllib3<3,>=1.21.1 in /opt/homebrew/Caskroom/miniforge/base/lib/python3.12/site-packages (from requests->panel>=1.0->hvplot<0.12.0,>=0.11.1->nucleus-cdk==0.5.0rc2) (2.5.0)
Requirement already satisfied: certifi>=2017.4.17 in /opt/homebrew/Caskroom/miniforge/base/lib/python3.12/site-packages (from requests->panel>=1.0->hvplot<0.12.0,>=0.11.1->nucleus-cdk==0.5.0rc2) (2025.11.12)
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

from cdk.analysis.cytosol import platereader as pr
# import platereader as pr
# Initialize plotting
pr.plot_setup()
import sys
# !ls bnext/experiments/20251106_labcraft_protein_sweep_01
data = pr.load_platereader_data("data/20251106-191540-cytation5-pure-timecourse-gfp-labcraft-protein-sweep1-biotek-cdk.txt")

platemap = pd.read_csv("data/concentration_key.csv")
# platemap.columns = ["Well", "Mg (mM)", "Ribo (uM)", "T7 (ng/ul)", "Water"]
# platemap["Name"] = "GFP " + platemap["[DNA] (nM)"].astype(str) + " nM"
platemap.rename(columns={'Well ID':"Well"}, inplace=True)
platemap = platemap.fillna(0)
# platemap.columns
sweep_cols= ['Mg_mM', 'Ribo_uM', 'T7poly_ng/ul']
for values, g in platemap.groupby(sweep_cols):
    mg, ribo, T7 = values
    name = f"Mg {mg}; R {ribo}; T7 {T7}"
    platemap.loc[g.index, "Name"] = name

platemap

data = data.merge(platemap)
platemap.head()
data.head()
Loading...
data
Loading...
pr.plot_curves(data)
<seaborn.axisgrid.FacetGrid at 0x111912f00>
<Figure size 776x500 with 1 Axes>
# pr.plot_curves(data, hue=sweep_cols[0], col=sweep_cols[2], palette="viridis")
print(sweep_cols)
['Mg_mM', 'Ribo_uM', 'T7poly_ng/ul']
sns.relplot(data, x="Time", y="Data", row=sweep_cols[0], col=sweep_cols[2], hue=sweep_cols[1], kind="line") #, hue="Name")
<seaborn.axisgrid.FacetGrid at 0x111bdaa50>
<Figure size 1599x1500 with 9 Axes>
kinetics = pr.kinetic_analysis(data, group_by=["Name"])
kinetics
/private/tmp/claude-501/-Users-sharonnewman-Documents-general-nucleus-skills/4a8d6065-9e0a-4e14-b326-0bffb6d3273b/scratchpad/cdkenv/lib/python3.12/site-packages/pandas/core/arraylike.py:399: RuntimeWarning: overflow encountered in exp
  result = getattr(ufunc, method)(*inputs, **kwargs)
/private/tmp/claude-501/-Users-sharonnewman-Documents-general-nucleus-skills/4a8d6065-9e0a-4e14-b326-0bffb6d3273b/scratchpad/cdkenv/lib/python3.12/site-packages/pandas/core/arraylike.py:399: RuntimeWarning: overflow encountered in exp
  result = getattr(ufunc, method)(*inputs, **kwargs)
/private/tmp/claude-501/-Users-sharonnewman-Documents-general-nucleus-skills/4a8d6065-9e0a-4e14-b326-0bffb6d3273b/scratchpad/cdkenv/lib/python3.12/site-packages/pandas/core/arraylike.py:399: RuntimeWarning: overflow encountered in exp
  result = getattr(ufunc, method)(*inputs, **kwargs)
PROVIDING AVERAGED KINETICS
Loading...
kinetics.columns
ss = kinetics["Steady State"]["Data"].reset_index().merge(platemap)
# ss = kinetics["Velocity"]["Max"].reset_index().merge(platemap)

# data_name = 'Max' #Data
data_name = 'Data'
pr.plot_kinetics(data, group_by=["Name"])
/private/tmp/claude-501/-Users-sharonnewman-Documents-general-nucleus-skills/4a8d6065-9e0a-4e14-b326-0bffb6d3273b/scratchpad/cdkenv/lib/python3.12/site-packages/pandas/core/arraylike.py:399: RuntimeWarning: overflow encountered in exp
  result = getattr(ufunc, method)(*inputs, **kwargs)
/private/tmp/claude-501/-Users-sharonnewman-Documents-general-nucleus-skills/4a8d6065-9e0a-4e14-b326-0bffb6d3273b/scratchpad/cdkenv/lib/python3.12/site-packages/pandas/core/arraylike.py:399: RuntimeWarning: overflow encountered in exp
  result = getattr(ufunc, method)(*inputs, **kwargs)
/private/tmp/claude-501/-Users-sharonnewman-Documents-general-nucleus-skills/4a8d6065-9e0a-4e14-b326-0bffb6d3273b/scratchpad/cdkenv/lib/python3.12/site-packages/pandas/core/arraylike.py:399: RuntimeWarning: overflow encountered in exp
  result = getattr(ufunc, method)(*inputs, **kwargs)
PROVIDING AVERAGED KINETICS
(<seaborn.axisgrid.FacetGrid at 0x112f36f00>, Velocity \ Time Data Max Name Mg 0.0; R 0.0; T7 0.0 0 days 01:25:06.384157767 391036.93 464495.78 Mg 0.0; R 0.0; T7 13.383 0 days 01:23:12.227808558 357873.19 379802.81 Mg 0.0; R 0.0; T7 6.6915 0 days 01:23:00.991286042 367973.45 427639.32 Mg 0.0; R 0.4008; T7 0.0 0 days 01:24:07.313832339 356736.31 414825.70 Mg 0.0; R 0.4008; T7 13.383 0 days 01:25:42.038596308 330913.22 334829.87 Mg 0.0; R 0.4008; T7 6.6915 0 days 01:23:42.845952033 392106.89 449012.30 Mg 0.0; R 0.6903; T7 0.0 0 days 01:29:55.710262378 340358.30 372208.22 Mg 0.0; R 0.6903; T7 13.383 0 days 01:30:56.793676442 310808.77 303268.05 Mg 0.0; R 0.6903; T7 6.6915 0 days 01:26:35.570398910 355678.24 362781.19 Mg 1.9744; R 0.0; T7 0.0 0 days 01:31:10.047957710 266026.68 265537.28 Mg 1.9744; R 0.0; T7 13.383 0 days 01:22:38.831052047 209776.90 181448.51 Mg 1.9744; R 0.0; T7 6.6915 0 days 01:29:22.965593733 234582.15 223475.76 Mg 1.9744; R 0.4008; T7 0.0 0 days 01:32:26.433102526 223002.30 199158.18 Mg 1.9744; R 0.4008; T7 13.383 0 days 01:24:06.960227581 188265.56 158092.24 Mg 1.9744; R 0.4008; T7 6.6915 0 days 01:29:32.494454971 214017.22 191450.93 Mg 1.9744; R 0.6903; T7 0.0 0 days 01:37:11.559421711 205806.38 185203.77 Mg 1.9744; R 0.6903; T7 13.383 0 days 01:33:27.475317193 146055.89 134875.18 Mg 1.9744; R 0.6903; T7 6.6915 0 days 01:35:55.723313741 179588.56 163286.65 Mg 3.9488; R 0.0; T7 0.0 0 days 01:30:38.899218105 131767.15 129933.86 Mg 3.9488; R 0.0; T7 13.383 0 days 01:29:35.896920936 102768.54 96198.01 Mg 3.9488; R 0.0; T7 6.6915 0 days 01:29:18.900008816 113198.80 109991.03 Mg 3.9488; R 0.4008; T7 0.0 0 days 01:36:19.162132231 110818.63 104481.71 Mg 3.9488; R 0.4008; T7 13.383 0 days 01:34:15.104488488 92453.17 81578.59 Mg 3.9488; R 0.4008; T7 6.6915 0 days 01:30:49.317234001 100944.78 96721.92 Mg 3.9488; R 0.6903; T7 0.0 0 days 01:44:19.124703433 81955.51 71528.78 Mg 3.9488; R 0.6903; T7 13.383 0 days 01:45:24.189728286 67605.95 59776.16 Mg 3.9488; R 0.6903; T7 6.6915 0 days 01:42:58.014040578 90240.65 78414.00 Lag \ Time Data Name Mg 0.0; R 0.0; T7 0.0 0 days 00:34:29.416211466 58273.39 Mg 0.0; R 0.0; T7 13.383 0 days 00:26:26.200437412 15446.97 Mg 0.0; R 0.0; T7 6.6915 0 days 00:31:07.936658955 36882.12 Mg 0.0; R 0.4008; T7 0.0 0 days 00:32:35.902041874 30788.26 Mg 0.0; R 0.4008; T7 13.383 0 days 00:26:29.008152804 11516.61 Mg 0.0; R 0.4008; T7 6.6915 0 days 00:31:18.592564633 33526.11 Mg 0.0; R 0.6903; T7 0.0 0 days 00:34:46.926552948 42570.22 Mg 0.0; R 0.6903; T7 13.383 0 days 00:29:02.185712247 12702.54 Mg 0.0; R 0.6903; T7 6.6915 0 days 00:27:44.533647051 15200.72 Mg 1.9744; R 0.0; T7 0.0 0 days 00:30:54.371716438 18549.69 Mg 1.9744; R 0.0; T7 13.383 0 days 00:12:12.048795450 -13817.18 Mg 1.9744; R 0.0; T7 6.6915 0 days 00:26:20.096878108 10803.28 Mg 1.9744; R 0.4008; T7 0.0 0 days 00:24:19.111957006 5599.48 Mg 1.9744; R 0.4008; T7 13.383 0 days 00:12:14.443602857 -15926.72 Mg 1.9744; R 0.4008; T7 6.6915 0 days 00:21:36.523796879 1314.50 Mg 1.9744; R 0.6903; T7 0.0 0 days 00:30:38.184571957 10962.55 Mg 1.9744; R 0.6903; T7 13.383 0 days 00:28:23.405745736 3120.04 Mg 1.9744; R 0.6903; T7 6.6915 0 days 00:30:22.672605073 8290.13 Mg 3.9488; R 0.0; T7 0.0 0 days 00:29:43.509092187 5846.39 Mg 3.9488; R 0.0; T7 13.383 0 days 00:25:27.577661245 1195.47 Mg 3.9488; R 0.0; T7 6.6915 0 days 00:27:33.175679690 2978.65 Mg 3.9488; R 0.4008; T7 0.0 0 days 00:32:40.159888568 5575.75 Mg 3.9488; R 0.4008; T7 13.383 0 days 00:26:11.535109176 645.93 Mg 3.9488; R 0.4008; T7 6.6915 0 days 00:29:05.448459917 3539.95 Mg 3.9488; R 0.6903; T7 0.0 0 days 00:35:33.186861893 4052.33 Mg 3.9488; R 0.6903; T7 13.383 0 days 00:36:49.789627331 4270.67 Mg 3.9488; R 0.6903; T7 6.6915 0 days 00:33:44.857105481 3507.00 Steady State \ Time Data Name Mg 0.0; R 0.0; T7 0.0 0 days 02:39:37.467556420 742970.17 Mg 0.0; R 0.0; T7 13.383 0 days 02:46:46.647685590 679959.06 Mg 0.0; R 0.0; T7 6.6915 0 days 02:39:24.090979379 699149.56 Mg 0.0; R 0.4008; T7 0.0 0 days 02:39:58.550519721 677798.99 Mg 0.0; R 0.4008; T7 13.383 0 days 02:52:52.879261863 628735.13 Mg 0.0; R 0.4008; T7 6.6915 0 days 02:40:51.877068735 745003.09 Mg 0.0; R 0.6903; T7 0.0 0 days 02:51:06.966125290 646680.76 Mg 0.0; R 0.6903; T7 13.383 0 days 03:02:05.511916839 590536.67 Mg 0.0; R 0.6903; T7 6.6915 0 days 02:53:14.031522829 675788.65 Mg 1.9744; R 0.0; T7 0.0 0 days 02:59:53.116987456 505450.70 Mg 1.9744; R 0.0; T7 13.383 0 days 03:06:21.582267995 398576.11 Mg 1.9744; R 0.0; T7 6.6915 0 days 03:02:12.178642696 445706.08 Mg 1.9744; R 0.4008; T7 0.0 0 days 03:12:43.866952370 423704.36 Mg 1.9744; R 0.4008; T7 13.383 0 days 03:09:55.931251162 357704.57 Mg 1.9744; R 0.4008; T7 6.6915 0 days 03:09:33.217896087 406632.71 Mg 1.9744; R 0.6903; T7 0.0 0 days 03:15:10.683704419 391032.12 Mg 1.9744; R 0.6903; T7 13.383 0 days 03:09:15.122628326 277506.19 Mg 1.9744; R 0.6903; T7 6.6915 0 days 03:12:26.037219836 341218.27 Mg 3.9488; R 0.0; T7 0.0 0 days 03:00:20.435802617 250357.58 Mg 3.9488; R 0.0; T7 13.383 0 days 03:04:01.467536549 195260.22 Mg 3.9488; R 0.0; T7 6.6915 0 days 03:00:14.539588342 215077.72 Mg 3.9488; R 0.4008; T7 0.0 0 days 03:10:01.571665405 210555.39 Mg 3.9488; R 0.4008; T7 13.383 0 days 03:14:27.014915055 175661.01 Mg 3.9488; R 0.4008; T7 6.6915 0 days 03:01:42.225029697 191795.09 Mg 3.9488; R 0.6903; T7 0.0 0 days 03:25:33.410805916 155715.47 Mg 3.9488; R 0.6903; T7 13.383 0 days 03:26:21.489744063 128451.30 Mg 3.9488; R 0.6903; T7 6.6915 0 days 03:24:52.372623242 171457.24 Fit \ params Name Mg 0.0; R 0.0; T7 0.0 [782073.8658722346, 2.375343636306762, 1.41844... Mg 0.0; R 0.0; T7 13.383 [715746.3803741209, 2.120389798812113, 1.38672... Mg 0.0; R 0.0; T7 6.6915 [735946.9077719614, 2.3319712416741716, 1.3836... Mg 0.0; R 0.4008; T7 0.0 [713472.616785697, 2.3321619476225703, 1.40203... Mg 0.0; R 0.4008; T7 13.383 [661826.4497586022, 2.0279097397860024, 1.4283... Mg 0.0; R 0.4008; T7 6.6915 [784213.7776576624, 2.2903019405603517, 1.3952... Mg 0.0; R 0.6903; T7 0.0 [680716.5903602357, 2.1819654470998326, 1.4988... Mg 0.0; R 0.6903; T7 13.383 [621617.5452810051, 1.9409725828795965, 1.5157... Mg 0.0; R 0.6903; T7 6.6915 [711356.4727441198, 2.040622517734187, 1.44321... Mg 1.9744; R 0.0; T7 0.0 [532053.3699681616, 1.9927951351190756, 1.5194... Mg 1.9744; R 0.0; T7 13.383 [419553.79744963074, 1.7154597418411308, 1.377... Mg 1.9744; R 0.0; T7 6.6915 [469164.29909062316, 1.9047340489697548, 1.489... Mg 1.9744; R 0.4008; T7 0.0 [446004.59312138916, 1.771045129517986, 1.5406... Mg 1.9744; R 0.4008; T7 13.383 [376531.1269253074, 1.6864992087448423, 1.4019... Mg 1.9744; R 0.4008; T7 6.6915 [428034.43628156517, 1.7775177326789922, 1.492... Mg 1.9744; R 0.6903; T7 0.0 [411612.76288470003, 1.806767669866755, 1.6198... Mg 1.9744; R 0.6903; T7 13.383 [292111.7806860807, 1.8444842465236158, 1.5576... Mg 1.9744; R 0.6903; T7 6.6915 [359177.12895069976, 1.8344457780391827, 1.598... Mg 3.9488; R 0.0; T7 0.0 [263534.2964317678, 1.9699816019059575, 1.5108... Mg 3.9488; R 0.0; T7 13.383 [205537.07077621744, 1.8715553549498225, 1.493... Mg 3.9488; R 0.0; T7 6.6915 [226397.5952397759, 1.9429951601790996, 1.4885... Mg 3.9488; R 0.4008; T7 0.0 [221637.2520672017, 1.8856147009081166, 1.6053... Mg 3.9488; R 0.4008; T7 13.383 [184906.33117008847, 1.76332935318427, 1.57086... Mg 3.9488; R 0.4008; T7 6.6915 [201889.56427534568, 1.9547755507089732, 1.513... Mg 3.9488; R 0.6903; T7 0.0 [163911.01667216455, 1.7452474371207776, 1.738... Mg 3.9488; R 0.6903; T7 13.383 [135211.8970598355, 1.78584639750524, 1.756719... Mg 3.9488; R 0.6903; T7 6.6915 [180481.3096179662, 1.7345681546538323, 1.7161... R^2 drift Name Mg 0.0; R 0.0; T7 0.0 1.00 5986.48 Mg 0.0; R 0.0; T7 13.383 1.00 14517.91 Mg 0.0; R 0.0; T7 6.6915 1.00 15055.08 Mg 0.0; R 0.4008; T7 0.0 1.00 10248.63 Mg 0.0; R 0.4008; T7 13.383 1.00 12056.37 Mg 0.0; R 0.4008; T7 6.6915 1.00 15223.10 Mg 0.0; R 0.6903; T7 0.0 1.00 8830.31 Mg 0.0; R 0.6903; T7 13.383 1.00 11147.12 Mg 0.0; R 0.6903; T7 6.6915 1.00 12554.64 Mg 1.9744; R 0.0; T7 0.0 1.00 8533.08 Mg 1.9744; R 0.0; T7 13.383 1.00 11364.84 Mg 1.9744; R 0.0; T7 6.6915 1.00 8501.90 Mg 1.9744; R 0.4008; T7 0.0 1.00 9000.57 Mg 1.9744; R 0.4008; T7 13.383 0.99 10684.48 Mg 1.9744; R 0.4008; T7 6.6915 1.00 9345.35 Mg 1.9744; R 0.6903; T7 0.0 1.00 7248.21 Mg 1.9744; R 0.6903; T7 13.383 1.00 5711.82 Mg 1.9744; R 0.6903; T7 6.6915 1.00 6520.42 Mg 3.9488; R 0.0; T7 0.0 1.00 4652.97 Mg 3.9488; R 0.0; T7 13.383 1.00 4166.14 Mg 3.9488; R 0.0; T7 6.6915 1.00 4317.58 Mg 3.9488; R 0.4008; T7 0.0 1.00 3817.49 Mg 3.9488; R 0.4008; T7 13.383 1.00 3832.44 Mg 3.9488; R 0.4008; T7 6.6915 1.00 3729.18 Mg 3.9488; R 0.6903; T7 0.0 1.00 2851.99 Mg 3.9488; R 0.6903; T7 13.383 1.00 3883.35 Mg 3.9488; R 0.6903; T7 6.6915 1.00 3297.77 )
<Figure size 1800x3600 with 27 Axes>
ss.head()
Loading...
ss.to_csv('steady_states.csv', index=False)
df = ss

# Average replicates
means = df.groupby(['Mg_mM', 'Ribo_uM', 'T7poly_ng/ul'], as_index=False)[data_name].mean()

# Global min/max for shared color bar
data_min = means[data_name].min()
data_max = means[data_name].max()

t7_values = means['Mg_mM'].unique()
n_plots = len(t7_values)
fig, axes = plt.subplots(1, n_plots, figsize=(7*n_plots, 6), squeeze=False)

for idx, t7 in enumerate(sorted(t7_values)):
    slice_df = means[means['Mg_mM'] == t7]
    pivot = slice_df.pivot(index='Ribo_uM', columns='T7poly_ng/ul', values=data_name)
    ax = axes[0, idx]
    hm = sns.heatmap(
        pivot, annot=True, cmap='viridis',
        vmin=data_min, vmax=data_max,  # shared color scale
        cbar=idx==n_plots-1, cbar_ax=None if idx < n_plots-1 else plt.gcf().add_axes([0.93, 0.3, 0.02, 0.4]),
        ax=ax
    )
    ax.set_title(f'Mg_mM={t7}')
    ax.set_xlabel('T7poly_ng/ul')
    ax.set_ylabel('Ribo_uM')

plt.suptitle("Vmax")
plt.tight_layout(rect=[0, 0, 0.9, 1])  # Leave space for color bar
plt.show()
/var/folders/4l/5bp8d9ks6v15tkhzpbszxxpc0000gn/T/ipykernel_21493/806804362.py:29: UserWarning: This figure includes Axes that are not compatible with tight_layout, so results might be incorrect.
  plt.tight_layout(rect=[0, 0, 0.9, 1])  # Leave space for color bar
<Figure size 2100x600 with 4 Axes>
df = ss
# Average replicates
means = df.groupby(['Mg_mM', 'Ribo_uM', 'T7poly_ng/ul'], as_index=False)[data_name].mean()

# Global min/max for shared color bar
data_min = means[data_name].min()
data_max = means[data_name].max()

t7_values = means['T7poly_ng/ul'].unique()
n_plots = len(t7_values)
fig, axes = plt.subplots(1, n_plots, figsize=(7*n_plots, 6), squeeze=False)

for idx, t7 in enumerate(sorted(t7_values)):
    slice_df = means[means['T7poly_ng/ul'] == t7]
    pivot = slice_df.pivot(index='Ribo_uM', columns='Mg_mM', values=data_name)
    ax = axes[0, idx]
    hm = sns.heatmap(
        pivot, annot=True, cmap='viridis',
        vmin=data_min, vmax=data_max,  # shared color scale
        cbar=idx==n_plots-1, cbar_ax=None if idx < n_plots-1 else plt.gcf().add_axes([0.93, 0.3, 0.02, 0.4]),
        ax=ax
    )
    ax.set_title(f'T7poly_ng/ul={t7}')
    ax.set_xlabel('Mg_mM')
    ax.set_ylabel('Ribo_uM')

plt.suptitle("Vmax")
plt.tight_layout(rect=[0, 0, 0.9, 1])  # Leave space for color bar
plt.show()
/var/folders/4l/5bp8d9ks6v15tkhzpbszxxpc0000gn/T/ipykernel_21493/2296589567.py:28: UserWarning: This figure includes Axes that are not compatible with tight_layout, so results might be incorrect.
  plt.tight_layout(rect=[0, 0, 0.9, 1])  # Leave space for color bar
<Figure size 2100x600 with 4 Axes>
# data_name = 'Max' #Data
# Average replicates
means = df.groupby(['Mg_mM', 'Ribo_uM', 'T7poly_ng/ul'], as_index=False)[data_name].mean()

# 3D scatter/Surface for each unique T7poly_ng/ul, or all together using T7poly as color:
fig = plt.figure(figsize=(10, 7))
ax = fig.add_subplot(111, projection='3d')

# Normalize T7poly for color mapping
norm = plt.Normalize(means['T7poly_ng/ul'].min(), means['T7poly_ng/ul'].max())
colors = plt.cm.viridis(norm(means['T7poly_ng/ul']))

scat = ax.scatter(means['Mg_mM'], means['Ribo_uM'], means[data_name],
                  c=colors, s=60, edgecolor='k')

# Add colorbar for T7poly_ng/ul
mappable = plt.cm.ScalarMappable(cmap='viridis', norm=norm)
mappable.set_array(means['T7poly_ng/ul'])
fig.colorbar(mappable, ax=ax, label='T7poly_ng/ul')

ax.set_xlabel('Mg_mM')
ax.set_ylabel('Ribo_uM')
ax.set_zlabel('Steady State Value')
ax.set_title('Steady State as a Function of Mg, Ribo, and T7RNAP')

plt.tight_layout()
plt.show()
<Figure size 1000x700 with 2 Axes>
# Build a continuous palette for Ribo
norm = plt.Normalize(df['Ribo_uM'].min(), df['Ribo_uM'].max())
cmap = plt.cm.viridis

g = sns.relplot(
    data=df,
    x="Mg_mM", y="Data",
    col="T7poly_ng/ul", hue="Ribo_uM",  err_style='band', errorbar='sd',
    kind="line",
    facet_kws={'sharey': True, 'sharex': True},
    hue_norm=(df['Ribo_uM'].min(), df['Ribo_uM'].max())
)
<Figure size 1599x500 with 3 Axes>
df.columns
Index(['Name', 'Data', 'Well', 'Mg_mM', 'Ribo_uM', 'T7poly_ng/ul', 'Water_--'], dtype='object')
def plot_main_effects(data, response_var):
    # Get all columns except the response variable
    factors = [col for col in data.columns if col != response_var]
    
    # Plot each factor's main effect
    for factor in factors:
        plt.figure(figsize=(6, 4))
        sns.pointplot(x=factor, y=response_var, data=data, errorbar="sd", markers="o", linestyles="-")
        plt.title(f"Main Effect of {factor} on {response_var}")
        plt.xlabel(factor)
        plt.ylabel(response_var)
        plt.show()
df["Steady State (AFU)"] = df["Data"]
plot_main_effects(df[['Steady State (AFU)', 'Mg_mM', 'Ribo_uM', 'T7poly_ng/ul']], "Steady State (AFU)")
<Figure size 600x400 with 1 Axes>
<Figure size 600x400 with 1 Axes>
<Figure size 600x400 with 1 Axes>
import math
def plot_main_effects(data, response_var):
    # Get all columns except the response variable
    factors = [col for col in data.columns if col != response_var]
    n = len(factors)

    # Determine subplot grid size
    cols = 3
    rows = math.ceil(n / cols)

    fig, axes = plt.subplots(rows, cols, figsize=(12, 4 * rows))
    axes = axes.flatten()  # flatten in case of 1 row

    for ax, factor in zip(axes, factors):
        sns.pointplot(
            x=factor,
            y=response_var,
            data=data,
            errorbar="sd",
            markers="o",
            linestyles="-",
            ax=ax
        )
        ax.set_title(f"Main Effect of {factor} on {response_var}")
        ax.set_xlabel(factor)
        ax.set_ylabel(response_var)

    # Hide any empty subplots
    for i in range(len(factors), len(axes)):
        axes[i].set_visible(False)

    plt.tight_layout()
    plt.show()

df["Steady State (AFU)"] = df["Data"]
plot_main_effects(df[['Steady State (AFU)', 'Mg_mM', 'Ribo_uM', 'T7poly_ng/ul']], "Steady State (AFU)")
<Figure size 1200x400 with 3 Axes>
import numpy as np
from itertools import combinations
def plot_all_interactions(data, response_var):
    factors = [col for col in data.columns if col != response_var]
    factor_pairs = list(combinations(factors, 2))
    n = len(factor_pairs)

    cols = 3
    rows = math.ceil(n / cols)

    fig, axes = plt.subplots(rows, cols, figsize=(12, 5 * rows))
    axes = axes.flatten()

    for ax, (factor_x, factor_hue) in zip(axes, factor_pairs):

        levels = np.sort(data[factor_hue].unique())

        # Build a continuous colormap (viridis is great for continuous data)
        cmap = sns.color_palette("viridis", as_cmap=True)

        # Normalize hue values to [0,1] for colormap mapping
        if np.issubdtype(data[factor_hue].dtype, np.number):
            norm_levels = (levels - levels.min()) / (levels.max() - levels.min() + 1e-9)
        else:
            # For categorical: evenly spaced colors
            norm_levels = np.linspace(0, 1, len(levels))

        color_map = {level: cmap(norm) for level, norm in zip(levels, norm_levels)}

        # Plot per hue level
        for level in levels:
            subset = data[data[factor_hue] == level]

            # Scatter points
            sns.scatterplot(
                data=subset,
                x=factor_x,
                y=response_var,
                ax=ax,
                color=color_map[level],
                label=f"{factor_hue}: {level}"
            )

            # CI-smoothed line, same color
            sns.lineplot(
                data=subset,
                x=factor_x,
                y=response_var,
                ax=ax,
                color=color_map[level],
                errorbar="sd"
            )

        ax.set_title(f"Interaction: {factor_x} × {factor_hue}")
        ax.set_xlabel(factor_x)
        ax.set_ylabel(response_var)

    # Hide unused axes
    for i in range(len(factor_pairs), len(axes)):
        axes[i].set_visible(False)

    plt.tight_layout()
    plt.show()

plot_all_interactions(df[['Steady State (AFU)', 'Mg_mM', 'Ribo_uM', 'T7poly_ng/ul']], "Steady State (AFU)")
<Figure size 1200x500 with 3 Axes>