import%20marimo%0A%0A__generated_with%20%3D%20%220.17.6%22%0Aapp%20%3D%20marimo.App(width%3D%22full%22)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%20Process%20plate%20counts%20to%20get%20fraction%20infectivities%20and%20fit%20curves%0A%20%20%20%20This%20notebook%20analyzes%20a%20plate%20of%20sequencing-based%20neutralization%20assays.%0A%0A%20%20%20%20The%20plots%20are%20interactive%2C%20so%20you%20can%20mouseover%20points%20for%20details%2C%20use%20the%20mouse-scroll%20to%20zoom%20and%20pan%2C%20and%20use%20interactive%20dropdowns%20at%20the%20bottom%20of%20the%20plots.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_()%3A%0A%20%20%20%20%23%20Import%20Python%20modules%0A%0A%20%20%20%20import%20io%0A%20%20%20%20import%20pickle%0A%20%20%20%20import%20sys%0A%20%20%20%20import%20warnings%0A%0A%20%20%20%20import%20altair%20as%20alt%0A%0A%20%20%20%20import%20matplotlib.style%0A%0A%20%20%20%20import%20neutcurve%0A%20%20%20%20from%20neutcurve.colorschemes%20import%20CBPALETTE%2C%20CBMARKERS%0A%20%20%20%20from%20neutcurve.marimo_utils%20import%20display_fig_marimo%0A%0A%20%20%20%20import%20numpy%0A%0A%20%20%20%20import%20pandas%20as%20pd%0A%0A%20%20%20%20import%20ruamel.yaml%20as%20yaml%0A%0A%20%20%20%20import%20marimo%20as%20mo%0A%0A%20%20%20%20_%20%3D%20alt.data_transformers.disable_max_rows()%0A%0A%20%20%20%20%23%20avoid%20clutter%20w%20RuntimeWarning%20during%20curve%20fitting%0A%20%20%20%20warnings.filterwarnings(%22ignore%22%2C%20category%3DRuntimeWarning)%0A%0A%20%20%20%20%23%20faster%20plotting%20of%20neut%20curves%0A%20%20%20%20matplotlib.style.use(%22fast%22)%0A%20%20%20%20return%20(%0A%20%20%20%20%20%20%20%20CBMARKERS%2C%0A%20%20%20%20%20%20%20%20CBPALETTE%2C%0A%20%20%20%20%20%20%20%20alt%2C%0A%20%20%20%20%20%20%20%20display_fig_marimo%2C%0A%20%20%20%20%20%20%20%20io%2C%0A%20%20%20%20%20%20%20%20mo%2C%0A%20%20%20%20%20%20%20%20neutcurve%2C%0A%20%20%20%20%20%20%20%20numpy%2C%0A%20%20%20%20%20%20%20%20pd%2C%0A%20%20%20%20%20%20%20%20pickle%2C%0A%20%20%20%20%20%20%20%20sys%2C%0A%20%20%20%20%20%20%20%20yaml%2C%0A%20%20%20%20)%0A%0A%0A%40app.cell%0Adef%20_(pickle%2C%20sys)%3A%0A%20%20%20%20%23%20Load%20context%20from%20pickled%20file.%0A%20%20%20%20%23%0A%20%20%20%20%23%20This%20cell%20supports%20multiple%20ways%20to%20provide%20context%3A%0A%20%20%20%20%23%201.%20Via%20command-line%3A%20marimo%20export%20html%20notebook.py%20--%20--context-pickle%20path%2Fto%2Fcontext.pickle%0A%20%20%20%20%23%202.%20Via%20saved%20pickle%3A%20Manually%20save%20a%20context%20pickle%20to%20test_example%2Fresults%2Fcontext_dev.pickle%0A%20%20%20%20%23%203.%20Stub%20context%3A%20If%20no%20pickle%20available%2C%20creates%20minimal%20empty%20context%20for%20exploration%0A%20%20%20%20%23%0A%20%20%20%20%23%20For%20interactive%20development%20with%20%60marimo%20edit%60%2C%20you%20can%3A%0A%20%20%20%20%23%20-%20Run%20the%20pipeline%20once%20to%20generate%20a%20real%20context%20pickle%2C%20then%20copy%20it%20to%20context_dev.pickle%0A%20%20%20%20%23%20-%20Or%20work%20with%20the%20stub%20context%20(downstream%20cells%20will%20show%20warnings%2Fempty%20data)%0A%0A%20%20%20%20import%20argparse%0A%20%20%20%20import%20os%0A%20%20%20%20import%20pathlib%0A%0A%20%20%20%20%23%20Check%20if%20context-pickle%20argument%20is%20provided%20(run%20by%20driver%20script)%0A%20%20%20%20from_cmdline%20%3D%20%22--context-pickle%22%20in%20sys.argv%0A%0A%20%20%20%20if%20from_cmdline%3A%0A%20%20%20%20%20%20%20%20%23%20Running%20via%20driver%20script%20-%20parse%20args%0A%20%20%20%20%20%20%20%20print(%22Loading%20context%20from%20command-line%20argument%22)%0A%20%20%20%20%20%20%20%20p%20%3D%20argparse.ArgumentParser()%0A%20%20%20%20%20%20%20%20p.add_argument(%22--context-pickle%22%2C%20required%3DTrue)%0A%20%20%20%20%20%20%20%20args%20%3D%20p.parse_args()%0A%20%20%20%20%20%20%20%20context_pickle_path%20%3D%20pathlib.Path(args.context_pickle)%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%23%20Running%20in%20marimo%20edit%20-%20try%20to%20use%20development%20pickle%0A%20%20%20%20%20%20%20%20print(%22Running%20in%20marimo%20edit%20mode%22)%0A%20%20%20%20%20%20%20%20%23%20If%20running%20in%20edit%20mode%2C%20make%20sure%20%60context_pickle_path%60%20below%20specifies%20a%20valid%20pickle%0A%20%20%20%20%20%20%20%20context_pickle_path%20%3D%20None%0A%20%20%20%20%20%20%20%20%23%20context_pickle_path%20%3D%20pathlib.Path(%22test_example%2Fresults%2Fplates%2Fplate11%2Fprocess_plate11_context.pickle%22)%0A%0A%20%20%20%20%23%20Load%20context%20if%20pickle%20path%20exists%20and%20is%20valid%0A%20%20%20%20if%20context_pickle_path%20and%20context_pickle_path.exists()%3A%0A%20%20%20%20%20%20%20%20print(f%22Reading%20context%20from%20%7Bcontext_pickle_path%7D%22)%0A%20%20%20%20%20%20%20%20with%20open(context_pickle_path%2C%20%22rb%22)%20as%20f_context%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20context%20%3D%20pickle.load(f_context)%0A%0A%20%20%20%20%20%20%20%20%23%20Handle%20working%20directory%0A%20%20%20%20%20%20%20%20context_workdir%20%3D%20context%5B%22workdir%22%5D%0A%20%20%20%20%20%20%20%20current_workdir%20%3D%20os.getcwd()%0A%0A%20%20%20%20%20%20%20%20if%20from_cmdline%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20Running%20via%20snakemake%20-%20verify%20workdir%20matches%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20context_workdir%20!%3D%20current_workdir%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20raise%20RuntimeError(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f%22Context%20workdir%20mismatch!%5Cn%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f%22%20%20Context%20was%20created%20in%3A%20%7Bcontext_workdir%7D%5Cn%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f%22%20%20Currently%20running%20in%3A%20%20%20%7Bcurrent_workdir%7D%5Cn%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f%22This%20should%20not%20happen%20when%20running%20via%20Snakemake.%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20print(f%22Verified%20working%20directory%3A%20%7Bcurrent_workdir%7D%22)%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20Running%20in%20marimo%20edit%20-%20change%20to%20context%20workdir%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20context_workdir%20and%20context_workdir%20!%3D%20current_workdir%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20print(f%22Changing%20directory%20from%20%7Bcurrent_workdir%7D%20to%20%7Bcontext_workdir%7D%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20os.chdir(context_workdir)%0A%20%20%20%20%20%20%20%20%20%20%20%20elif%20context_workdir%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20print(f%22Already%20in%20correct%20working%20directory%3A%20%7Bcontext_workdir%7D%22)%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%23%20Create%20a%20minimal%20stub%20context%20for%20interactive%20development%0A%20%20%20%20%20%20%20%20print(%22Creating%20minimal%20stub%20context%20that%20you%20need%20to%20complete%22)%0A%20%20%20%20%20%20%20%20context%20%3D%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22input%22%3A%20%7B%7D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22output%22%3A%20%7B%7D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22params%22%3A%20%7B%7D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22wildcards%22%3A%20%7B%7D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22threads%22%3A%201%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22resources%22%3A%20%7B%7D%2C%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20return%20(context%2C)%0A%0A%0A%40app.cell%0Adef%20_(context%2C%20io%2C%20mo%2C%20pd%2C%20yaml)%3A%0A%20%20%20%20%23%20Extract%20variables%20from%20context%20-%20raises%20KeyError%20if%20required%20keys%20missing%0A%20%20%20%20count_csvs%20%3D%20context%5B%22input%22%5D%5B%22count_csvs%22%5D%0A%20%20%20%20fate_csvs%20%3D%20context%5B%22input%22%5D%5B%22fate_csvs%22%5D%0A%20%20%20%20qc_drops_yaml%20%3D%20context%5B%22output%22%5D%5B%22qc_drops%22%5D%0A%20%20%20%20frac_infectivity_csv%20%3D%20context%5B%22output%22%5D%5B%22frac_infectivity_csv%22%5D%0A%20%20%20%20fits_csv%20%3D%20context%5B%22output%22%5D%5B%22fits_csv%22%5D%0A%20%20%20%20fits_pickle%20%3D%20context%5B%22output%22%5D%5B%22fits_pickle%22%5D%0A%20%20%20%20viral_barcodes%20%3D%20context%5B%22params%22%5D%5B%22viral_barcodes%22%5D%0A%20%20%20%20neut_standard_barcodes%20%3D%20context%5B%22params%22%5D%5B%22neut_standard_barcodes%22%5D%0A%20%20%20%20samples%20%3D%20context%5B%22params%22%5D%5B%22samples%22%5D%0A%20%20%20%20plate%20%3D%20context%5B%22wildcards%22%5D%5B%22plate%22%5D%0A%20%20%20%20plate_params%20%3D%20context%5B%22params%22%5D%5B%22plate_params%22%5D%0A%20%20%20%20curve_display_method%20%3D%20context%5B%22params%22%5D%5B%22curve_display_method%22%5D%0A%0A%20%20%20%20%23%20Show%20informative%20message%20about%20context%20mode%0A%20%20%20%20if%20not%20context%5B%22input%22%5D%3A%0A%20%20%20%20%20%20%20%20mo.output.append(%0A%20%20%20%20%20%20%20%20%20%20%20%20mo.callout(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22**%E2%9A%A0%EF%B8%8F%20Running%20in%20interactive%20mode%20with%20stub%20context**%5Cn%5Cn%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22To%20run%20with%20real%20data%3A%5Cn%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%221.%20Run%20the%20pipeline%20to%20generate%20a%20context%20pickle%5Cn%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%222.%20Copy%20it%20to%20%60test_example%2Fresults%2Fcontext_dev.pickle%60%5Cn%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%223.%20Or%20run%3A%20%60marimo%20export%20html%20notebook.py%20--%20--context-pickle%20path%2Fto%2Fcontext.pickle%60%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20kind%3D%22warn%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20)%0A%0A%20%20%20%20%23%20Process%20plate_params%20only%20if%20we%20have%20real%20data%0A%20%20%20%20if%20plate_params%3A%0A%20%20%20%20%20%20%20%20%23%20get%20thresholds%20turning%20lists%20into%20tuples%20as%20needed%0A%20%20%20%20%20%20%20%20manual_drops%20%3D%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20filter_type%3A%20%5Btuple(w)%20if%20isinstance(w%2C%20list)%20else%20w%20for%20w%20in%20filter_drops%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20(filter_type%2C%20filter_drops)%20in%20plate_params%5B%22manual_drops%22%5D.items()%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20group%20%3D%20plate_params%5B%22group%22%5D%0A%20%20%20%20%20%20%20%20qc_thresholds%20%3D%20plate_params%5B%22qc_thresholds%22%5D%0A%20%20%20%20%20%20%20%20curvefit_params%20%3D%20plate_params%5B%22curvefit_params%22%5D%0A%20%20%20%20%20%20%20%20curvefit_qc%20%3D%20plate_params%5B%22curvefit_qc%22%5D%0A%20%20%20%20%20%20%20%20if%20%22barcode_serum_replicates_ignore_curvefit_qc%22%20in%20curvefit_qc%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20curvefit_qc%5B%22barcode_serum_replicates_ignore_curvefit_qc%22%5D%20%3D%20%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20tuple(w)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20for%20w%20in%20curvefit_qc%5B%22barcode_serum_replicates_ignore_curvefit_qc%22%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%5D%0A%0A%20%20%20%20%20%20%20%20mo.output.append(mo.md(f%22Processing%20%60%7Bplate%7D%60%22))%0A%0A%20%20%20%20%20%20%20%20samples_df%20%3D%20pd.DataFrame(plate_params%5B%22samples%22%5D)%0A%20%20%20%20%20%20%20%20mo.output.append(mo.md(f%22Plate%20has%20%7Blen(samples)%7D%20samples%20(wells)%22))%0A%20%20%20%20%20%20%20%20assert%20all(%0A%20%20%20%20%20%20%20%20%20%20%20%20(len(samples_df)%20%3D%3D%20samples_df%5Bc%5D.nunique())%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20c%20in%20%5B%22well%22%2C%20%22sample%22%2C%20%22sample_noplate%22%5D%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20assert%20len(samples_df)%20%3D%3D%20len(%0A%20%20%20%20%20%20%20%20%20%20%20%20samples_df.groupby(%5B%22serum_replicate%22%2C%20%22dilution_factor%22%5D)%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20assert%20len(samples)%20%3D%3D%20len(count_csvs)%20%3D%3D%20len(fate_csvs)%20%3D%3D%20len(samples_df)%0A%0A%20%20%20%20%20%20%20%20for%20d%2C%20key%2C%20title%20in%20%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20(manual_drops%2C%20%22manual_drops%22%2C%20%22Data%20manually%20specified%20to%20drop%3A%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20(qc_thresholds%2C%20%22qc_thresholds%22%2C%20%22QC%20thresholds%20applied%20to%20data%3A%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20(curvefit_params%2C%20%22curvefit_params%22%2C%20%22Curve-fitting%20parameters%3A%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20(curvefit_qc%2C%20%22curvefit_qc%22%2C%20%22Curve-fitting%20QC%3A%22)%2C%0A%20%20%20%20%20%20%20%20%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20mo.output.append(mo.md(f%22%7Btitle%7D%22))%0A%20%20%20%20%20%20%20%20%20%20%20%20yaml_buffer_params%20%3D%20io.StringIO()%0A%20%20%20%20%20%20%20%20%20%20%20%20yaml.YAML(typ%3D%22rt%22).dump(%7Bkey%3A%20d%7D%2C%20stream%3Dyaml_buffer_params)%0A%20%20%20%20%20%20%20%20%20%20%20%20mo.output.append(mo.md(f%22%60%60%60yaml%5Cn%7Byaml_buffer_params.getvalue()%7D%60%60%60%22))%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%23%20Stub%20context%20-%20initialize%20with%20empty%20values%0A%20%20%20%20%20%20%20%20manual_drops%20%3D%20%7B%7D%0A%20%20%20%20%20%20%20%20group%20%3D%20%22unknown%22%0A%20%20%20%20%20%20%20%20qc_thresholds%20%3D%20%7B%7D%0A%20%20%20%20%20%20%20%20curvefit_params%20%3D%20%7B%7D%0A%20%20%20%20%20%20%20%20curvefit_qc%20%3D%20%7B%7D%0A%20%20%20%20%20%20%20%20samples_df%20%3D%20pd.DataFrame()%0A%20%20%20%20return%20(%0A%20%20%20%20%20%20%20%20count_csvs%2C%0A%20%20%20%20%20%20%20%20curve_display_method%2C%0A%20%20%20%20%20%20%20%20curvefit_params%2C%0A%20%20%20%20%20%20%20%20curvefit_qc%2C%0A%20%20%20%20%20%20%20%20fate_csvs%2C%0A%20%20%20%20%20%20%20%20fits_csv%2C%0A%20%20%20%20%20%20%20%20fits_pickle%2C%0A%20%20%20%20%20%20%20%20frac_infectivity_csv%2C%0A%20%20%20%20%20%20%20%20group%2C%0A%20%20%20%20%20%20%20%20manual_drops%2C%0A%20%20%20%20%20%20%20%20neut_standard_barcodes%2C%0A%20%20%20%20%20%20%20%20plate%2C%0A%20%20%20%20%20%20%20%20qc_drops_yaml%2C%0A%20%20%20%20%20%20%20%20qc_thresholds%2C%0A%20%20%20%20%20%20%20%20samples%2C%0A%20%20%20%20%20%20%20%20samples_df%2C%0A%20%20%20%20%20%20%20%20viral_barcodes%2C%0A%20%20%20%20)%0A%0A%0A%40app.cell%0Adef%20_(manual_drops)%3A%0A%20%20%20%20%23%20Set%20up%20dictionary%20to%20keep%20track%20of%20wells%2C%20barcodes%2C%20well-barcodes%2C%20and%20serum-replicates%20that%20are%20dropped%3A%0A%20%20%20%20qc_drops%20%3D%20%7B%0A%20%20%20%20%20%20%20%20%22wells%22%3A%20%7B%7D%2C%0A%20%20%20%20%20%20%20%20%22barcodes%22%3A%20%7B%7D%2C%0A%20%20%20%20%20%20%20%20%22barcode_wells%22%3A%20%7B%7D%2C%0A%20%20%20%20%20%20%20%20%22barcode_serum_replicates%22%3A%20%7B%7D%2C%0A%20%20%20%20%20%20%20%20%22serum_replicates%22%3A%20%7B%7D%2C%0A%20%20%20%20%7D%0A%0A%20%20%20%20assert%20set(manual_drops).issubset(%0A%20%20%20%20%20%20%20%20qc_drops%0A%20%20%20%20)%2C%20f%22%7Bmanual_drops.keys()%3D%7D%2C%20%7Bqc_drops.keys()%7D%22%0A%20%20%20%20return%20(qc_drops%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20Statistics%20on%20barcode-parsing%20for%20each%20sample%0A%20%20%20%20Make%20interactive%20chart%20of%20the%20%22fates%22%20of%20the%20sequencing%20reads%20parsed%20for%20each%20sample%20on%20the%20plate.%0A%0A%20%20%20%20If%20most%20sequencing%20reads%20are%20not%20%22valid%20barcodes%22%2C%20this%20could%20potentially%20indicate%20some%20problem%20in%20the%20sequencing%20or%20barcode%20set%20you%20are%20parsing.%0A%0A%20%20%20%20Potential%20fates%20are%3A%0A%20%20%20%20%20-%20*valid%20barcode*%3A%20barcode%20that%20matches%20a%20known%20virus%20or%20neutralization%20standard%2C%20we%20hope%20most%20reads%20are%20this.%0A%20%20%20%20%20-%20*invalid%20barcode*%3A%20a%20barcode%20with%20proper%20flanking%20sequences%2C%20but%20does%20not%20match%20a%20known%20virus%20or%20neutralization%20standard.%20If%20you%20%20have%20a%20lot%20of%20reads%20of%20this%20type%2C%20it%20is%20probably%20a%20good%20idea%20to%20look%20at%20the%20invalid%20barcode%20CSVs%20(in%20the%20%60.%2Fresults%2Fbarcode_invalid%2F%60%20subdirectory%20created%20by%20the%20pipeline)%20to%20see%20what%20these%20invalid%20barcodes%20are.%0A%20%20%20%20%20-%20*unparseable%20barcode*%3A%20could%20not%20parse%20a%20barcode%20from%20this%20read%20as%20there%20was%20not%20a%20sequence%20of%20the%20correct%20length%20with%20the%20appropriate%20flanking%20sequence.%0A%20%20%20%20%20-%20*invalid%20outer%20flank*%3A%20if%20using%20an%20outer%20upstream%20or%20downstream%20region%20(%60upstream2%60%20or%20%60downstream2%60%20for%20the%20%5Billuminabarcodeparser%5D(https%3A%2F%2Fjbloomlab.github.io%2Fdms_variants%2Fdms_variants.illuminabarcodeparser.html))%2C%20reads%20that%20are%20otherwise%20valid%20except%20for%20this%20outer%20flank.%20Typically%20you%20would%20be%20using%20%60upstream2%60%20if%20you%20have%20a%20plate%20index%20embedded%20in%20your%20primer%2C%20and%20reads%20with%20this%20classification%20correspond%20to%20a%20different%20index%20than%20the%20one%20for%20this%20plate.%0A%20%20%20%20%20-%20*low%20quality%20barcode*%3A%20low-quality%20or%20%60N%60%20nucleotides%20in%20barcode%2C%20could%20indicate%20problem%20with%20sequencing.%0A%20%20%20%20%20-%20*failed%20chastity%20filter*%3A%20reads%20that%20failed%20the%20Illumina%20chastity%20filter%2C%20if%20these%20are%20reported%20in%20the%20FASTQ%20(they%20may%20not%20be).%0A%0A%20%20%20%20Also%2C%20if%20the%20number%20of%20reads%20per%20sample%20is%20very%20uneven%2C%20that%20could%20indicate%20that%20you%20did%20not%20do%20a%20good%20job%20of%20balancing%20the%20different%20samples%20in%20the%20Illumina%20sequencing.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(alt%2C%20fate_csvs%2C%20pd%2C%20plate%2C%20samples%2C%20samples_df)%3A%0A%20%20%20%20fates%20%3D%20(%0A%20%20%20%20%20%20%20%20pd.concat(%5Bpd.read_csv(f).assign(sample%3Ds)%20for%20f%2C%20s%20in%20zip(fate_csvs%2C%20samples)%5D)%0A%20%20%20%20%20%20%20%20.merge(samples_df%2C%20validate%3D%22many_to_one%22%2C%20on%3D%22sample%22)%0A%20%20%20%20%20%20%20%20.assign(%0A%20%20%20%20%20%20%20%20%20%20%20%20fate_counts%3Dlambda%20x%3A%20x.groupby(%22fate%22)%5B%22count%22%5D.transform(%22sum%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20sample_well%3Dlambda%20x%3A%20x%5B%22sample_noplate%22%5D%20%2B%20%22%20(%22%20%2B%20x%5B%22well%22%5D%20%2B%20%22)%22%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20.query(%22fate_counts%20%3E%200%22)%5B%20%20%23%20only%20keep%20fates%20with%20at%20least%20one%20count%0A%20%20%20%20%20%20%20%20%20%20%20%20%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22fate%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22count%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22well%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22serum_replicate%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22sample_well%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22dilution_factor%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%5D%0A%20%20%20%20%20%20%20%20%5D%0A%20%20%20%20)%0A%0A%20%20%20%20assert%20len(fates)%20%3D%3D%20len(fates.drop_duplicates())%0A%0A%20%20%20%20serum_replicates%20%3D%20sorted(fates%5B%22serum_replicate%22%5D.unique())%0A%20%20%20%20sample_wells%20%3D%20list(%0A%20%20%20%20%20%20%20%20fates.sort_values(%5B%22serum_replicate%22%2C%20%22dilution_factor%22%5D)%5B%22sample_well%22%5D%0A%20%20%20%20)%0A%0A%20%20%20%20serum_selection%20%3D%20alt.selection_point(%0A%20%20%20%20%20%20%20%20fields%3D%5B%22serum_replicate%22%5D%2C%0A%20%20%20%20%20%20%20%20bind%3Dalt.binding_select(%0A%20%20%20%20%20%20%20%20%20%20%20%20options%3D%5BNone%5D%20%2B%20serum_replicates%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20labels%3D%5B%22all%22%5D%20%2B%20serum_replicates%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20name%3D%22serum%22%2C%0A%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20)%0A%0A%20%20%20%20fates_chart%20%3D%20(%0A%20%20%20%20%20%20%20%20alt.Chart(fates)%0A%20%20%20%20%20%20%20%20.add_params(serum_selection)%0A%20%20%20%20%20%20%20%20.transform_filter(serum_selection)%0A%20%20%20%20%20%20%20%20.encode(%0A%20%20%20%20%20%20%20%20%20%20%20%20alt.X(%22count%22%2C%20scale%3Dalt.Scale(nice%3DFalse%2C%20padding%3D3))%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20alt.Y(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22sample_well%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20title%3DNone%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20sort%3Dsample_wells%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20alt.Color(%22fate%22%2C%20sort%3Dsorted(fates%5B%22fate%22%5D.unique()%2C%20reverse%3DTrue))%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20alt.Order(%22fate%22%2C%20sort%3D%22descending%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20tooltip%3Dfates.columns.tolist()%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20.mark_bar(height%3D%7B%22band%22%3A%200.85%7D)%0A%20%20%20%20%20%20%20%20.properties(%0A%20%20%20%20%20%20%20%20%20%20%20%20height%3Dalt.Step(10)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20width%3D200%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20title%3Df%22Barcode%20parsing%20for%20%7Bplate%7D%22%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20.configure_axis(grid%3DFalse)%0A%20%20%20%20)%0A%0A%20%20%20%20fates_chart%0A%20%20%20%20return%20sample_wells%2C%20serum_replicates%2C%20serum_selection%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20Read%20barcode%20counts%20and%20apply%20manually%20specified%20drops%0A%20%20%20%20Read%20the%20counts%20per%20barcode%2C%20then%20apply%20any%20manually%20specified%20drops.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(%0A%20%20%20%20count_csvs%2C%0A%20%20%20%20neut_standard_barcodes%2C%0A%20%20%20%20pd%2C%0A%20%20%20%20sample_wells%2C%0A%20%20%20%20samples%2C%0A%20%20%20%20samples_df%2C%0A%20%20%20%20serum_replicates%2C%0A%20%20%20%20viral_barcodes%2C%0A)%3A%0A%20%20%20%20%23%20get%20barcode%20counts%0A%20%20%20%20counts%20%3D%20(%0A%20%20%20%20%20%20%20%20pd.concat(%0A%20%20%20%20%20%20%20%20%20%20%20%20%5Bpd.read_csv(c).assign(sample%3Ds)%20for%20c%2C%20s%20in%20zip(count_csvs%2C%20samples)%5D%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20.merge(samples_df%2C%20validate%3D%22many_to_one%22%2C%20on%3D%22sample%22)%0A%20%20%20%20%20%20%20%20.drop(columns%3D%5B%22replicate%22%2C%20%22plate%22%2C%20%22fastq%22%5D)%0A%20%20%20%20%20%20%20%20.assign(sample_well%3Dlambda%20x%3A%20x%5B%22sample_noplate%22%5D%20%2B%20%22%20(%22%20%2B%20x%5B%22well%22%5D%20%2B%20%22)%22)%0A%20%20%20%20)%0A%0A%20%20%20%20%23%20classify%20barcodes%20as%20viral%20or%20neut%20standard%0A%20%20%20%20barcode_class%20%3D%20pd.concat(%0A%20%20%20%20%20%20%20%20%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20pd.DataFrame(viral_barcodes).assign(neut_standard%3DFalse)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20pd.DataFrame(neut_standard_barcodes).assign(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20neut_standard%3DTrue%2C%20strain%3Dpd.NA%0A%20%20%20%20%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20%5D%2C%0A%20%20%20%20%20%20%20%20ignore_index%3DTrue%2C%0A%20%20%20%20)%0A%0A%20%20%20%20%23%20merge%20counts%20and%20classification%20of%20barcodes%0A%20%20%20%20assert%20set(counts%5B%22barcode%22%5D)%20%3D%3D%20set(barcode_class%5B%22barcode%22%5D)%0A%20%20%20%20counts%20%3D%20counts.merge(barcode_class%2C%20on%3D%22barcode%22%2C%20validate%3D%22many_to_one%22)%0A%20%20%20%20assert%20set(sample_wells)%20%3D%3D%20set(counts%5B%22sample_well%22%5D)%0A%20%20%20%20assert%20set(serum_replicates)%20%3D%3D%20set(counts%5B%22serum_replicate%22%5D)%0A%20%20%20%20return%20(counts%2C)%0A%0A%0A%40app.cell%0Adef%20_(counts%2C%20manual_drops%2C%20mo%2C%20qc_drops)%3A%0A%20%20%20%20counts_qc_1%20%3D%20counts.copy()%0A%20%20%20%20for%20filter_type%2C%20filter_drops%20in%20manual_drops.items()%3A%0A%20%20%20%20%20%20%20%20mo.output.append(%0A%20%20%20%20%20%20%20%20%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f%22Dropping%20%7Blen(filter_drops)%7D%20%7Bfilter_type%7D%20specified%20in%20manual_drops%22%0A%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20assert%20filter_type%20in%20qc_drops%0A%20%20%20%20%20%20%20%20qc_drops%5Bfilter_type%5D.update(%0A%20%20%20%20%20%20%20%20%20%20%20%20%7Bw%3A%20%22manual_drop%22%20for%20w%20in%20filter_drops%20if%20not%20isinstance(w%2C%20list)%7D%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20if%20filter_type%20%3D%3D%20%22barcode_wells%22%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20counts_qc_1%20%3D%20counts_qc_1%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20~counts.assign(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20barcode_well%3Dlambda%20x%3A%20x.apply(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20lambda%20r%3A%20(r%5B%22barcode%22%5D%2C%20r%5B%22well%22%5D)%2C%20axis%3D1%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%5B%22barcode_well%22%5D.isin(qc_drops%5Bfilter_type%5D)%0A%20%20%20%20%20%20%20%20%20%20%20%20%5D%0A%20%20%20%20%20%20%20%20elif%20filter_type%20%3D%3D%20%22barcode_serum_replicates%22%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20counts_qc_1%20%3D%20counts_qc_1%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20~counts_qc_1.assign(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20barcode_serum_replicate%3Dlambda%20x%3A%20x.apply(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20lambda%20r%3A%20(r%5B%22barcode%22%5D%2C%20r%5B%22serum_replicate%22%5D)%2C%20axis%3D1%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%5B%22barcode_serum_replicate%22%5D.isin(qc_drops%5Bfilter_type%5D)%0A%20%20%20%20%20%20%20%20%20%20%20%20%5D%0A%20%20%20%20%20%20%20%20elif%20filter_type%20%3D%3D%20%22wells%22%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20counts_qc_1%20%3D%20counts_qc_1%5B~counts_qc_1%5B%22well%22%5D.isin(qc_drops%5Bfilter_type%5D)%5D%0A%20%20%20%20%20%20%20%20elif%20filter_type%20%3D%3D%20%22barcodes%22%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20counts_qc_1%20%3D%20counts_qc_1%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20~counts_qc_1%5B%22barcode%22%5D.isin(qc_drops%5Bfilter_type%5D)%0A%20%20%20%20%20%20%20%20%20%20%20%20%5D%0A%20%20%20%20%20%20%20%20elif%20filter_type%20%3D%3D%20%22serum_replicates%22%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20counts_qc_1%20%3D%20counts_qc_1%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20~counts_qc_1%5B%22serum_replicate%22%5D.isin(qc_drops%5Bfilter_type%5D)%0A%20%20%20%20%20%20%20%20%20%20%20%20%5D%0A%20%20%20%20%20%20%20%20elif%20filter_type%20%3D%3D%20%22barcode_serum_replicates%22%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20counts_qc_1%20%3D%20counts_qc_1%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20~counts_qc_1%5B%22barcode_serum_replicate%22%5D.isin(qc_drops%5Bfilter_type%5D)%0A%20%20%20%20%20%20%20%20%20%20%20%20%5D%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20assert%20filter_type%20in%20set(counts_qc_1.columns)%0A%20%20%20%20%20%20%20%20%20%20%20%20counts_qc_1%20%3D%20counts_qc_1%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20~counts_qc_1%5Bfilter_type%5D.isin(qc_drops%5Bfilter_type%5D)%0A%20%20%20%20%20%20%20%20%20%20%20%20%5D%0A%0A%20%20%20%20if%20not%20manual_drops%3A%0A%20%20%20%20%20%20%20%20mo.output.append(mo.md(%22No%20drops%20specified%20in%20manual_drops%22))%0A%20%20%20%20return%20(counts_qc_1%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20Average%20counts%20per%20barcode%20in%20each%20well%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20Plot%20average%20counts%20per%20barcode.%0A%20%20%20%20If%20a%20sample%20has%20inadequate%20barcode%20counts%2C%20it%20may%20not%20have%20good%20enough%20statistics%20for%20accurate%20analysis%2C%20and%20a%20QC-threshold%20is%20applied%3A%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(%0A%20%20%20%20alt%2C%0A%20%20%20%20counts_qc_1%2C%0A%20%20%20%20mo%2C%0A%20%20%20%20pd%2C%0A%20%20%20%20plate%2C%0A%20%20%20%20qc_drops%2C%0A%20%20%20%20qc_thresholds%2C%0A%20%20%20%20sample_wells%2C%0A%20%20%20%20serum_selection%2C%0A)%3A%0A%20%20%20%20%23%20Compute%20average%20barcode%20counts%20per%20well%0A%20%20%20%20avg_barcode_counts%20%3D%20(%0A%20%20%20%20%20%20%20%20counts_qc_1.groupby(%0A%20%20%20%20%20%20%20%20%20%20%20%20%5B%22well%22%2C%20%22serum_replicate%22%2C%20%22sample_well%22%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20dropna%3DFalse%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20as_index%3DFalse%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20.aggregate(avg_count%3Dpd.NamedAgg(%22count%22%2C%20%22mean%22))%0A%20%20%20%20%20%20%20%20.assign(%0A%20%20%20%20%20%20%20%20%20%20%20%20fails_qc%3Dlambda%20x%3A%20(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20x%5B%22avg_count%22%5D%20%3C%20qc_thresholds%5B%22avg_barcode_counts_per_well%22%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20)%0A%0A%20%20%20%20%23%20Create%20chart%0A%20%20%20%20avg_barcode_counts_chart%20%3D%20(%0A%20%20%20%20%20%20%20%20alt.Chart(avg_barcode_counts)%0A%20%20%20%20%20%20%20%20.add_params(serum_selection)%0A%20%20%20%20%20%20%20%20.transform_filter(serum_selection)%0A%20%20%20%20%20%20%20%20.encode(%0A%20%20%20%20%20%20%20%20%20%20%20%20alt.X(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22avg_count%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20title%3D%22average%20barcode%20counts%20per%20well%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20scale%3Dalt.Scale(nice%3DFalse%2C%20padding%3D3)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20alt.Y(%22sample_well%22%2C%20sort%3Dsample_wells)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20alt.Color(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22fails_qc%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20title%3Df%22fails%20qc_thresholds%5B'avg_barcode_counts_per_well'%5D%3D%7Bqc_thresholds%5B'avg_barcode_counts_per_well'%5D!r%7D%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20legend%3Dalt.Legend(titleLimit%3D500)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20tooltip%3D%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20alt.Tooltip(c%2C%20format%3D%22.3g%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20avg_barcode_counts%5Bc%5D.dtype%20%3D%3D%20float%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20else%20c%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20for%20c%20in%20avg_barcode_counts.columns%0A%20%20%20%20%20%20%20%20%20%20%20%20%5D%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20.mark_bar(height%3D%7B%22band%22%3A%200.85%7D)%0A%20%20%20%20%20%20%20%20.properties(%0A%20%20%20%20%20%20%20%20%20%20%20%20height%3Dalt.Step(10)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20width%3D250%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20title%3Df%22Average%20barcode%20counts%20per%20well%20for%20%7Bplate%7D%22%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20.configure_axis(grid%3DFalse)%0A%20%20%20%20)%0A%0A%20%20%20%20%23%20Display%20the%20chart%0A%20%20%20%20mo.output.append(avg_barcode_counts_chart)%0A%0A%20%20%20%20%23%20Drop%20wells%20failing%20QC%0A%20%20%20%20avg_barcode_counts_per_well_drops%20%3D%20list(%0A%20%20%20%20%20%20%20%20avg_barcode_counts.query(%22fails_qc%22)%5B%22well%22%5D%0A%20%20%20%20)%0A%20%20%20%20mo.output.append(%0A%20%20%20%20%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22Dropping%20%7Blen(avg_barcode_counts_per_well_drops)%7D%20wells%20for%20failing%20%22%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22%60qc_thresholds%5B'avg_barcode_counts_per_well'%5D%3D%7Bqc_thresholds%5B'avg_barcode_counts_per_well'%5D!r%7D%60%3A%20%22%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22%7Bavg_barcode_counts_per_well_drops%7D%22%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20)%0A%20%20%20%20qc_drops%5B%22wells%22%5D.update(%0A%20%20%20%20%20%20%20%20%7Bw%3A%20%22avg_barcode_counts_per_well%22%20for%20w%20in%20avg_barcode_counts_per_well_drops%7D%0A%20%20%20%20)%0A%20%20%20%20counts_qc_2%20%3D%20counts_qc_1%5B~counts_qc_1%5B%22well%22%5D.isin(qc_drops%5B%22wells%22%5D)%5D%0A%20%20%20%20return%20(counts_qc_2%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20Fraction%20of%20counts%20from%20neutralization%20standard%0A%20%20%20%20Determine%20the%20fraction%20of%20counts%20from%20the%20neutralization%20standard%20in%20each%20sample%2C%20and%20make%20sure%20this%20fraction%20passess%20the%20QC%20threshold.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(%0A%20%20%20%20alt%2C%0A%20%20%20%20counts_qc_2%2C%0A%20%20%20%20pd%2C%0A%20%20%20%20plate%2C%0A%20%20%20%20qc_thresholds%2C%0A%20%20%20%20sample_wells%2C%0A%20%20%20%20serum_selection%2C%0A)%3A%0A%20%20%20%20%23%20Compute%20neutralization%20standard%20fractions%20per%20well%0A%20%20%20%20neut_standard_fracs%20%3D%20(%0A%20%20%20%20%20%20%20%20counts_qc_2.assign(%0A%20%20%20%20%20%20%20%20%20%20%20%20neut_standard_count%3Dlambda%20x%3A%20x%5B%22count%22%5D%20*%20x%5B%22neut_standard%22%5D.astype(int)%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20.groupby(%0A%20%20%20%20%20%20%20%20%20%20%20%20%5B%22well%22%2C%20%22serum_replicate%22%2C%20%22sample_well%22%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20dropna%3DFalse%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20as_index%3DFalse%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20.aggregate(%0A%20%20%20%20%20%20%20%20%20%20%20%20total_count%3Dpd.NamedAgg(%22count%22%2C%20%22sum%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20neut_standard_count%3Dpd.NamedAgg(%22neut_standard_count%22%2C%20%22sum%22)%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20.assign(%0A%20%20%20%20%20%20%20%20%20%20%20%20neut_standard_frac%3Dlambda%20x%3A%20x%5B%22neut_standard_count%22%5D%20%2F%20x%5B%22total_count%22%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20fails_qc%3Dlambda%20x%3A%20(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20x%5B%22neut_standard_frac%22%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%3C%20qc_thresholds%5B%22min_neut_standard_frac_per_well%22%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20)%0A%0A%20%20%20%20%23%20Create%20chart%0A%20%20%20%20neut_standard_fracs_chart%20%3D%20(%0A%20%20%20%20%20%20%20%20alt.Chart(neut_standard_fracs)%0A%20%20%20%20%20%20%20%20.add_params(serum_selection)%0A%20%20%20%20%20%20%20%20.transform_filter(serum_selection)%0A%20%20%20%20%20%20%20%20.encode(%0A%20%20%20%20%20%20%20%20%20%20%20%20alt.X(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22neut_standard_frac%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20title%3D%22frac%20counts%20from%20neutralization%20standard%20per%20well%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20scale%3Dalt.Scale(nice%3DFalse%2C%20padding%3D3)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20alt.Y(%22sample_well%22%2C%20sort%3Dsample_wells)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20alt.Color(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22fails_qc%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20title%3Df%22fails%20qc_thresholds%5B'min_neut_standard_frac_per_well'%5D%3D%7Bqc_thresholds%5B'min_neut_standard_frac_per_well'%5D!r%7D%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20legend%3Dalt.Legend(titleLimit%3D500)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20tooltip%3D%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20alt.Tooltip(c%2C%20format%3D%22.3g%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20neut_standard_fracs%5Bc%5D.dtype%20%3D%3D%20float%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20else%20c%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20for%20c%20in%20neut_standard_fracs.columns%0A%20%20%20%20%20%20%20%20%20%20%20%20%5D%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20.mark_bar(height%3D%7B%22band%22%3A%200.85%7D)%0A%20%20%20%20%20%20%20%20.properties(%0A%20%20%20%20%20%20%20%20%20%20%20%20height%3Dalt.Step(10)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20width%3D250%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20title%3Df%22Neutralization-standard%20fracs%20per%20well%20for%20%7Bplate%7D%22%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20.configure_axis(grid%3DFalse)%0A%20%20%20%20%20%20%20%20.configure_legend(titleLimit%3D1000)%0A%20%20%20%20)%0A%0A%20%20%20%20%23%20Display%20the%20chart%0A%20%20%20%20neut_standard_fracs_chart%0A%20%20%20%20return%20(neut_standard_fracs%2C)%0A%0A%0A%40app.cell%0Adef%20_(counts_qc_2%2C%20mo%2C%20neut_standard_fracs%2C%20qc_drops%2C%20qc_thresholds)%3A%0A%20%20%20%20%23%20drop%20wells%20failing%20QC%0A%20%20%20%20min_neut_standard_frac_per_well_drops%20%3D%20list(%0A%20%20%20%20%20%20%20%20neut_standard_fracs.query(%22fails_qc%22)%5B%22well%22%5D%0A%20%20%20%20)%0A%20%20%20%20mo.output.append(%0A%20%20%20%20%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22Dropping%20%7Blen(min_neut_standard_frac_per_well_drops)%7D%20wells%20for%20failing%20%60qc_thresholds%5B'min_neut_standard_frac_per_well'%5D%3D%7Bqc_thresholds%5B'min_neut_standard_frac_per_well'%5D!r%7D%60%3A%20%7Bmin_neut_standard_frac_per_well_drops%7D%22%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20)%0A%20%20%20%20qc_drops%5B%22wells%22%5D.update(%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20w%3A%20%22min_neut_standard_frac_per_well%22%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20w%20in%20min_neut_standard_frac_per_well_drops%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20)%0A%20%20%20%20counts_qc_3%20%3D%20counts_qc_2%5B~counts_qc_2%5B%22well%22%5D.isin(qc_drops%5B%22wells%22%5D)%5D%0A%20%20%20%20return%20(counts_qc_3%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20Consistency%20and%20minimum%20fractions%20for%20barcodes%0A%20%20%20%20We%20examine%20the%20fraction%20of%20counts%20attributable%20to%20each%20barcode.%20We%20do%20this%20splitting%20the%20data%20two%20ways%3A%0A%0A%20%20%20%20%201.%20Looking%20at%20all%20viral%20(but%20not%20neut-standard)%20barcodes%20only%20for%20the%20no-serum%20samples%20(wells).%0A%0A%20%20%20%20%202.%20Looking%20at%20just%20the%20neut-standard%20barcodes%20for%20all%20samples%20(wells).%0A%0A%20%20%20%20The%20reason%20is%20that%20if%20the%20experiment%20is%20set%20up%20perfectly%2C%20these%20fractions%20should%20be%20the%20same%20across%20all%20samples%20for%20each%20barcode.%0A%20%20%20%20(We%20do%20not%20expect%20viral%20barcodes%20to%20have%20consistent%20fractions%20across%20no-serum%20samples%20as%20they%20will%20be%20neutralized%20differently%20depending%20on%20strain).%0A%0A%20%20%20%20We%20plot%20these%20fractions%20in%20interactive%20plots%20(you%20can%20mouseover%20points%20and%20zoom)%20so%20you%20can%20identify%20barcodes%20that%20fail%20the%20expected%20consistency%20QC%20thresholds.%0A%0A%20%20%20%20We%20also%20make%20sure%20the%20barcodes%20meet%20specified%20QC%20minimum%20thresholds%20for%20all%20samples%2C%20and%20flag%20any%20that%20do%20not.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(%0A%20%20%20%20alt%2C%0A%20%20%20%20counts_qc_3%2C%0A%20%20%20%20mo%2C%0A%20%20%20%20numpy%2C%0A%20%20%20%20pd%2C%0A%20%20%20%20plate%2C%0A%20%20%20%20qc_drops%2C%0A%20%20%20%20qc_thresholds%2C%0A%20%20%20%20sample_wells%2C%0A)%3A%0A%20%20%20%20%23%20Create%20barcode%20selection%20parameter%20for%20mouseover%20highlighting%0A%20%20%20%20barcode_selection%20%3D%20alt.selection_point(%0A%20%20%20%20%20%20%20%20fields%3D%5B%22barcode%22%5D%2C%0A%20%20%20%20%20%20%20%20on%3D%22mouseover%22%2C%0A%20%20%20%20%20%20%20%20empty%3DFalse%2C%0A%20%20%20%20)%0A%0A%20%20%20%20%23%20Analyze%20barcode%20evenness%20for%20two%20groups%3A%20neut-standard%20barcodes%20and%20viral%20barcodes%0A%20%20%20%20counts_qc_4%20%3D%20counts_qc_3.copy()%0A%0A%20%20%20%20for%20is_neut_standard%2C%20df%20in%20counts_qc_3.groupby(%22neut_standard%22)%3A%0A%20%20%20%20%20%20%20%20%23%20Determine%20which%20QC%20to%20apply%20based%20on%20barcode%20type%0A%20%20%20%20%20%20%20%20if%20is_neut_standard%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20mo.output.append(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f%22%7B'%3D'%20*%2089%7D%5Cn%5CnAnalyzing%20neut-standard%20barcodes%20from%20all%20samples%20(wells)%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20qc_name%20%3D%20%22per_neut_standard_barcode_filters%22%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20mo.output.append(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f%22%7B'%3D'%20*%2089%7D%5Cn%5CnAnalyzing%20all%20barcodes%20from%20no-serum%20samples%20(wells)%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20qc_name%20%3D%20%22no_serum_per_viral_barcode_filters%22%0A%20%20%20%20%20%20%20%20%20%20%20%20df%20%3D%20df.query(%22serum%20%3D%3D%20'none'%22)%0A%0A%20%20%20%20%20%20%20%20%23%20Compute%20barcode%20fractions%20and%20fold%20changes%0A%20%20%20%20%20%20%20%20df%20%3D%20df.assign(%0A%20%20%20%20%20%20%20%20%20%20%20%20sample_counts%3Dlambda%20x%3A%20x.groupby(%22sample%22)%5B%22count%22%5D.transform(%22sum%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20count_frac%3Dlambda%20x%3A%20x%5B%22count%22%5D%20%2F%20x%5B%22sample_counts%22%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20median_count_frac%3Dlambda%20x%3A%20x.groupby(%22barcode%22)%5B%22count_frac%22%5D.transform(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22median%22%0A%20%20%20%20%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20fold_change_from_median%3Dlambda%20x%3A%20numpy.where(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20x%5B%22count_frac%22%5D%20%3E%20x%5B%22median_count_frac%22%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20x%5B%22count_frac%22%5D%20%2F%20x%5B%22median_count_frac%22%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20x%5B%22median_count_frac%22%5D%20%2F%20x%5B%22count_frac%22%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20)%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%5B%22barcode%22%2C%20%22count%22%2C%20%22sample_well%22%2C%20%22count_frac%22%2C%20%22fold_change_from_median%22%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%2B%20(%5B%5D%20if%20is_neut_standard%20else%20%5B%22strain%22%5D)%0A%20%20%20%20%20%20%20%20%5D%0A%0A%20%20%20%20%20%20%20%20%23%20Apply%20QC%20thresholds%0A%20%20%20%20%20%20%20%20qc%20%3D%20qc_thresholds%5Bqc_name%5D%0A%20%20%20%20%20%20%20%20mo.output.append(mo.md(f%22Apply%20QC%20%60%7Bqc_name%7D%60%3A%20%60%7Bqc%7D%60%22))%0A%0A%20%20%20%20%20%20%20%20fails_qc%20%3D%20(%0A%20%20%20%20%20%20%20%20%20%20%20%20df.assign(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20fails_qc%3Dlambda%20x%3A%20~(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20(x%5B%22count_frac%22%5D%20%3E%3D%20qc%5B%22min_frac%22%5D)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%26%20(x%5B%22fold_change_from_median%22%5D%20%3C%3D%20qc%5B%22max_fold_change%22%5D)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20.groupby(%22barcode%22%2C%20as_index%3DFalse)%0A%20%20%20%20%20%20%20%20%20%20%20%20.aggregate(n_wells_fail_qc%3Dpd.NamedAgg(%22fails_qc%22%2C%20%22sum%22))%0A%20%20%20%20%20%20%20%20%20%20%20%20.assign(fails_qc%3Dlambda%20x%3A%20x%5B%22n_wells_fail_qc%22%5D%20%3E%3D%20qc%5B%22max_wells%22%5D)%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%5B%22barcode%22%2C%20%22fails_qc%22%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%5D%0A%20%20%20%20%20%20%20%20)%0A%0A%20%20%20%20%20%20%20%20df%20%3D%20df.merge(fails_qc%2C%20on%3D%22barcode%22%2C%20validate%3D%22many_to_one%22)%0A%0A%20%20%20%20%20%20%20%20%23%20Create%20evenness%20chart%0A%20%20%20%20%20%20%20%20evenness_chart%20%3D%20(%0A%20%20%20%20%20%20%20%20%20%20%20%20alt.Chart(df)%0A%20%20%20%20%20%20%20%20%20%20%20%20.add_params(barcode_selection)%0A%20%20%20%20%20%20%20%20%20%20%20%20.encode(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20alt.X(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22count_frac%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20title%3D(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22barcode's%20fraction%20of%20neut%20standard%20counts%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20is_neut_standard%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20else%20%22barcode's%20fraction%20of%20non-neut%20standard%20counts%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20scale%3Dalt.Scale(nice%3DFalse%2C%20padding%3D5)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20alt.Y(%22sample_well%22%2C%20sort%3Dsample_wells)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20alt.Fill(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22fails_qc%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20title%3Df%22fails%20%7Bqc_name%7D%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20legend%3Dalt.Legend(titleLimit%3D500)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20strokeWidth%3Dalt.condition(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20barcode_selection%2C%20alt.value(2)%2C%20alt.value(0)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20size%3Dalt.condition(barcode_selection%2C%20alt.value(60)%2C%20alt.value(35))%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20tooltip%3D%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20alt.Tooltip(c%2C%20format%3D%22.2g%22)%20if%20df%5Bc%5D.dtype%20%3D%3D%20float%20else%20c%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20for%20c%20in%20df.columns%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20.mark_circle(fillOpacity%3D0.45%2C%20stroke%3D%22black%22%2C%20strokeOpacity%3D1)%0A%20%20%20%20%20%20%20%20%20%20%20%20.properties(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20height%3Dalt.Step(10)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20width%3D300%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20title%3Dalt.TitleParams(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f%22%7Bplate%7D%20all%20samples%2C%20neut-standard%20barcodes%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20is_neut_standard%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20else%20f%22%7Bplate%7D%20no-serum%20samples%2C%20all%20barcodes%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20subtitle%3D%22x-axis%20is%20zoomable%20(use%20mouse%20scroll%2Fpan)%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20.configure_axis(grid%3DFalse)%0A%20%20%20%20%20%20%20%20%20%20%20%20.configure_legend(titleLimit%3D1000)%0A%20%20%20%20%20%20%20%20%20%20%20%20.interactive()%0A%20%20%20%20%20%20%20%20)%0A%0A%20%20%20%20%20%20%20%20%23%20Display%20the%20chart%0A%20%20%20%20%20%20%20%20mo.output.append(evenness_chart)%0A%0A%20%20%20%20%20%20%20%20%23%20Drop%20barcodes%20failing%20QC%0A%20%20%20%20%20%20%20%20barcode_drops%20%3D%20list(fails_qc.query(%22fails_qc%22)%5B%22barcode%22%5D)%0A%20%20%20%20%20%20%20%20mo.output.append(%0A%20%20%20%20%20%20%20%20%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f%22Dropping%20%7Blen(barcode_drops)%7D%20barcodes%20for%20failing%20%60qc%3D%7Bqc!r%7D%60%3A%20%7Bbarcode_drops%7D%22%0A%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20qc_drops%5B%22barcodes%22%5D.update(%0A%20%20%20%20%20%20%20%20%20%20%20%20%7Bbc%3A%20%22min_neut_standard_frac_per_well%22%20for%20bc%20in%20barcode_drops%7D%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20counts_qc_4%20%3D%20counts_qc_4%5B~counts_qc_4%5B%22barcode%22%5D.isin(qc_drops%5B%22barcodes%22%5D)%5D%0A%20%20%20%20return%20barcode_selection%2C%20counts_qc_4%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20Compute%20fraction%20infectivity%0A%0A%20%20%20%20The%20fraction%20infectivity%20for%20viral%20barcode%20%24v_b%24%20in%20sample%20%24s%24%20is%20computed%20as%3A%0A%0A%20%20%20%20%24%24F_%7Bv_b%2Cs%7D%20%3D%20%5Cfrac%7Bc_%7Bv_b%2Cs%7D%20%2F%20%5Cleft(%5Csum_%7Bn_b%7D%20c_%7Bn_b%2Cs%7D%5Cright)%7D%7B%7B%5Crm%20median%7D_%7Bs_0%7D%5Cleft%5B%20c_%7Bv_b%2Cs_0%7D%20%2F%20%5Cleft(%5Csum_%7Bn_b%7D%20c_%7Bn_b%2Cs_0%7D%5Cright)%5Cright%5D%7D%24%24%0A%0A%20%20%20%20where%0A%20%20%20%20%20-%20%24c_%7Bv_b%2Cs%7D%24%20is%20the%20counts%20of%20viral%20barcode%20%24v_b%24%20in%20sample%20%24s%24.%0A%20%20%20%20%20-%20%24%5Csum_%7Bn_b%7D%20c_%7Bn_b%2Cs%7D%24%20is%20the%20sum%20of%20the%20counts%20for%20all%20neutralization%20standard%20barcodes%20%24n_b%24%20for%20sample%20%24s%24.%0A%20%20%20%20%20-%20%24c_%7Bv_b%2Cs_0%7D%24%20is%20the%20counts%20of%20viral%20barcode%20%24v_b%24%20in%20no-serum%20sample%20%24s_0%24.%0A%20%20%20%20%20-%20%24%5Csum_%7Bn_b%7D%20c_%7Bn_b%2Cs_0%7D%24%20is%20the%20sum%20of%20the%20counts%20for%20all%20neutralization%20standard%20barcodes%20%24n_b%24%20for%20no-serum%20sample%20%24s_0%24.%0A%20%20%20%20%20-%20%24%7B%5Crm%20median%7D_%7Bs_0%7D%5Cleft%5B%20c_%7Bv_b%2Cs_0%7D%20%2F%20%5Cleft(%5Csum_%7Bn_b%7D%20c_%7Bn_b%2Cs_0%7D%5Cright)%5Cright%5D%24%20is%20the%20median%20taken%20across%20all%20no-serum%20samples%20of%20the%20counts%20of%20viral%20barcode%20%24v_b%24%20versus%20the%20total%20counts%20for%20all%20neutralization%20standard%20barcodes.%0A%0A%20%20%20%20First%2C%20compute%20the%20total%20neutralization-standard%20counts%20for%20each%20sample%20(well).%0A%20%20%20%20Plot%20these%2C%20and%20drop%20any%20wells%20that%20do%20not%20meet%20the%20QC%20threshold.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(%0A%20%20%20%20alt%2C%0A%20%20%20%20counts_qc_4%2C%0A%20%20%20%20pd%2C%0A%20%20%20%20plate%2C%0A%20%20%20%20qc_thresholds%2C%0A%20%20%20%20sample_wells%2C%0A%20%20%20%20serum_selection%2C%0A)%3A%0A%20%20%20%20%23%20Compute%20neutralization%20standard%20counts%20per%20well%0A%20%20%20%20neut_standard_counts%20%3D%20(%0A%20%20%20%20%20%20%20%20counts_qc_4.query(%22neut_standard%22)%0A%20%20%20%20%20%20%20%20.groupby(%0A%20%20%20%20%20%20%20%20%20%20%20%20%5B%22well%22%2C%20%22serum_replicate%22%2C%20%22sample_well%22%2C%20%22dilution_factor%22%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20dropna%3DFalse%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20as_index%3DFalse%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20.aggregate(neut_standard_count%3Dpd.NamedAgg(%22count%22%2C%20%22sum%22))%0A%20%20%20%20%20%20%20%20.assign(%0A%20%20%20%20%20%20%20%20%20%20%20%20fails_qc%3Dlambda%20x%3A%20(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20x%5B%22neut_standard_count%22%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%3C%20qc_thresholds%5B%22min_neut_standard_count_per_well%22%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20)%0A%0A%20%20%20%20%23%20Create%20chart%0A%20%20%20%20neut_standard_counts_chart%20%3D%20(%0A%20%20%20%20%20%20%20%20alt.Chart(neut_standard_counts)%0A%20%20%20%20%20%20%20%20.add_params(serum_selection)%0A%20%20%20%20%20%20%20%20.transform_filter(serum_selection)%0A%20%20%20%20%20%20%20%20.encode(%0A%20%20%20%20%20%20%20%20%20%20%20%20alt.X(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22neut_standard_count%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20title%3D%22counts%20from%20neutralization%20standard%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20scale%3Dalt.Scale(nice%3DFalse%2C%20padding%3D3)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20alt.Y(%22sample_well%22%2C%20sort%3Dsample_wells)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20alt.Color(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22fails_qc%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20title%3Df%22fails%20qc_thresholds%5B'min_neut_standard_count_per_well'%5D%3D%7Bqc_thresholds%5B'min_neut_standard_count_per_well'%5D!r%7D%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20legend%3Dalt.Legend(titleLimit%3D500)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20tooltip%3D%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20alt.Tooltip(c%2C%20format%3D%22.3g%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20neut_standard_counts%5Bc%5D.dtype%20%3D%3D%20float%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20else%20c%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20for%20c%20in%20neut_standard_counts.columns%0A%20%20%20%20%20%20%20%20%20%20%20%20%5D%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20.mark_bar(height%3D%7B%22band%22%3A%200.85%7D)%0A%20%20%20%20%20%20%20%20.properties(%0A%20%20%20%20%20%20%20%20%20%20%20%20height%3Dalt.Step(10)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20width%3D250%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20title%3Df%22Neutralization-standard%20counts%20for%20%7Bplate%7D%22%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20.configure_axis(grid%3DFalse)%0A%20%20%20%20%20%20%20%20.configure_legend(titleLimit%3D1000)%0A%20%20%20%20)%0A%0A%20%20%20%20%23%20Display%20the%20chart%0A%20%20%20%20neut_standard_counts_chart%0A%20%20%20%20return%20(neut_standard_counts%2C)%0A%0A%0A%40app.cell%0Adef%20_(counts_qc_4%2C%20mo%2C%20neut_standard_counts%2C%20qc_drops%2C%20qc_thresholds)%3A%0A%20%20%20%20%23%20drop%20wells%20failing%20QC%0A%20%20%20%20min_neut_standard_count_per_well_drops%20%3D%20list(%0A%20%20%20%20%20%20%20%20neut_standard_counts.query(%22fails_qc%22)%5B%22well%22%5D%0A%20%20%20%20)%0A%20%20%20%20mo.output.append(%0A%20%20%20%20%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22Dropping%20%7Blen(min_neut_standard_count_per_well_drops)%7D%20wells%20for%20failing%20%60qc_thresholds%5B'min_neut_standard_count_per_well'%5D%3D%7Bqc_thresholds%5B'min_neut_standard_count_per_well'%5D!r%7D%60%3A%20%7Bmin_neut_standard_count_per_well_drops%7D%22%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20)%0A%20%20%20%20qc_drops%5B%22wells%22%5D.update(%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20w%3A%20%22min_neut_standard_count_per_well%22%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20w%20in%20min_neut_standard_count_per_well_drops%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20)%0A%20%20%20%20neut_standard_counts_1%20%3D%20neut_standard_counts%5B%0A%20%20%20%20%20%20%20%20~neut_standard_counts%5B%22well%22%5D.isin(qc_drops%5B%22wells%22%5D)%0A%20%20%20%20%5D%0A%20%20%20%20counts_qc_5%20%3D%20counts_qc_4%5B~counts_qc_4%5B%22well%22%5D.isin(qc_drops%5B%22wells%22%5D)%5D%0A%20%20%20%20return%20counts_qc_5%2C%20neut_standard_counts_1%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20Compute%20and%20plot%20the%20no-serum%20sample%20viral%20barcode%20counts%20and%20check%20if%20they%20pass%20the%20QC%20filters.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(%0A%20%20%20%20alt%2C%0A%20%20%20%20barcode_selection%2C%0A%20%20%20%20counts_qc_5%2C%0A%20%20%20%20neut_standard_counts_1%2C%0A%20%20%20%20plate%2C%0A%20%20%20%20qc_thresholds%2C%0A%20%20%20%20sample_wells%2C%0A)%3A%0A%20%20%20%20%23%20Compute%20no-serum%20viral%20barcode%20counts%20with%20QC%0A%20%20%20%20no_serum_counts%20%3D%20(%0A%20%20%20%20%20%20%20%20counts_qc_5.query(%22serum%20%3D%3D%20'none'%22)%0A%20%20%20%20%20%20%20%20.query(%22not%20neut_standard%22)%0A%20%20%20%20%20%20%20%20.merge(neut_standard_counts_1%2C%20validate%3D%22many_to_one%22)%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%5B%22barcode%22%2C%20%22strain%22%2C%20%22well%22%2C%20%22sample_well%22%2C%20%22count%22%2C%20%22neut_standard_count%22%5D%0A%20%20%20%20%20%20%20%20%5D%0A%20%20%20%20%20%20%20%20.assign(%0A%20%20%20%20%20%20%20%20%20%20%20%20fails_qc%3Dlambda%20x%3A%20(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20x%5B%22count%22%5D%20%3C%3D%20qc_thresholds%5B%22min_no_serum_count_per_viral_barcode_well%22%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20)%0A%0A%20%20%20%20%23%20Create%20strain%20selection%20dropdown%0A%20%20%20%20strains%20%3D%20sorted(no_serum_counts%5B%22strain%22%5D.unique())%0A%20%20%20%20strain_selection_dropdown%20%3D%20alt.selection_point(%0A%20%20%20%20%20%20%20%20fields%3D%5B%22strain%22%5D%2C%0A%20%20%20%20%20%20%20%20bind%3Dalt.binding_select(%0A%20%20%20%20%20%20%20%20%20%20%20%20options%3D%5BNone%5D%20%2B%20strains%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20labels%3D%5B%22all%22%5D%20%2B%20strains%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20name%3D%22virus%20strain%22%2C%0A%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20)%0A%0A%20%20%20%20%23%20Prepare%20data%20for%20plotting%0A%20%20%20%20no_serum_counts_plot_df%20%3D%20no_serum_counts.drop(%0A%20%20%20%20%20%20%20%20columns%3D%5B%22well%22%2C%20%22neut_standard_count%22%5D%0A%20%20%20%20)%0A%0A%20%20%20%20%23%20Create%20chart%0A%20%20%20%20no_serum_counts_chart%20%3D%20(%0A%20%20%20%20%20%20%20%20alt.Chart(no_serum_counts_plot_df)%0A%20%20%20%20%20%20%20%20.add_params(barcode_selection%2C%20strain_selection_dropdown)%0A%20%20%20%20%20%20%20%20.transform_filter(strain_selection_dropdown)%0A%20%20%20%20%20%20%20%20.encode(%0A%20%20%20%20%20%20%20%20%20%20%20%20alt.X(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22count%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20title%3D%22viral%20barcode%20count%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20scale%3Dalt.Scale(nice%3DFalse%2C%20padding%3D5)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20alt.Y(%22sample_well%22%2C%20sort%3Dsample_wells)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20alt.Fill(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22fails_qc%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20title%3Df%22fails%20qc_thresholds%5B'min_no_serum_count_per_viral_barcode_well'%5D%3D%7Bqc_thresholds%5B'min_no_serum_count_per_viral_barcode_well'%5D!r%7D%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20legend%3Dalt.Legend(titleLimit%3D500)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20strokeWidth%3Dalt.condition(barcode_selection%2C%20alt.value(2)%2C%20alt.value(0))%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20size%3Dalt.condition(barcode_selection%2C%20alt.value(60)%2C%20alt.value(35))%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20tooltip%3Dno_serum_counts_plot_df.columns.tolist()%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20.mark_circle(fillOpacity%3D0.6%2C%20stroke%3D%22black%22%2C%20strokeOpacity%3D1)%0A%20%20%20%20%20%20%20%20.properties(%0A%20%20%20%20%20%20%20%20%20%20%20%20height%3Dalt.Step(10)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20width%3D400%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20title%3Df%22%7Bplate%7D%20viral%20barcode%20counts%20in%20no-serum%20samples%22%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20.configure_axis(grid%3DFalse)%0A%20%20%20%20%20%20%20%20.configure_legend(titleLimit%3D1000)%0A%20%20%20%20%20%20%20%20.interactive()%0A%20%20%20%20)%0A%0A%20%20%20%20%23%20Display%20the%20chart%0A%20%20%20%20no_serum_counts_chart%0A%20%20%20%20return%20no_serum_counts%2C%20strain_selection_dropdown%0A%0A%0A%40app.cell%0Adef%20_(counts_qc_5%2C%20mo%2C%20no_serum_counts%2C%20qc_drops%2C%20qc_thresholds)%3A%0A%20%20%20%20%23%20drop%20barcode%20%2F%20wells%20failing%20QC%0A%20%20%20%20min_no_serum_count_per_viral_barcode_well_drops%20%3D%20list(%0A%20%20%20%20%20%20%20%20no_serum_counts.query(%22fails_qc%22)%5B%5B%22barcode%22%2C%20%22well%22%5D%5D.itertuples(%0A%20%20%20%20%20%20%20%20%20%20%20%20index%3DFalse%2C%20name%3DNone%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20)%0A%20%20%20%20mo.output.append(%0A%20%20%20%20%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22Dropping%20%7Blen(min_no_serum_count_per_viral_barcode_well_drops)%7D%20barcode-wells%20for%20failing%20%60qc_thresholds%5B'min_no_serum_count_per_viral_barcode_well'%5D%3D%7Bqc_thresholds%5B'min_no_serum_count_per_viral_barcode_well'%5D!r%7D%60%3A%20%7Bmin_no_serum_count_per_viral_barcode_well_drops%7D%22%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20)%0A%20%20%20%20qc_drops%5B%22barcode_wells%22%5D.update(%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20w%3A%20%22min_no_serum_count_per_viral_barcode_well%22%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20w%20in%20min_no_serum_count_per_viral_barcode_well_drops%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20)%0A%20%20%20%20no_serum_counts_1%20%3D%20no_serum_counts%5B%0A%20%20%20%20%20%20%20%20~no_serum_counts.assign(%0A%20%20%20%20%20%20%20%20%20%20%20%20barcode_well%3Dlambda%20x%3A%20x.apply(lambda%20r%3A%20(r%5B%22barcode%22%5D%2C%20r%5B%22well%22%5D)%2C%20axis%3D1)%0A%20%20%20%20%20%20%20%20)%5B%22barcode_well%22%5D.isin(qc_drops%5B%22barcode_wells%22%5D)%0A%20%20%20%20%5D%0A%20%20%20%20counts_qc_6%20%3D%20counts_qc_5%5B%0A%20%20%20%20%20%20%20%20~counts_qc_5.assign(%0A%20%20%20%20%20%20%20%20%20%20%20%20barcode_well%3Dlambda%20x%3A%20x.apply(lambda%20r%3A%20(r%5B%22barcode%22%5D%2C%20r%5B%22well%22%5D)%2C%20axis%3D1)%0A%20%20%20%20%20%20%20%20)%5B%22barcode_well%22%5D.isin(qc_drops%5B%22barcode_wells%22%5D)%0A%20%20%20%20%5D%0A%20%20%20%20return%20counts_qc_6%2C%20no_serum_counts_1%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20Compute%20and%20plot%20the%20median%20ratio%20of%20viral%20barcode%20count%20to%20neut%20standard%20counts%20across%20no-serum%20samples.%0A%20%20%20%20If%20library%20composition%20is%20equal%2C%20all%20of%20these%20values%20should%20be%20similar%3A%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(alt%2C%20no_serum_counts_1%2C%20pd%2C%20plate)%3A%0A%20%20%20%20%23%20Compute%20median%20ratio%20of%20viral%20barcode%20to%20neut%20standard%20counts%0A%20%20%20%20median_no_serum_ratio%20%3D%20(%0A%20%20%20%20%20%20%20%20no_serum_counts_1.assign(ratio%3Dlambda%20x%3A%20x%5B%22count%22%5D%20%2F%20x%5B%22neut_standard_count%22%5D)%0A%20%20%20%20%20%20%20%20.groupby(%5B%22barcode%22%2C%20%22strain%22%5D%2C%20as_index%3DFalse)%0A%20%20%20%20%20%20%20%20.aggregate(median_no_serum_ratio%3Dpd.NamedAgg(%22ratio%22%2C%20%22median%22))%0A%20%20%20%20)%0A%0A%20%20%20%20%23%20Create%20strain%20selection%20for%20mouseover%0A%20%20%20%20strain_selection%20%3D%20alt.selection_point(%0A%20%20%20%20%20%20%20%20fields%3D%5B%22strain%22%5D%2C%0A%20%20%20%20%20%20%20%20on%3D%22mouseover%22%2C%0A%20%20%20%20%20%20%20%20empty%3DFalse%2C%0A%20%20%20%20)%0A%0A%20%20%20%20%23%20Create%20chart%0A%20%20%20%20median_no_serum_ratio_chart%20%3D%20(%0A%20%20%20%20%20%20%20%20alt.Chart(median_no_serum_ratio)%0A%20%20%20%20%20%20%20%20.add_params(strain_selection)%0A%20%20%20%20%20%20%20%20.encode(%0A%20%20%20%20%20%20%20%20%20%20%20%20alt.X(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22median_no_serum_ratio%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20title%3D%22median%20ratio%20of%20counts%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20scale%3Dalt.Scale(nice%3DFalse%2C%20padding%3D5)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20alt.Y(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22barcode%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20sort%3Dalt.SortField(%22median_no_serum_ratio%22%2C%20order%3D%22descending%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20axis%3Dalt.Axis(labelFontSize%3D5)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20color%3Dalt.condition(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20strain_selection%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20alt.value(%22orange%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20alt.value(%22gray%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20tooltip%3D%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20alt.Tooltip(c%2C%20format%3D%22.3g%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20median_no_serum_ratio%5Bc%5D.dtype%20%3D%3D%20float%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20else%20c%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20for%20c%20in%20median_no_serum_ratio.columns%0A%20%20%20%20%20%20%20%20%20%20%20%20%5D%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20.mark_bar(height%3D%7B%22band%22%3A%200.85%7D)%0A%20%20%20%20%20%20%20%20.properties(%0A%20%20%20%20%20%20%20%20%20%20%20%20height%3Dalt.Step(5)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20width%3D250%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20title%3Df%22%7Bplate%7D%20no-serum%20median%20ratio%20viral%20barcode%20to%20neut-standard%20barcode%22%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20.configure_axis(grid%3DFalse)%0A%20%20%20%20%20%20%20%20.configure_legend(titleLimit%3D1000)%0A%20%20%20%20)%0A%0A%20%20%20%20%23%20Display%20the%20chart%0A%20%20%20%20median_no_serum_ratio_chart%0A%20%20%20%20return%20(median_no_serum_ratio%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20Compute%20and%20plot%20the%20actual%20fraction%20infectivities.%0A%20%20%20%20We%20compute%20both%20the%20raw%20fraction%20infectivities%20and%20the%20ones%20with%20the%20ceiling%20applied%3A%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(%0A%20%20%20%20counts_qc_6%2C%0A%20%20%20%20curvefit_params%2C%0A%20%20%20%20median_no_serum_ratio%2C%0A%20%20%20%20neut_standard_counts_1%2C%0A)%3A%0A%20%20%20%20frac_infectivity%20%3D%20(%0A%20%20%20%20%20%20%20%20counts_qc_6.query(%22not%20neut_standard%22)%0A%20%20%20%20%20%20%20%20.query(%22serum%20!%3D%20'none'%22)%0A%20%20%20%20%20%20%20%20.merge(median_no_serum_ratio%2C%20validate%3D%22many_to_one%22)%0A%20%20%20%20%20%20%20%20.merge(neut_standard_counts_1%2C%20validate%3D%22many_to_one%22)%0A%20%20%20%20%20%20%20%20.assign(%0A%20%20%20%20%20%20%20%20%20%20%20%20frac_infectivity_raw%3Dlambda%20x%3A%20(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20x%5B%22count%22%5D%20%2F%20x%5B%22neut_standard_count%22%5D%20%2F%20x%5B%22median_no_serum_ratio%22%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20frac_infectivity_ceiling%3Dlambda%20x%3A%20(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20x%5B%22frac_infectivity_raw%22%5D.clip(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20upper%3Dcurvefit_params%5B%22frac_infectivity_ceiling%22%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20concentration%3Dlambda%20x%3A%201%20%2F%20x%5B%22dilution_factor%22%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20plate_barcode%3Dlambda%20x%3A%20x%5B%22plate_replicate%22%5D%20%2B%20%22-%22%20%2B%20x%5B%22barcode%22%5D%2C%0A%20%20%20%20%20%20%20%20)%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22barcode%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22plate_barcode%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22well%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22strain%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22serum%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22serum_replicate%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22dilution_factor%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22concentration%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22frac_infectivity_raw%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22frac_infectivity_ceiling%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%5D%0A%20%20%20%20%20%20%20%20%5D%0A%20%20%20%20)%0A%20%20%20%20assert%20len(%0A%20%20%20%20%20%20%20%20frac_infectivity.groupby(%5B%22serum%22%2C%20%22plate_barcode%22%2C%20%22dilution_factor%22%5D)%0A%20%20%20%20)%20%3D%3D%20len(frac_infectivity)%0A%20%20%20%20assert%20frac_infectivity%5B%22dilution_factor%22%5D.notnull().all()%0A%20%20%20%20assert%20frac_infectivity%5B%22frac_infectivity_raw%22%5D.notnull().all()%0A%20%20%20%20assert%20frac_infectivity%5B%22frac_infectivity_ceiling%22%5D.notnull().all()%0A%20%20%20%20return%20(frac_infectivity%2C)%0A%0A%0A%40app.cell%0Adef%20_(curvefit_params%2C%20frac_infectivity%2C%20qc_thresholds)%3A%0A%20%20%20%20frac_infectivity_cols%20%3D%20%7B%0A%20%20%20%20%20%20%20%20%22frac_infectivity_raw%22%3A%20%22raw%20fraction%20infectivity%22%2C%0A%20%20%20%20%20%20%20%20%22frac_infectivity_ceiling%22%3A%20f%22fraction%20infectivity%20with%20ceiling%20at%20%7Bcurvefit_params%5B'frac_infectivity_ceiling'%5D%7D%22%2C%0A%20%20%20%20%7D%0A%0A%20%20%20%20frac_infectivity_chart_df%20%3D%20frac_infectivity.assign(%0A%20%20%20%20%20%20%20%20fails_qc%3Dlambda%20x%3A%20(%0A%20%20%20%20%20%20%20%20%20%20%20%20x%5B%22frac_infectivity_raw%22%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%3E%20qc_thresholds%5B%22max_frac_infectivity_per_viral_barcode_well%22%5D%0A%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20)%5B%0A%20%20%20%20%20%20%20%20%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22barcode%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22strain%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22well%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22serum_replicate%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22dilution_factor%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22fails_qc%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20*list(frac_infectivity_cols)%2C%0A%20%20%20%20%20%20%20%20%5D%0A%20%20%20%20%5D.rename(%0A%20%20%20%20%20%20%20%20columns%3Dfrac_infectivity_cols%0A%20%20%20%20)%0A%0A%20%20%20%20%23%20some%20manipulations%20to%20shrink%20data%20frame%20plotted%20by%20altair%20below%20by%20putting%0A%20%20%20%20%23%20them%20in%20smaller%20data%20frames%20that%20are%20used%20via%20transform_lookup%0A%20%20%20%20barcode_lookup_df%20%3D%20frac_infectivity%5B%5B%22barcode%22%2C%20%22strain%22%5D%5D.drop_duplicates()%0A%20%20%20%20assert%20len(barcode_lookup_df)%20%3D%3D%20barcode_lookup_df%5B%22barcode%22%5D.nunique()%0A%20%20%20%20well_lookup_df%20%3D%20frac_infectivity%5B%0A%20%20%20%20%20%20%20%20%5B%22well%22%2C%20%22serum_replicate%22%2C%20%22dilution_factor%22%5D%0A%20%20%20%20%5D.drop_duplicates()%0A%20%20%20%20assert%20len(well_lookup_df)%20%3D%3D%20well_lookup_df%5B%22well%22%5D.nunique()%0A%0A%20%20%20%20frac_infectivity_chart_df%20%3D%20frac_infectivity_chart_df.drop(%0A%20%20%20%20%20%20%20%20columns%3D%5B%22strain%22%2C%20%22serum_replicate%22%2C%20%22dilution_factor%22%5D%0A%20%20%20%20)%0A%20%20%20%20return%20(%0A%20%20%20%20%20%20%20%20barcode_lookup_df%2C%0A%20%20%20%20%20%20%20%20frac_infectivity_chart_df%2C%0A%20%20%20%20%20%20%20%20frac_infectivity_cols%2C%0A%20%20%20%20%20%20%20%20well_lookup_df%2C%0A%20%20%20%20)%0A%0A%0A%40app.cell%0Adef%20_(%0A%20%20%20%20alt%2C%0A%20%20%20%20barcode_lookup_df%2C%0A%20%20%20%20barcode_selection%2C%0A%20%20%20%20frac_infectivity_chart_df%2C%0A%20%20%20%20frac_infectivity_cols%2C%0A%20%20%20%20mo%2C%0A%20%20%20%20plate%2C%0A%20%20%20%20qc_thresholds%2C%0A%20%20%20%20strain_selection_dropdown%2C%0A%20%20%20%20well_lookup_df%2C%0A)%3A%0A%20%20%20%20frac_infectivity_chart%20%3D%20(%0A%20%20%20%20%20%20%20%20alt.Chart(frac_infectivity_chart_df)%0A%20%20%20%20%20%20%20%20.transform_lookup(%0A%20%20%20%20%20%20%20%20%20%20%20%20lookup%3D%22barcode%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20from_%3Dalt.LookupData(barcode_lookup_df%2C%20key%3D%22barcode%22%2C%20fields%3D%5B%22strain%22%5D)%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20.transform_lookup(%0A%20%20%20%20%20%20%20%20%20%20%20%20lookup%3D%22well%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20from_%3Dalt.LookupData(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20well_lookup_df%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20key%3D%22well%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20fields%3D%5B%22serum_replicate%22%2C%20%22dilution_factor%22%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20.transform_fold(%0A%20%20%20%20%20%20%20%20%20%20%20%20frac_infectivity_cols.values()%2C%20%5B%22ceiling_applied%22%2C%20%22frac_infectivity%22%5D%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20.add_params(strain_selection_dropdown%2C%20barcode_selection)%0A%20%20%20%20%20%20%20%20.transform_filter(strain_selection_dropdown)%0A%20%20%20%20%20%20%20%20.encode(%0A%20%20%20%20%20%20%20%20%20%20%20%20alt.X(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22dilution_factor%3AQ%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20title%3D%22dilution%20factor%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20scale%3Dalt.Scale(nice%3DFalse%2C%20padding%3D5%2C%20type%3D%22log%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20alt.Y(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22frac_infectivity%3AQ%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20title%3D%22fraction%20infectivity%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20scale%3Dalt.Scale(nice%3DFalse%2C%20padding%3D5)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20alt.Column(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22ceiling_applied%3AN%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20sort%3D%22descending%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20title%3DNone%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20header%3Dalt.Header(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20labelFontSize%3D13%2C%20labelFontStyle%3D%22bold%22%2C%20labelPadding%3D2%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20alt.Row(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22serum_replicate%3AN%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20title%3DNone%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20spacing%3D3%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20header%3Dalt.Header(labelFontSize%3D13%2C%20labelFontStyle%3D%22bold%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20alt.Detail(%22barcode%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20alt.Shape(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22fails_qc%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20title%3Df%22fails%20%7Bqc_thresholds%5B'max_frac_infectivity_per_viral_barcode_well'%5D%3D%7D%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20legend%3Dalt.Legend(titleLimit%3D500%2C%20orient%3D%22bottom%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20color%3Dalt.condition(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20barcode_selection%2C%20alt.value(%22black%22)%2C%20alt.value(%22MediumBlue%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20strokeWidth%3Dalt.condition(barcode_selection%2C%20alt.value(3)%2C%20alt.value(1))%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20opacity%3Dalt.condition(barcode_selection%2C%20alt.value(1)%2C%20alt.value(0.25))%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20tooltip%3D%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20alt.Tooltip(c%2C%20format%3D%22.3g%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20frac_infectivity_chart_df%5Bc%5D.dtype%20%3D%3D%20float%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20else%20c%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20for%20c%20in%20frac_infectivity_chart_df.columns%0A%20%20%20%20%20%20%20%20%20%20%20%20%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%2B%20%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20alt.Tooltip(%22strain%3AN%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20alt.Tooltip(%22serum_replicate%3AN%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20alt.Tooltip(%22dilution_factor%3AQ%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%5D%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20.mark_line(point%3DTrue)%0A%20%20%20%20%20%20%20%20.properties(%0A%20%20%20%20%20%20%20%20%20%20%20%20height%3D150%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20width%3D250%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20title%3Df%22Fraction%20infectivities%20for%20%7Bplate%7D%22%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20.interactive(bind_x%3DFalse)%0A%20%20%20%20%20%20%20%20.configure_axis(grid%3DFalse)%0A%20%20%20%20%20%20%20%20.configure_legend(titleLimit%3D1000)%0A%20%20%20%20%20%20%20%20.configure_point(size%3D50)%0A%20%20%20%20%20%20%20%20.resolve_scale(x%3D%22independent%22%2C%20y%3D%22independent%22)%0A%20%20%20%20)%0A%0A%20%20%20%20%23%20Display%20the%20chart%0A%20%20%20%20mo.output.append(frac_infectivity_chart)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(%0A%20%20%20%20frac_infectivity%2C%0A%20%20%20%20frac_infectivity_chart_df%2C%0A%20%20%20%20mo%2C%0A%20%20%20%20qc_drops%2C%0A%20%20%20%20qc_thresholds%2C%0A)%3A%0A%20%20%20%20%23%20drop%20barcode%20%2F%20wells%20failing%20QC%0A%20%20%20%20max_frac_infectivity_per_viral_barcode_well_drops%20%3D%20list(%0A%20%20%20%20%20%20%20%20frac_infectivity_chart_df.query(%22fails_qc%22)%5B%5B%22barcode%22%2C%20%22well%22%5D%5D%0A%20%20%20%20%20%20%20%20.drop_duplicates()%0A%20%20%20%20%20%20%20%20.itertuples(index%3DFalse%2C%20name%3DNone)%0A%20%20%20%20)%0A%20%20%20%20mo.output.append(%0A%20%20%20%20%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22Dropping%20%7Blen(max_frac_infectivity_per_viral_barcode_well_drops)%7D%20barcode-wells%20for%20failing%20%60qc_thresholds%5B'max_frac_infectivity_per_viral_barcode_well'%5D%3D%7Bqc_thresholds%5B'max_frac_infectivity_per_viral_barcode_well'%5D!r%7D%60%3A%20%7Bmax_frac_infectivity_per_viral_barcode_well_drops%7D%22%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20)%0A%20%20%20%20qc_drops%5B%22barcode_wells%22%5D.update(%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20w%3A%20%22max_frac_infectivity_per_viral_barcode_well%22%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20w%20in%20max_frac_infectivity_per_viral_barcode_well_drops%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20)%0A%20%20%20%20frac_infectivity_1%20%3D%20frac_infectivity%5B%0A%20%20%20%20%20%20%20%20~frac_infectivity.assign(%0A%20%20%20%20%20%20%20%20%20%20%20%20barcode_well%3Dlambda%20x%3A%20x.apply(lambda%20r%3A%20(r%5B%22barcode%22%5D%2C%20r%5B%22well%22%5D)%2C%20axis%3D1)%0A%20%20%20%20%20%20%20%20)%5B%22barcode_well%22%5D.isin(qc_drops%5B%22barcode_wells%22%5D)%0A%20%20%20%20%5D%0A%20%20%20%20return%20(frac_infectivity_1%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20Check%20how%20many%20dilutions%20we%20have%20per%20barcode%20%2F%20serum-replicate%3A%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(%0A%20%20%20%20alt%2C%0A%20%20%20%20barcode_selection%2C%0A%20%20%20%20frac_infectivity_1%2C%0A%20%20%20%20mo%2C%0A%20%20%20%20pd%2C%0A%20%20%20%20qc_drops%2C%0A%20%20%20%20qc_thresholds%2C%0A)%3A%0A%20%20%20%20%23%20Count%20number%20of%20dilutions%20per%20barcode%2Fserum-replicate%0A%20%20%20%20n_dilutions%20%3D%20(%0A%20%20%20%20%20%20%20%20frac_infectivity_1.groupby(%0A%20%20%20%20%20%20%20%20%20%20%20%20%5B%22serum_replicate%22%2C%20%22strain%22%2C%20%22barcode%22%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20as_index%3DFalse%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20.aggregate(**%7B%22number%20of%20dilutions%22%3A%20pd.NamedAgg(%22dilution_factor%22%2C%20%22nunique%22)%7D)%0A%20%20%20%20%20%20%20%20.assign(%0A%20%20%20%20%20%20%20%20%20%20%20%20fails_qc%3Dlambda%20x%3A%20(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20x%5B%22number%20of%20dilutions%22%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%3C%20qc_thresholds%5B%22min_dilutions_per_barcode_serum_replicate%22%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20)%0A%0A%20%20%20%20%23%20Create%20chart%0A%20%20%20%20n_dilutions_chart%20%3D%20(%0A%20%20%20%20%20%20%20%20alt.Chart(n_dilutions)%0A%20%20%20%20%20%20%20%20.add_params(barcode_selection)%0A%20%20%20%20%20%20%20%20.encode(%0A%20%20%20%20%20%20%20%20%20%20%20%20alt.X(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22number%20of%20dilutions%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20scale%3Dalt.Scale(nice%3DFalse%2C%20padding%3D4)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20alt.Y(%22strain%22%2C%20title%3DNone)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20alt.Column(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22serum_replicate%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20title%3DNone%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20header%3Dalt.Header(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20labelFontSize%3D12%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20labelFontStyle%3D%22bold%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20labelPadding%3D0%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20alt.Fill(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22fails_qc%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20title%3Df%22fails%20qc_thresholds%5B'min_dilutions_per_barcode_serum_replicate'%5D%3D%7Bqc_thresholds%5B'min_dilutions_per_barcode_serum_replicate'%5D!r%7D%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20legend%3Dalt.Legend(titleLimit%3D500%2C%20orient%3D%22bottom%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20strokeWidth%3Dalt.condition(barcode_selection%2C%20alt.value(2)%2C%20alt.value(0))%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20size%3Dalt.condition(barcode_selection%2C%20alt.value(55)%2C%20alt.value(35))%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20tooltip%3D%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20alt.Tooltip(c%2C%20format%3D%22.3g%22)%20if%20n_dilutions%5Bc%5D.dtype%20%3D%3D%20float%20else%20c%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20for%20c%20in%20n_dilutions.columns%0A%20%20%20%20%20%20%20%20%20%20%20%20%5D%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20.mark_circle(stroke%3D%22black%22%2C%20strokeOpacity%3D1%2C%20fillOpacity%3D0.45)%0A%20%20%20%20%20%20%20%20.properties(%0A%20%20%20%20%20%20%20%20%20%20%20%20height%3Dalt.Step(10)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20width%3D120%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20title%3Dalt.TitleParams(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22number%20of%20dilutions%20for%20each%20barcode%20for%20each%20serum-replicate%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dy%3D-2%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20)%0A%0A%20%20%20%20%23%20Display%20the%20chart%0A%20%20%20%20mo.output.append(n_dilutions_chart)%0A%0A%20%20%20%20%23%20Drop%20barcode%2Fserum-replicates%20failing%20QC%0A%20%20%20%20min_dilutions_per_barcode_serum_replicate_drops%20%3D%20list(%0A%20%20%20%20%20%20%20%20n_dilutions.query(%22fails_qc%22)%5B%5B%22barcode%22%2C%20%22serum_replicate%22%5D%5D.itertuples(%0A%20%20%20%20%20%20%20%20%20%20%20%20index%3DFalse%2C%20name%3DNone%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20)%0A%20%20%20%20mo.output.append(%0A%20%20%20%20%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22Dropping%20%7Blen(min_dilutions_per_barcode_serum_replicate_drops)%7D%20barcode%2Fserum-replicates%20for%20failing%20%60qc_thresholds%5B'min_dilutions_per_barcode_serum_replicate'%5D%3D%7Bqc_thresholds%5B'min_dilutions_per_barcode_serum_replicate'%5D!r%7D%60%3A%20%7Bmin_dilutions_per_barcode_serum_replicate_drops%7D%22%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20)%0A%20%20%20%20qc_drops%5B%22barcode_serum_replicates%22%5D.update(%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20w%3A%20%22min_dilutions_per_barcode_serum_replicate%22%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20w%20in%20min_dilutions_per_barcode_serum_replicate_drops%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20)%0A%20%20%20%20frac_infectivity_2%20%3D%20frac_infectivity_1%5B%0A%20%20%20%20%20%20%20%20~frac_infectivity_1.assign(%0A%20%20%20%20%20%20%20%20%20%20%20%20barcode_serum_replicate%3Dlambda%20x%3A%20x.apply(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20lambda%20r%3A%20(r%5B%22barcode%22%5D%2C%20r%5B%22serum_replicate%22%5D)%2C%20axis%3D1%0A%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20)%5B%22barcode_serum_replicate%22%5D.isin(qc_drops%5B%22barcode_serum_replicates%22%5D)%0A%20%20%20%20%5D%0A%20%20%20%20return%20(frac_infectivity_2%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20Fit%20neutralization%20curves%20without%20applying%20QC%20to%20curves%0A%20%20%20%20First%20fit%20curves%20to%20all%20serum%20replicates%2C%20then%20we%20will%20apply%20QC%20on%20the%20curve%20fits.%0A%20%20%20%20Note%20that%20the%20fitting%20is%20done%20to%20the%20fraction%20infectivities%20**with**%20the%20ceiling.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(curvefit_params%2C%20frac_infectivity_2%2C%20neutcurve)%3A%0A%20%20%20%20fits_noqc%20%3D%20neutcurve.CurveFits(%0A%20%20%20%20%20%20%20%20frac_infectivity_2.rename(%0A%20%20%20%20%20%20%20%20%20%20%20%20columns%3D%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22frac_infectivity_ceiling%22%3A%20%22fraction%20infectivity%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22concentration%22%3A%20%22serum%20concentration%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20conc_col%3D%22serum%20concentration%22%2C%0A%20%20%20%20%20%20%20%20fracinf_col%3D%22fraction%20infectivity%22%2C%0A%20%20%20%20%20%20%20%20virus_col%3D%22strain%22%2C%0A%20%20%20%20%20%20%20%20serum_col%3D%22serum_replicate%22%2C%0A%20%20%20%20%20%20%20%20replicate_col%3D%22barcode%22%2C%0A%20%20%20%20%20%20%20%20fixtop%3Dcurvefit_params%5B%22fixtop%22%5D%2C%0A%20%20%20%20%20%20%20%20fixbottom%3Dcurvefit_params%5B%22fixbottom%22%5D%2C%0A%20%20%20%20%20%20%20%20fixslope%3Dcurvefit_params%5B%22fixslope%22%5D%2C%0A%20%20%20%20)%0A%20%20%20%20return%20(fits_noqc%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20Determine%20which%20fits%20fail%20the%20curve%20fitting%20QC%2C%20and%20plot%20them.%0A%20%20%20%20Note%20the%20plot%20indicates%20as%20failing%20QC%20any%20barcode%20%2F%20serum-replicate%20that%20fails%2C%20even%20if%20we%20are%20also%20specified%20to%20ignore%20the%20QC%20for%20that%20one%20(so%20it%20will%20not%20be%20removed%20later)%3A%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(curvefit_qc%2C%20fits_noqc%2C%20frac_infectivity_2%2C%20pd)%3A%0A%20%20%20%20goodness_of_fit%20%3D%20curvefit_qc%5B%22goodness_of_fit%22%5D%0A%20%20%20%20fit_params_noqc%20%3D%20(%0A%20%20%20%20%20%20%20%20frac_infectivity_2.groupby(%5B%22serum_replicate%22%2C%20%22barcode%22%5D%2C%20as_index%3DFalse)%0A%20%20%20%20%20%20%20%20.aggregate(max_frac_infectivity%3Dpd.NamedAgg(%22frac_infectivity_ceiling%22%2C%20%22max%22))%0A%20%20%20%20%20%20%20%20.merge(%0A%20%20%20%20%20%20%20%20%20%20%20%20fits_noqc.fitParams(average_only%3DFalse%2C%20no_average%3DTrue)%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%5B%22serum%22%2C%20%22virus%22%2C%20%22replicate%22%2C%20%22r2%22%2C%20%22rmsd%22%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%5D.rename(columns%3D%7B%22serum%22%3A%20%22serum_replicate%22%2C%20%22replicate%22%3A%20%22barcode%22%7D)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20validate%3D%22one_to_one%22%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20.assign(%0A%20%20%20%20%20%20%20%20%20%20%20%20fails_max_frac_infectivity_at_least%3Dlambda%20x%3A%20(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20x%5B%22max_frac_infectivity%22%5D%20%3C%20curvefit_qc%5B%22max_frac_infectivity_at_least%22%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20fails_goodness_of_fit%3Dlambda%20x%3A%20(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20(x%5B%22r2%22%5D%20%3C%20goodness_of_fit%5B%22min_R2%22%5D)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%26%20(x%5B%22rmsd%22%5D%20%3E%20goodness_of_fit%5B%22max_RMSD%22%5D)%0A%20%20%20%20%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20fails_qc%3Dlambda%20x%3A%20(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20x%5B%22fails_max_frac_infectivity_at_least%22%5D%20%7C%20x%5B%22fails_goodness_of_fit%22%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20ignore_qc%3Dlambda%20x%3A%20x.apply(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20lambda%20r%3A%20(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20r%5B%22serum_replicate%22%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20in%20curvefit_qc%5B%22serum_replicates_ignore_curvefit_qc%22%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20or%20(r%5B%22barcode%22%5D%2C%20r%5B%22serum_replicate%22%5D)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20in%20curvefit_qc%5B%22barcode_serum_replicates_ignore_curvefit_qc%22%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20axis%3D1%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20)%0A%20%20%20%20return%20(fit_params_noqc%2C)%0A%0A%0A%40app.cell%0Adef%20_(alt%2C%20barcode_selection%2C%20curvefit_qc%2C%20fit_params_noqc%2C%20mo)%3A%0A%20%20%20%20mo.output.append(%0A%20%20%20%20%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22Plotting%20barcode%20%2F%20serum-replicates%20that%20fail%20%60curvefit_qc%3D%7Bcurvefit_qc!r%7D%60%22%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20)%0A%0A%20%20%20%20fit_params_noqc_base_chart%20%3D%20alt.Chart(fit_params_noqc).add_params(%0A%20%20%20%20%20%20%20%20barcode_selection%0A%20%20%20%20)%0A%20%20%20%20for%20prop%2C%20col%20in%20%5B%0A%20%20%20%20%20%20%20%20(%22max%20frac%20infectivity%22%2C%20%22max_frac_infectivity%22)%2C%0A%20%20%20%20%20%20%20%20(%22curve%20fit%20R2%22%2C%20%22r2%22)%2C%0A%20%20%20%20%20%20%20%20(%22curve%20fit%20RMSD%22%2C%20%22rmsd%22)%2C%0A%20%20%20%20%5D%3A%0A%20%20%20%20%20%20%20%20chart%20%3D%20(%0A%20%20%20%20%20%20%20%20%20%20%20%20fit_params_noqc_base_chart.encode(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20alt.X(col%2C%20title%3Dprop%2C%20scale%3Dalt.Scale(nice%3DFalse%2C%20padding%3D4))%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20alt.Y(%22virus%22%2C%20title%3DNone)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20alt.Fill(%22fails_qc%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20alt.Column(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22serum_replicate%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20title%3DNone%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20header%3Dalt.Header(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20labelFontSize%3D12%2C%20labelFontStyle%3D%22bold%22%2C%20labelPadding%3D0%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20strokeWidth%3Dalt.condition(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20barcode_selection%2C%20alt.value(2)%2C%20alt.value(0)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20size%3Dalt.condition(barcode_selection%2C%20alt.value(55)%2C%20alt.value(35))%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20tooltip%3D%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20alt.Tooltip(c%2C%20format%3D%22.3g%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20fit_params_noqc%5Bc%5D.dtype%20%3D%3D%20float%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20else%20c%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20for%20c%20in%20fit_params_noqc.columns%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20.mark_circle(stroke%3D%22black%22%2C%20strokeOpacity%3D1%2C%20fillOpacity%3D0.55)%0A%20%20%20%20%20%20%20%20%20%20%20%20.properties(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20height%3Dalt.Step(10)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20width%3D90%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20title%3Dalt.TitleParams(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f%22%7Bprop%7D%20for%20each%20barcode%20serum-replicate%22%2C%20dy%3D-2%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20)%0A%0A%20%20%20%20%20%20%20%20%23%20Display%20the%20chart%0A%20%20%20%20%20%20%20%20mo.output.append(chart)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20Now%20plot%20curves%20for%20all%20virus%20vs%20serum-replicates%20that%20have%20a%20barcode%20that%20fails%20any%20of%20the%20QC.%0A%20%20%20%20In%20these%20plots%2C%20the%20suffix%20on%20the%20barcode%20name%20in%20the%20color%20key%20indicates%20if%20it%20passed%20or%20failed%20QC%3A%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(%0A%20%20%20%20CBMARKERS%2C%0A%20%20%20%20CBPALETTE%2C%0A%20%20%20%20curve_display_method%2C%0A%20%20%20%20curvefit_qc%2C%0A%20%20%20%20display_fig_marimo%2C%0A%20%20%20%20fit_params_noqc%2C%0A%20%20%20%20fits_noqc%2C%0A%20%20%20%20mo%2C%0A%20%20%20%20pd%2C%0A)%3A%0A%20%20%20%20barcode_serum_replicates_fail_qc%20%3D%20fit_params_noqc.query(%22fails_qc%22).reset_index(%0A%20%20%20%20%20%20%20%20drop%3DTrue%0A%20%20%20%20)%0A%0A%20%20%20%20if%20len(barcode_serum_replicates_fail_qc)%3A%0A%20%20%20%20%20%20%20%20mo.output.append(%0A%20%20%20%20%20%20%20%20%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f%22Here%20are%20barcode%20%2F%20serum-replicates%20that%20fail%20%60curvefit_qc%3D%7Bcurvefit_qc!r%7D%60%22%0A%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20mo.output.append(barcode_serum_replicates_fail_qc)%0A%20%20%20%20%20%20%20%20mo.output.append(%0A%20%20%20%20%20%20%20%20%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22Curves%20for%20virus%20vs%20serum-replicates%20with%20at%20least%20one%20failed%20barcode.%5Cn%5CnColor%20key%20labels%20indicate%20if%20barcodes%20failed%20or%20passed%20QC.%22%0A%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20plots%20%3D%20%7B%7D%0A%20%20%20%20%20%20%20%20ncol%20%3D%206%0A%20%20%20%20%20%20%20%20for%20iplot%2C%20(serum%2C%20virus%2C%20failed_barcodes)%20in%20enumerate(%0A%20%20%20%20%20%20%20%20%20%20%20%20barcode_serum_replicates_fail_qc.groupby(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%5B%22serum_replicate%22%2C%20%22virus%22%5D%2C%20as_index%3DFalse%0A%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20.aggregate(barcodes%3Dpd.NamedAgg(%22barcode%22%2C%20list))%0A%20%20%20%20%20%20%20%20%20%20%20%20.itertuples(index%3DFalse)%0A%20%20%20%20%20%20%20%20)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20passed_barcodes%20%3D%20%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20bc%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20for%20bc%20in%20fits_noqc.replicates%5Bserum%2C%20virus%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20bc%20not%20in%20failed_barcodes%20and%20bc%20!%3D%20%22average%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20curvelist%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20assert%20len(CBMARKERS)%20%3E%3D%20len(failed_barcodes%20%2B%20passed_barcodes)%0A%20%20%20%20%20%20%20%20%20%20%20%20assert%20len(CBPALETTE)%20%3E%3D%20len(failed_barcodes%20%2B%20passed_barcodes)%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20replicate%2C%20marker%2C%20color%20in%20zip(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20failed_barcodes%20%2B%20passed_barcodes%2C%20CBMARKERS%2C%20CBPALETTE%0A%20%20%20%20%20%20%20%20%20%20%20%20)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20curvelist.append(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22serum%22%3A%20serum%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22virus%22%3A%20virus%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22replicate%22%3A%20replicate%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22label%22%3A%20replicate%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%2B%20(%22-fail%22%20if%20replicate%20in%20failed_barcodes%20else%20%22-pass%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22color%22%3A%20color%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22marker%22%3A%20marker%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20plots%5Biplot%20%2F%2F%20ncol%2C%20iplot%20%25%20ncol%5D%20%3D%20(f%22%7Bserum%7D%20vs%20%7Bvirus%7D%22%2C%20curvelist)%0A%20%20%20%20%20%20%20%20_fig_fail_qc%2C%20_%20%3D%20fits_noqc.plotGrid(%0A%20%20%20%20%20%20%20%20%20%20%20%20plots%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20attempt_shared_legend%3DFalse%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20legendfontsize%3D8%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20titlesize%3D9%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20ticksize%3D10%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20draw_in_bounds%3DTrue%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20mo.output.append(%0A%20%20%20%20%20%20%20%20%20%20%20%20display_fig_marimo(_fig_fail_qc%2C%20display_method%3Dcurve_display_method)%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20mo.output.append(mo.md(%22No%20serum-replicates%20fail%20QC.%22))%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(curvefit_qc%2C%20fit_params_noqc%2C%20frac_infectivity_2%2C%20mo%2C%20qc_drops)%3A%0A%20%20%20%20%23%20drop%20barcode%20%2F%20serum-replicates%20failing%20QC%0A%20%20%20%20frac_infectivity_3%20%3D%20frac_infectivity_2.copy()%0A%20%20%20%20fit_params_noqc_1%20%3D%20fit_params_noqc.copy()%0A%20%20%20%20for%20qc_filter%20in%20%5B%22max_frac_infectivity_at_least%22%2C%20%22goodness_of_fit%22%5D%3A%0A%20%20%20%20%20%20%20%20fits_qc_drops%20%3D%20list(%0A%20%20%20%20%20%20%20%20%20%20%20%20fit_params_noqc_1.query(f%22fails_%7Bqc_filter%7D%20and%20(not%20ignore_qc)%22)%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%5B%22barcode%22%2C%20%22serum_replicate%22%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%5D.itertuples(index%3DFalse%2C%20name%3DNone)%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20mo.output.append(%0A%20%20%20%20%20%20%20%20%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f%22Dropping%20%7Blen(fits_qc_drops)%7D%20barcode%2Fserum-replicates%20for%20failing%20%60%7Bqc_filter%7D%3D%7Bcurvefit_qc%5Bqc_filter%5D%7D%60%3A%20%7Bfits_qc_drops%7D%22%0A%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20qc_drops%5B%22barcode_serum_replicates%22%5D.update(%0A%20%20%20%20%20%20%20%20%20%20%20%20%7Bw%3A%20qc_filter%20for%20w%20in%20fits_qc_drops%7D%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20frac_infectivity_3%20%3D%20frac_infectivity_3%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20~frac_infectivity_3.assign(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20barcode_serum_replicate%3Dlambda%20x%3A%20x.apply(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20lambda%20r%3A%20(r%5B%22barcode%22%5D%2C%20r%5B%22serum_replicate%22%5D)%2C%20axis%3D1%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20)%5B%22barcode_serum_replicate%22%5D.isin(qc_drops%5B%22barcode_serum_replicates%22%5D)%0A%20%20%20%20%20%20%20%20%5D%0A%20%20%20%20%20%20%20%20fit_params_noqc_1%20%3D%20fit_params_noqc_1%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20~fit_params_noqc_1.assign(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20barcode_serum_replicate%3Dlambda%20x%3A%20x.apply(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20lambda%20r%3A%20(r%5B%22barcode%22%5D%2C%20r%5B%22serum_replicate%22%5D)%2C%20axis%3D1%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20)%5B%22barcode_serum_replicate%22%5D.isin(qc_drops%5B%22barcode_serum_replicates%22%5D)%0A%20%20%20%20%20%20%20%20%5D%0A%20%20%20%20return%20(frac_infectivity_3%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20Fit%20neutralization%20curves%20after%20applying%20QC%0A%20%20%20%20No%20we%20re-fit%20and%20plot%20curves%20after%20applying%20all%20the%20QC.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(curvefit_params%2C%20fits_noqc%2C%20frac_infectivity_3%2C%20group%2C%20mo%2C%20neutcurve)%3A%0A%20%20%20%20fits_qc%20%3D%20neutcurve.CurveFits(%0A%20%20%20%20%20%20%20%20frac_infectivity_3.rename(%0A%20%20%20%20%20%20%20%20%20%20%20%20columns%3D%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22frac_infectivity_ceiling%22%3A%20%22fraction%20infectivity%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22concentration%22%3A%20%22serum%20concentration%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20conc_col%3D%22serum%20concentration%22%2C%0A%20%20%20%20%20%20%20%20fracinf_col%3D%22fraction%20infectivity%22%2C%0A%20%20%20%20%20%20%20%20virus_col%3D%22strain%22%2C%0A%20%20%20%20%20%20%20%20serum_col%3D%22serum%22%2C%0A%20%20%20%20%20%20%20%20replicate_col%3D%22plate_barcode%22%2C%0A%20%20%20%20%20%20%20%20fixtop%3Dcurvefit_params%5B%22fixtop%22%5D%2C%0A%20%20%20%20%20%20%20%20fixbottom%3Dcurvefit_params%5B%22fixbottom%22%5D%2C%0A%20%20%20%20%20%20%20%20fixslope%3Dcurvefit_params%5B%22fixslope%22%5D%2C%0A%20%20%20%20)%0A%20%20%20%20fit_params_qc%20%3D%20fits_qc.fitParams(average_only%3DFalse%2C%20no_average%3DTrue)%0A%20%20%20%20assert%20len(fit_params_qc)%20%3C%3D%20len(%0A%20%20%20%20%20%20%20%20fits_noqc.fitParams(average_only%3DFalse%2C%20no_average%3DTrue)%0A%20%20%20%20)%0A%20%20%20%20mo.output.append(mo.md(f%22Assigning%20fits%20for%20this%20plate%20to%20%60%7Bgroup%7D%60%22))%0A%20%20%20%20fit_params_qc.insert(0%2C%20%22group%22%2C%20group)%0A%20%20%20%20return%20fit_params_qc%2C%20fits_qc%0A%0A%0A%40app.cell%0Adef%20_(curve_display_method%2C%20display_fig_marimo%2C%20fits_qc%2C%20mo)%3A%0A%20%20%20%20if%20fits_qc.sera%3A%0A%20%20%20%20%20%20%20%20_fig_passed_qc%2C%20_%20%3D%20fits_qc.plotReplicates(%0A%20%20%20%20%20%20%20%20%20%20%20%20attempt_shared_legend%3DFalse%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20legendfontsize%3D8%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20titlesize%3D9%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20ticksize%3D10%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20ncol%3D6%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20draw_in_bounds%3DTrue%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20mo.output.append(%0A%20%20%20%20%20%20%20%20%20%20%20%20display_fig_marimo(_fig_passed_qc%2C%20display_method%3Dcurve_display_method)%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20mo.output.append(mo.md(%22No%20sera%20passed%20QC.%22))%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20Save%20results%20to%20files%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(%0A%20%20%20%20fit_params_qc%2C%0A%20%20%20%20fits_csv%2C%0A%20%20%20%20fits_pickle%2C%0A%20%20%20%20fits_qc%2C%0A%20%20%20%20frac_infectivity_3%2C%0A%20%20%20%20frac_infectivity_csv%2C%0A%20%20%20%20io%2C%0A%20%20%20%20mo%2C%0A%20%20%20%20pickle%2C%0A%20%20%20%20qc_drops%2C%0A%20%20%20%20qc_drops_yaml%2C%0A%20%20%20%20yaml%2C%0A)%3A%0A%20%20%20%20mo.output.append(%0A%20%20%20%20%20%20%20%20mo.md(f%22Writing%20fraction%20infectivities%20to%20%60%7Bfrac_infectivity_csv%7D%60%22)%0A%20%20%20%20)%0A%20%20%20%20(%0A%20%20%20%20%20%20%20%20frac_infectivity_3%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22serum%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22strain%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22plate_barcode%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22dilution_factor%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22frac_infectivity_raw%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22frac_infectivity_ceiling%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%5D%0A%20%20%20%20%20%20%20%20%5D%0A%20%20%20%20%20%20%20%20.sort_values(%5B%22serum%22%2C%20%22plate_barcode%22%2C%20%22dilution_factor%22%5D)%0A%20%20%20%20%20%20%20%20.to_csv(frac_infectivity_csv%2C%20index%3DFalse%2C%20float_format%3D%22%25.4g%22)%0A%20%20%20%20)%0A%20%20%20%20mo.output.append(mo.md(f%22Writing%20fit%20parameters%20to%20%60%7Bfits_csv%7D%60%22))%0A%20%20%20%20fit_params_qc.drop(columns%3D%5B%22nreplicates%22%2C%20%22ic50_str%22%5D).to_csv(%0A%20%20%20%20%20%20%20%20fits_csv%2C%20index%3DFalse%2C%20float_format%3D%22%25.4g%22%0A%20%20%20%20)%0A%20%20%20%20mo.output.append(%0A%20%20%20%20%20%20%20%20mo.md(f%22Pickling%20neutcurve.CurveFits%20object%20for%20these%20data%20to%20%60%7Bfits_pickle%7D%60%22)%0A%20%20%20%20)%0A%20%20%20%20with%20open(fits_pickle%2C%20%22wb%22)%20as%20f_pickle%3A%0A%20%20%20%20%20%20%20%20pickle.dump(fits_qc%2C%20f_pickle)%0A%20%20%20%20mo.output.append(mo.md(f%22Writing%20QC%20drops%20to%20%60%7Bqc_drops_yaml%7D%60%22))%0A%0A%20%20%20%20def%20tup_to_str(x)%3A%0A%20%20%20%20%20%20%20%20return%20%22%20%22.join(x)%20if%20isinstance(x%2C%20tuple)%20else%20x%0A%0A%20%20%20%20qc_drops_for_yaml%20%3D%20%7B%0A%20%20%20%20%20%20%20%20key%3A%20%7Btup_to_str(key2)%3A%20val2%20for%20key2%2C%20val2%20in%20val.items()%7D%0A%20%20%20%20%20%20%20%20for%20key%2C%20val%20in%20qc_drops.items()%0A%20%20%20%20%7D%0A%20%20%20%20with%20open(qc_drops_yaml%2C%20%22w%22)%20as%20f_yaml%3A%0A%20%20%20%20%20%20%20%20yaml.YAML(typ%3D%22rt%22).dump(qc_drops_for_yaml%2C%20f_yaml)%0A%20%20%20%20mo.output.append(mo.md(%22Here%20are%20the%20QC%20drops%3A%22))%0A%20%20%20%20yaml_buffer_qc_drops%20%3D%20io.StringIO()%0A%20%20%20%20yaml.YAML(typ%3D%22rt%22).dump(qc_drops_for_yaml%2C%20stream%3Dyaml_buffer_qc_drops)%0A%20%20%20%20mo.output.append(mo.md(f%22%60%60%60yaml%5Cn%7Byaml_buffer_qc_drops.getvalue()%7D%60%60%60%22))%0A%20%20%20%20return%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20app.run()%0A
354bd526b3aab209ae77dfba1519788ce90d4fe564e40901dfa657c44b3e1478