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%0Adef%20_()%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%20%20%20%20import%20pickle%0A%20%20%20%20import%20sys%0A%0A%20%20%20%20import%20marimo%20as%20mo%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%20set%20%60context_pickle_path%60%20to%20valid%20pickle%20if%20running%20via%20marimo%20edit%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%2Fqc_drops%2Faggregate_qc_drops_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%20context%2C%20mo%0A%0A%0A%40app.cell%0Adef%20_(context%2C%20mo)%3A%0A%20%20%20%20%23%20Extract%20variables%20from%20context%20-%20raises%20KeyError%20if%20required%20keys%20missing%0A%20%20%20%20input_plate_qc_drops%20%3D%20context%5B%22input%22%5D%5B%22plate_qc_drops%22%5D%0A%20%20%20%20input_groups_sera_qc_drops%20%3D%20context%5B%22input%22%5D%5B%22groups_sera_qc_drops%22%5D%0A%20%20%20%20output_plate_qc_drops%20%3D%20context%5B%22output%22%5D%5B%22plate_qc_drops%22%5D%0A%20%20%20%20output_barcode_qc_drops%20%3D%20context%5B%22output%22%5D%5B%22barcode_qc_drops%22%5D%0A%20%20%20%20output_groups_sera_qc_drops%20%3D%20context%5B%22output%22%5D%5B%22groups_sera_qc_drops%22%5D%0A%20%20%20%20plates%20%3D%20context%5B%22params%22%5D%5B%22plates%22%5D%0A%20%20%20%20groups_sera%20%3D%20context%5B%22params%22%5D%5B%22groups_sera%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%20%20%20%20return%20(%0A%20%20%20%20%20%20%20%20groups_sera%2C%0A%20%20%20%20%20%20%20%20input_groups_sera_qc_drops%2C%0A%20%20%20%20%20%20%20%20input_plate_qc_drops%2C%0A%20%20%20%20%20%20%20%20output_barcode_qc_drops%2C%0A%20%20%20%20%20%20%20%20output_groups_sera_qc_drops%2C%0A%20%20%20%20%20%20%20%20output_plate_qc_drops%2C%0A%20%20%20%20%20%20%20%20plates%2C%0A%20%20%20%20)%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%20Aggregate%20and%20analyze%20the%20drops%20from%20QC-ing%20the%20plates%20and%20sera%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%20import%20altair%20as%20alt%0A%0A%20%20%20%20import%20pandas%20as%20pd%0A%0A%20%20%20%20from%20ruamel.yaml%20import%20YAML%0A%0A%20%20%20%20yaml%20%3D%20YAML(typ%3D%22rt%22)%0A%0A%20%20%20%20_%20%3D%20alt.data_transformers.disable_max_rows()%0A%20%20%20%20return%20alt%2C%20pd%2C%20yaml%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%20Analyze%20plate%20QC%20drops%0A%20%20%20%20Read%20QC%20drops%20for%20individual%20plates%20into%20a%20merged%20dictionary%2C%20write%20it%20to%20YAML%2C%20and%20also%20convert%20to%20a%20DataFrame.%0A%20%20%20%20If%20you%20really%20want%20to%20look%20into%20the%20details%20of%20what%20is%20being%20dropped%2C%20you%20will%20want%20to%20look%20at%20that%20merged%20YAML%20file.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(input_plate_qc_drops%2C%20mo%2C%20output_plate_qc_drops%2C%20pd%2C%20plates%2C%20yaml)%3A%0A%20%20%20%20%23%20read%20dictionary%20of%20QC%20drops%0A%20%20%20%20assert%20len(plates)%20%3D%3D%20len(input_plate_qc_drops)%0A%20%20%20%20plate_qc_drops%20%3D%20%7B%7D%0A%20%20%20%20for%20_plate%2C%20_qc_drops_yaml%20in%20zip(plates%2C%20input_plate_qc_drops)%3A%0A%20%20%20%20%20%20%20%20with%20open(_qc_drops_yaml)%20as%20_f%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20plate_qc_drops%5B_plate%5D%20%3D%20yaml.load(_f)%0A%20%20%20%20assert%20len(plate_qc_drops)%20%3D%3D%20len(input_plate_qc_drops)%0A%0A%20%20%20%20mo.output.append(mo.md(f%22Writing%20merged%20plate%20drops%20to%20%7Boutput_plate_qc_drops%7D%22))%0A%20%20%20%20with%20open(output_plate_qc_drops%2C%20%22w%22)%20as%20_f%3A%0A%20%20%20%20%20%20%20%20yaml.dump(plate_qc_drops%2C%20stream%3D_f)%0A%0A%20%20%20%20%23%20convert%20dictionary%20of%20QC%20drops%20into%20list%20of%20tuples%0A%20%20%20%20plate_qc_drop_tups%20%3D%20%5B%0A%20%20%20%20%20%20%20%20(plate_key%2C%20droptype_key%2C%20drop_key%2C%20reason)%0A%20%20%20%20%20%20%20%20for%20plate_key%2C%20plate_val%20in%20plate_qc_drops.items()%0A%20%20%20%20%20%20%20%20for%20droptype_key%2C%20droptype_val%20in%20plate_val.items()%0A%20%20%20%20%20%20%20%20for%20drop_key%2C%20reason%20in%20droptype_val.items()%0A%20%20%20%20%5D%0A%0A%20%20%20%20%23%20create%20data%20frame%20of%20QC%20drops%0A%20%20%20%20plate_qc_drops_df%20%3D%20pd.DataFrame(%0A%20%20%20%20%20%20%20%20plate_qc_drop_tups%2C%20columns%3D%5B%22plate%22%2C%20%22drop%20type%22%2C%20%22drop%22%2C%20%22reason%22%5D%0A%20%20%20%20)%0A%20%20%20%20return%20plate_qc_drops%2C%20plate_qc_drops_df%0A%0A%0A%40app.cell%0Adef%20_(pd%2C%20plate_qc_drops_df)%3A%0A%20%20%20%20plate_qc_drop_counts%20%3D%20plate_qc_drops_df.groupby(%0A%20%20%20%20%20%20%20%20%5B%22plate%22%2C%20%22drop%20type%22%2C%20%22reason%22%5D%2C%20as_index%3DFalse%0A%20%20%20%20).aggregate(n_drops%3Dpd.NamedAgg(%22drop%22%2C%20%22nunique%22))%0A%20%20%20%20assert%20plate_qc_drop_counts%5B%22n_drops%22%5D.sum()%20%3D%3D%20len(plate_qc_drops_df)%0A%20%20%20%20return%20(plate_qc_drop_counts%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%20Now%20plot%20the%20number%20of%20drops%20for%20each%20plate.%0A%20%20%20%20You%20should%20be%20worried%20(maybe%20re-do%20or%20discard)%20any%20plates%20with%20a%20very%20large%20number%20of%20drops%3A%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(alt%2C%20plate_qc_drop_counts%2C%20plates)%3A%0A%20%20%20%20plate_selection%20%3D%20alt.selection_point(fields%3D%5B%22plate%22%5D%2C%20on%3D%22mouseover%22%2C%20empty%3DFalse)%0A%0A%20%20%20%20plate_qc_drop_counts_chart%20%3D%20(%0A%20%20%20%20%20%20%20%20alt.Chart(plate_qc_drop_counts)%0A%20%20%20%20%20%20%20%20.add_params(plate_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%22n_drops%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20title%3D%22number%20of%20drops%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%22plate%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20sort%3Dplates%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%20axis%3Dalt.Axis(labelFontStyle%3D%22bold%22%2C%20labelFontSize%3D11)%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%22drop%20type%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%3D5%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%20labelFontStyle%3D%22bold%22%2C%20labelPadding%3D1%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.Color(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22reason%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20legend%3Dalt.Legend(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20orient%3D%22top%22%2C%20columns%3D1%2C%20labelLimit%3D230%2C%20title%3DNone%2C%20padding%3D1%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%20strokeWidth%3Dalt.condition(plate_selection%2C%20alt.value(3)%2C%20alt.value(0.5))%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20tooltip%3Dplate_qc_drop_counts.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.8%7D%2C%20stroke%3D%22black%22)%0A%20%20%20%20%20%20%20%20.properties(%0A%20%20%20%20%20%20%20%20%20%20%20%20width%3D230%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20height%3Dalt.Step(16)%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%20QC%20drops%20when%20processing%20plates%22%2C%20anchor%3D%22middle%22%2C%20dy%3D-2%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.configure_axis(grid%3DFalse)%0A%20%20%20%20%20%20%20%20.resolve_scale(color%3D%22independent%22%2C%20x%3D%22independent%22)%0A%20%20%20%20)%0A%0A%20%20%20%20plate_qc_drop_counts_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%20%23%23%20Analyze%20barcode%20QC%20drops%0A%20%20%20%20If%20a%20barcode%20is%20dropped%20especially%20often%20across%20plates%2C%20that%20could%20indicate%20something%20problematic%20with%20that%20barcode%20such%20that%20it%20should%20be%20removed%20altogether%20from%20the%20library%20analysis.%0A%0A%20%20%20%20First%2C%20write%20a%20YAML%20with%20this%20information%3A%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(mo%2C%20output_barcode_qc_drops%2C%20plate_qc_drops%2C%20yaml)%3A%0A%20%20%20%20barcode_qc_drops%20%3D%20%7B%7D%0A%0A%20%20%20%20for%20_plate%2C%20plate_d%20in%20plate_qc_drops.items()%3A%0A%20%20%20%20%20%20%20%20for%20key%2C%20val%20in%20%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20tup%20for%20tup%20in%20plate_d.items()%20if%20tup%5B0%5D.startswith(%22barcode%22)%20and%20tup%5B1%5D%0A%20%20%20%20%20%20%20%20%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20desc%2C%20reason%20in%20val.items()%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20desc_entries%20%3D%20desc.split(None%2C%20maxsplit%3D1)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20barcode%20%3D%20desc_entries%5B0%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20len(desc_entries)%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20description%20%3D%20f%22%7B_plate%7D%20%7Bdesc_entries%5B1%5D%7D%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20elif%20len(desc_entries)%20%3D%3D%201%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20description%20%3D%20_plate%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20raise%20RuntimeError(%22should%20not%20get%20here%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20barcode%20not%20in%20barcode_qc_drops%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20barcode_qc_drops%5Bbarcode%5D%20%3D%20%7B%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20key%20not%20in%20barcode_qc_drops%5Bbarcode%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20barcode_qc_drops%5Bbarcode%5D%5Bkey%5D%20%3D%20%7B%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20barcode_qc_drops%5Bbarcode%5D%5Bkey%5D%5Bdescription%5D%20%3D%20reason%0A%0A%20%20%20%20%23%20sort%20keys%0A%20%20%20%20def%20sort_nested(d)%3A%0A%20%20%20%20%20%20%20%20if%20isinstance(d%2C%20dict)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20dict(sorted((key%2C%20sort_nested(val))%20for%20key%2C%20val%20in%20d.items()))%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20d%0A%0A%20%20%20%20barcode_qc_drops%20%3D%20sort_nested(barcode_qc_drops)%0A%0A%20%20%20%20mo.output.append(%0A%20%20%20%20%20%20%20%20mo.md((f%22Writing%20merged%20barcode%20drops%20to%20%7Boutput_barcode_qc_drops%7D%22))%0A%20%20%20%20)%0A%20%20%20%20with%20open(output_barcode_qc_drops%2C%20%22w%22)%20as%20_f%3A%0A%20%20%20%20%20%20%20%20yaml.dump(barcode_qc_drops%2C%20stream%3D_f)%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%20make%20a%20plot%20showing%20how%20often%20each%20barcode%20is%20dropped%20for%20each%20reason%3A%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(pd%2C%20plate_qc_drops_df)%3A%0A%20%20%20%20barcode_drops%20%3D%20(%0A%20%20%20%20%20%20%20%20plate_qc_drops_df.query(%22%60drop%20type%60.str.startswith('barcode')%22)%0A%20%20%20%20%20%20%20%20.assign(barcode%3Dlambda%20x%3A%20x%5B%22drop%22%5D.str.split().str%5B0%5D)%0A%20%20%20%20%20%20%20%20.groupby(%5B%22drop%20type%22%2C%20%22barcode%22%5D%2C%20as_index%3DFalse)%0A%20%20%20%20%20%20%20%20.aggregate(%0A%20%20%20%20%20%20%20%20%20%20%20%20plates_where_dropped%3Dpd.NamedAgg(%22plate%22%2C%20%22nunique%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20total_drops%3Dpd.NamedAgg(%22plate%22%2C%20%22count%22)%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20)%0A%20%20%20%20return%20(barcode_drops%2C)%0A%0A%0A%40app.cell%0Adef%20_(alt%2C%20barcode_drops)%3A%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%20on%3D%22mouseover%22%2C%20empty%3DFalse%0A%20%20%20%20)%0A%0A%20%20%20%20barcode_drops_chart%20%3D%20(%0A%20%20%20%20%20%20%20%20alt.Chart(barcode_drops)%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%22total_drops%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20title%3D%22times%20barcode%20dropped%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%22barcode%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20sort%3Dalt.SortField(%22total_drops%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%3D9)%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%22drop%20type%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%3D8%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%20labelFontStyle%3D%22bold%22%2C%20labelPadding%3D1%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%20strokeWidth%3Dalt.condition(barcode_selection%2C%20alt.value(3)%2C%20alt.value(0.5))%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20tooltip%3Dbarcode_drops.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.8%7D%2C%20stroke%3D%22black%22)%0A%20%20%20%20%20%20%20%20.properties(%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%20height%3Dalt.Step(10)%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%20QC%20drops%20when%20processing%20plates%22%2C%20anchor%3D%22middle%22%2C%20dy%3D-2%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.configure_axis(grid%3DFalse)%0A%20%20%20%20%20%20%20%20.resolve_scale(color%3D%22independent%22%2C%20x%3D%22independent%22%2C%20y%3D%22independent%22)%0A%20%20%20%20)%0A%0A%20%20%20%20barcode_drops_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%20%23%23%20Analyze%20the%20groups%2Fsera%20QC%0A%20%20%20%20Analyze%20the%20QC%20performed%20on%20the%20groups%2Fsera%2C%20which%20involves%20completely%20dropping%20titers%20for%20certain%20virus-sera%20pairs.%0A%0A%20%20%20%20Read%20the%20QC%20for%20different%20groups%2Fsera%20into%20a%20merged%20dictionary%2C%20write%20it%20to%20YAML%2C%20and%20also%20convert%20to%20a%20DataFrame.%0A%20%20%20%20If%20you%20really%20want%20to%20look%20into%20the%20details%20of%20what%20is%20being%20dropped%2C%20you%20will%20want%20to%20look%20at%20that%20merged%20YAML%20file.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(%0A%20%20%20%20groups_sera%2C%0A%20%20%20%20input_groups_sera_qc_drops%2C%0A%20%20%20%20mo%2C%0A%20%20%20%20output_groups_sera_qc_drops%2C%0A%20%20%20%20pd%2C%0A%20%20%20%20yaml%2C%0A)%3A%0A%20%20%20%20%23%20read%20dictionary%20of%20QC%20drops%0A%20%20%20%20assert%20len(groups_sera)%20%3D%3D%20len(input_groups_sera_qc_drops)%0A%20%20%20%20groups_sera_qc_drops%20%3D%20%7B%7D%0A%20%20%20%20for%20(group%2C%20serum)%2C%20_qc_drops_yaml%20in%20zip(groups_sera%2C%20input_groups_sera_qc_drops)%3A%0A%20%20%20%20%20%20%20%20if%20group%20not%20in%20groups_sera_qc_drops%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20groups_sera_qc_drops%5Bgroup%5D%20%3D%20%7B%7D%0A%20%20%20%20%20%20%20%20with%20open(_qc_drops_yaml)%20as%20_f%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20groups_sera_qc_drops%5Bgroup%5D%5Bserum%5D%20%3D%20yaml.load(_f)%0A%0A%20%20%20%20mo.output.append(%0A%20%20%20%20%20%20%20%20mo.md(f%22Writing%20merged%20groups%2Fsera%20drops%20to%20%7Boutput_groups_sera_qc_drops%7D%22)%0A%20%20%20%20)%0A%20%20%20%20with%20open(output_groups_sera_qc_drops%2C%20%22w%22)%20as%20_f%3A%0A%20%20%20%20%20%20%20%20yaml.dump(groups_sera_qc_drops%2C%20stream%3D_f)%0A%0A%20%20%20%20%23%20convert%20dictionary%20of%20QC%20drops%20into%20list%20of%20tuples%0A%20%20%20%20groups_sera_qc_drop_tups%20%3D%20%5B%0A%20%20%20%20%20%20%20%20(group_key%2C%20serum_key%2C%20virus%2C%20reason)%0A%20%20%20%20%20%20%20%20for%20group_key%2C%20group_val%20in%20groups_sera_qc_drops.items()%0A%20%20%20%20%20%20%20%20for%20serum_key%2C%20serum_val%20in%20group_val.items()%0A%20%20%20%20%20%20%20%20for%20virus%2C%20reason%20in%20serum_val.items()%0A%20%20%20%20%5D%0A%0A%20%20%20%20%23%20create%20data%20frame%20of%20QC%20drops%0A%20%20%20%20groups_sera_qc_drops_df%20%3D%20pd.DataFrame(%0A%20%20%20%20%20%20%20%20groups_sera_qc_drop_tups%2C%20columns%3D%5B%22group%22%2C%20%22serum%22%2C%20%22virus%22%2C%20%22reason%22%5D%0A%20%20%20%20)%0A%20%20%20%20return%20(groups_sera_qc_drops_df%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%20Plot%20the%20number%20of%20viruses%20dropped%20for%20each%20group%2Fserum.%0A%20%20%20%20If%20a%20group%2Fserum%20has%20many%20missed%20viruses%2C%20then%20you%20will%20lack%20a%20lot%20of%20titers%20and%20so%20it%20may%20be%20worth%20reviewing%20the%20cause%20of%20the%20drops.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(alt%2C%20groups_sera_qc_drops_df%2C%20pd)%3A%0A%20%20%20%20groups_sera_n_drops%20%3D%20groups_sera_qc_drops_df.groupby(%0A%20%20%20%20%20%20%20%20%5B%22group%22%2C%20%22serum%22%2C%20%22reason%22%5D%2C%20as_index%3DFalse%0A%20%20%20%20).aggregate(n_viruses%3Dpd.NamedAgg(%22virus%22%2C%20%22nunique%22))%0A%20%20%20%20assert%20groups_sera_n_drops%5B%22n_viruses%22%5D.sum()%20%3D%3D%20len(groups_sera_qc_drops_df)%0A%0A%20%20%20%20groups_sera_n_drops_chart%20%3D%20(%0A%20%20%20%20%20%20%20%20alt.Chart(groups_sera_n_drops)%0A%20%20%20%20%20%20%20%20.encode(%0A%20%20%20%20%20%20%20%20%20%20%20%20alt.X(%22n_viruses%22%2C%20title%3D%22number%20of%20viruses%20dropped%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20alt.Y(%22serum%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20alt.Row(%22group%22)%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%22reason%22%2C%20title%3D%22reason%20dropped%22%2C%20legend%3Dalt.Legend(labelLimit%3D350)%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%3Dgroups_sera_n_drops.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.8%7D)%0A%20%20%20%20%20%20%20%20.properties(%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%20height%3Dalt.Step(13)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20title%3D%22Number%20of%20viruses%20dropped%20at%20serum%20QC%20for%20each%20serum%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.resolve_scale(y%3D%22independent%22%2C%20x%3D%22independent%22)%0A%20%20%20%20)%0A%0A%20%20%20%20groups_sera_n_drops_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%20Plot%20the%20number%20of%20sera%20for%20which%20each%20virus%20is%20dropped%20during%20serum%20QC.%0A%20%20%20%20If%20a%20virus%20is%20dropped%20for%20many%20sera%2C%20that%20may%20indicate%20some%20issue%20with%20that%20virus%20in%20assays%3A%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(alt%2C%20groups_sera_qc_drops_df%2C%20pd)%3A%0A%20%20%20%20virus_n_drops%20%3D%20groups_sera_qc_drops_df.groupby(%0A%20%20%20%20%20%20%20%20%5B%22group%22%2C%20%22virus%22%2C%20%22reason%22%5D%2C%20as_index%3DFalse%0A%20%20%20%20).aggregate(n_sera%3Dpd.NamedAgg(%22serum%22%2C%20%22nunique%22))%0A%20%20%20%20assert%20virus_n_drops%5B%22n_sera%22%5D.sum()%20%3D%3D%20len(groups_sera_qc_drops_df)%0A%0A%20%20%20%20virus_n_drops_chart%20%3D%20(%0A%20%20%20%20%20%20%20%20alt.Chart(virus_n_drops)%0A%20%20%20%20%20%20%20%20.encode(%0A%20%20%20%20%20%20%20%20%20%20%20%20alt.X(%22n_sera%22%2C%20title%3D%22number%20of%20sera%20for%20which%20virus%20is%20dropped%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20alt.Y(%22virus%22%2C%20sort%3Dalt.SortField(%22n_sera%22%2C%20order%3D%22descending%22))%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20alt.Row(%22group%22)%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%22reason%22%2C%20title%3D%22reason%20dropped%22%2C%20legend%3Dalt.Legend(labelLimit%3D350)%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%3Dvirus_n_drops.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.8%7D)%0A%20%20%20%20%20%20%20%20.properties(%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%20height%3Dalt.Step(13)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20title%3D%22Number%20of%20sera%20for%20which%20each%20virus%20is%20dropped%20at%20serum%20QC%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.resolve_scale(y%3D%22independent%22%2C%20x%3D%22independent%22)%0A%20%20%20%20)%0A%0A%20%20%20%20virus_n_drops_chart%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
09d250e083ded6e182dcb10bd7194c70dcd2767f9e21d0b2b86639d99559525a