From 045050f6bf46da23f93761d1a2df34628da237ce Mon Sep 17 00:00:00 2001 From: rpotter6298 Date: Mon, 8 Jun 2026 14:42:29 +0200 Subject: [PATCH] Add new Excel report BEA25P077_RP.xlsx with multiple worksheets and styles --- Pilot/BEA25P077_RP.xlsx | Bin 0 -> 15169 bytes Pilot/code/generate_sequencing_plan.py | 88 +++ Pilot/code/miRNA_high_expression.py | 589 +++++++++++++++++++++ Pilot/code/mirna_threshold_analysis.py | 256 +++++++++ Pilot/code/mirna_threshold_unified.py | 370 +++++++++++++ Pilot/code/qc_threshold_analysis.py | 62 +++ Pilot/miRNA_counts.xlsx | Bin 0 -> 23305 bytes Pilot/mirna_threshold_summary.xlsx | Bin 0 -> 6236 bytes Pilot/mirna_threshold_unified_summary.xlsx | Bin 0 -> 6482 bytes Pilot/patients.xlsx | Bin 0 -> 12106 bytes Pilot/sequencing_plan.xlsx | Bin 0 -> 8251 bytes QC/BEA25P077_RP.xlsx | Bin 0 -> 15169 bytes 12 files changed, 1365 insertions(+) create mode 100644 Pilot/BEA25P077_RP.xlsx create mode 100644 Pilot/code/generate_sequencing_plan.py create mode 100644 Pilot/code/miRNA_high_expression.py create mode 100644 Pilot/code/mirna_threshold_analysis.py create mode 100644 Pilot/code/mirna_threshold_unified.py create mode 100644 Pilot/code/qc_threshold_analysis.py create mode 100644 Pilot/miRNA_counts.xlsx create mode 100644 Pilot/mirna_threshold_summary.xlsx create mode 100644 Pilot/mirna_threshold_unified_summary.xlsx create mode 100644 Pilot/patients.xlsx create mode 100644 Pilot/sequencing_plan.xlsx create mode 100644 QC/BEA25P077_RP.xlsx diff --git a/Pilot/BEA25P077_RP.xlsx b/Pilot/BEA25P077_RP.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..95df2503fe0160e80db0e35cd92e541ce3c755fa GIT binary patch literal 15169 zcmeHu1zQ~3vNplpU4sU9cXxO9!QEYhy9W&cfY{%2qXwJ2nYx<2ywJHxAQv?kZ1@H5Ht{IP#qC_I~RbR zi-D@A1Hf62-ow`BeI7U{WiALP@c;j}{V#5TKGk8n9!BJLjbj1P4)usY5j8b%M7>x# z)SqCoyu_Fhww{o%{u>cms%o^t?{5+*s}F2%W&NA1j!j|S#nd(*nh_)f&}Ff^rAB=l z_I@NoB?Hu#W{2pOzw38!@L%{`&S%&&hv}?@@?cI?U7V}*i3=%JLmCTL>>0=Gdzo>i z*?aHoiG12D@~ULzC}PML4a&K^!d=_`P7dk9fyiAz?YFNZAy*yjAtad8Y3gspQhHh$ zpmj8u2qNZ(V3aW!AZ)Jt!A1j&kRsfV9Fy|Fy0~)Dl-9}BJZm5Bcg&J6=h~~|=MBI+6eTV` zyC844#$S5+qND6bI~KSic%1CS|^GE&)$ntz0mF7u^mm!86d!-rm4K6#pYu>(v-Ze*!hKz)*w( z#;So6z{Z(@{@3UK#pwUy!2H{-mnX_8^fJPSUPwNN4c*PH#Ul&LxC=?P607ug49wNToW-X6GMdu-HaIXkWdvtV#qNZ?46?ZJ( z>O*mxyP3O9myq_PbZd{JE^8|OA~U=}Dn4^5T8%tOuYn7L_7y)AlP^6`dq7TW-T1!p z-JGz>Sw(17BS-FW(qyLpLTcd=fEqDo33lo%uciyA3FK0A!QO))O+Shi9zz*8-MLO*2~ds≺A-m;+|!$DC*#p}Sd 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

+-t9cZa*W$s?(d51%C zU+e6r=lRJqKV-#dbjMkCE0}gmSWk*eGlkCC*zDTLg>NATJ{NO|wPT4GzrRHV8(0BI zuXp9w9~3t1(y#Y4CbY`R_4(+rJSpm3DMK<x@mt|`5#IVrh1y~sFDVzOF03923psDvT>NQ>rvUS8F{(M+K5FYIMUOufW}#>gcjtx1y?}#isfGgne;D}ReE|JVI#Xn zB^g+$P7($Oo)?|u*Lk5RdNld2#m^L*CpT8i^@aO>IY{^~{}cWg$d)Z{@soJB@Fhdf z(89{oGAq%sG0}~39NhV?K(!tjZPU8f(b^9DijT3$m_QGknnG#YeQ&3M?S+E4>MPe? z_wmwgDM1^wf{);V^9|@@1ZE)-WZWj>r`JN<`c}r$G97KcJL`J zv}Tu_C=%RL(Ge@QdS7Js9BAK?Azab+>_O6Y6U0*S$G&G}B-H!y~uUCAZKbZwZX>k2xMgQa%I#w%Snc9($S$CD&qu;F$& z_c#B4tY}N}HbdK>KtPa5eo-s_m@Ra%0N4T;{`mZ3qVQCEJPxlBqZ4ZAO>j(ae(ccO zh-<2d;ZXA;#soYyQ zuakJjR%B%Rt6-wBI|Iw?1>fs_`{3Pryz^aB*{3gXBxHM13vlQ6ge1e!la@87UJ4XD z(kmj$Tc)u%3X%&tXh*(I7dvt1+h7<8pWeqS?7lnqN>Dx$s;4$*jNT*2Nd))PaX>MF zXf)Q!hVy~(GEqnH?bM24lH*O@#Gd8Xrag>SMGyD7V{egwr;tpZ1!JL@s53K$!9IQF zoq}#o1*4dPFw;XSOurt5X;vmQ)}+4wa`Lc0S+?G)(+|~*tm17d<79H3Ho6CTeLXcs zl{#})@Jea35M1F0W@v@QVRiCW!4K9|i^FR<@U@)9g7wAilZj03>Gvb#*5<)m~!>4zB zu-0tqSH|(%Lufg9TzYuM3pa50e}FNFF64&r?%h?qTPEv=&obWjFEJkOb6wY zAu}LIH@f6-3hKk(`Irq3YR9oaQXHmA1!6-?-4NSr~ z@~)XDYrF9kvUHOhQ2xXb^Oen}NS`XfkPMk3j_GEw#u9R<2W0LP6;Gz0l!Wc}i1*ZV zjS2Y)e*z3Vd3<#^Rp#nuqQQHHE*lcNvY|5rtV&Y<_?gHUEACA4Rc1dS;DC!6>(&^05(ju7);HiM-d3gKi!KiOKTuEM2hOecpDFSeZ9I#NC6IU}kU ziypw+>_W~}yNq3U4@lX07{C-LUHp+5!zL1JVs6V|9-(MQQJ_NLng!t5a0$j2M=C5T>xTUgTwy z7T5$nXhQovT>s=JDnD_g@k{eFH_+#5o8LR_eS!7PB-SS{0v{Q++itP7wbQ}7cNJsC|qR#{lz;AxzF0xh;}2fbK>UY~E(&(C2BBH%ilK3G;rFXLFBoACUW zvu-Y{9t*Oly?Wy!;k@?b@px3D=nbZzLl&)4Q&l5{ZIBMBApV0rOTyf9M^X3S6%T7s+Va(;{@HvN6Edq$yejny_S}ol&P} zX;e1CroU6pN-9KEgf@R{o8rXuHKK$BlnK|J@8x;L!7xIK1g9C1cFlmgS&^a10GI1W z%Nd|XGd`XAshu^CE_+QlaxyXGT7&fU){t&kWAnV$!Y`uGhMdp@x>}rOYCT&8W)Mnf z|ClS}y2_SA}Y{2%KwSR;*Q@WTO z%lddPx8*@Td;Q%aN^+@!bKF{kSdV2yBi+k;cRnlEkVV#1iXY?6o)0#e3ZI3puzRSs zBLPKgw1GS`+J!|u_iiHxC(nt)w9}^i3v!mJBSc9MdDmr?SS{Rx&p$XXAT>cPX0Cf+ zS75nY`E(SqT6I}W7_AxE%)d|j5D36FWS(xSd^8GN3@*JjgKuGEvaO(+R#F1 zw5osH@elmsI)Uh1y9(#IFTIz|uKOh=SMQ?HFJFV`V~kM0J%@OTSH-!;a*Te(cWYLw z?qZbtUcY;;DHSDr)*2+D0SDZ^`G*4}GoJ&ra1ZBASMz}#YL3sGsxf!tACQF(@4-Rk zeo7z`a-v#<yI#{}v?>KD7P?hKrWMXs@qpkliD{}>u9^sp}0FZt4-_uuyCo;>Hz!%Ds zz!!%Iehj)Ngp++2xB- z_3L$r;r{0HX?-D}mO>Z|!}^n1=`1Yk_pYQ6Er7y(ZE{Y|YbImr1qh8yaR0X9HXQN# zucf)dG^v|>WtS&E_oA8_yt^wKqqV@1OXYveFIx#oX4|J1+@x223d&Z-lcJLy51mah zsw-Y2r;!J=dX)Htq0eRa%~^2Pu_f{K91w zR05-90!eyytl&mA8B^(MISG<>ShLIZlzyHy1~VQf8Su|N3Krq>|5l7JMDB-V-gDM+n&M3RL{N@^|l z?Yx>6agAAQ--C%%Rj2BUTj9nj>2;!1d~k=TzJw#nub0c&UWCCFYq;>v#NyS5nBTDz z2blAj7sX!g%?tu(V6R$t5On-`+nHR5K29HkS|S9MEu$+c1j8O6|FENqfuNxS@A)x4 z|DvPUeW9%b$CWk!QG{1f>`;wo7i}+0AUsU2Src?Y*5#Jrs8$t<0h1%I%|0P%zte|D z_c?VU0xRIY>!}YfJLxSX&L7s3$a^R^3&!!x__c(0=;*kJDq(q8cDWJR)#4NVbQtlj zyufL}35EQ3Chai3plkscOt!suZ-O*Gi47?((`SOyxm|M8^W)NpajDBm_|1u&`v8zwYRR_My-vHC$WKYiY z-L!qD$LU58?cG=&R|EORUY+YjPJ#}+Jz^#df9yu%2I|ns>nxOCnJrwx z=FWZglOcqPIT){xM(-WO>-5AAaQ}AQj*M^gwNsf7qv;%6<_R2esWc23ZCaJ;ORGpP z92?@_zIA$d*T;z+a(A05!Fha1Uth|3zHy}M&XwU&`HQnPh46YHZ9xS}0h742K=@$ zjnct#+jx!^+1Onh+-i_r>1I2rbELo1D=96de;NoAu5*O<tFFqPrJS%_LROuYkqtclFOumUBT9NM7A5P_U7{4Jl=Nt7RPd^sq8_J zf4F^N3H99=iLEf~6QNQmj5%c;`aHT7?vP?QB`YzESdB2Be1qyD<~vk~!C9e;Z}fUe z=l%5xmM(kECoN>^C#@89c69s?oBa}>4I+XYhFMUyWhp&1HO%a(*81&Evzu^Q?PoA9 z^G5AUwE#sA#4qF$=s9%USczy4mm2a<52u-K&s%+N;am;XXdh-UX{wP8mSc##I8Gbx zHybUNcRvlJU=%hzkezpYcp}u>wn}ZvBCj-$fYaX2lFm|%Fby&7KR`&tSVsSwurB{i zh(s-BfWq>p`1ItB=l*}m8~;Xg}EGHv#!Jm7}1NBt=cxV8sKSjU-za5w%M*-m$fRnPy( zB;m1Tmgxq%q%e?MMEa*|{$d~fi%Ie)9qEsQKi+x#K}Px**B#lzh%R;q_A27xoirZ; zFQURh(xi3(X8gDexsn!thyLPSix249+jE%QW6ylzEx|KGOrV`@0S(zT!Lq3O;k``P zDOAmQyowG(#~W`nL*J1BCND1?F&M9j0aqQOMD~K+>({qb9Sc(u&%eJU72(M^{1AmV zRbFakdu0Gs#~@#SLA&d#5zNh}to3>t>r2FIm)|)dc{TY_iYwlbKTnctNbKw?z*y+S z{Wizi7ZJSBc5L+e7RZ?S?}S+i_Y%tN_OE~3Uavw92}v2 zx<07%JzBj-kXko#{s6p}jYX0PbgFG}PL3>Z{`2O8{nsXZIDSNAV$nFKaO}@pQ+Ft2 zZezufpNb7?@4;?ekTzGT}^562?5QXhW3(tBx3N7 z0m#Up!h+Dy?}*?3br9$X%?t8R{g40MwN?h$|F*yZ8U@J0GqpESbh3AFW-zgL0{kkP zfRy(CBi{j=&L>gHy7xVL$OhCCQum#xoCQu|K~)TYg`(!*z`CftjwP$m+4t9{^n!6_ z?%U#n)k$ai{ivkMJccUyxk5o!VoFd_tCF!wLHkU=a{$Rfb3$7w0{S65BHhRnuxOW^ z#hh=j>+*u2hb7zjWC2@u*I|UsQw=q)hl)=TzOrm}-aCEn(Ayt)0aEmFtQvBX{0`r} zs|hFKFeuqrlwCsS284=HHMiR%%u@I=ZXtN?d@@i!owNSxfWoDvFT3U|cHgwGlND=& zZDySo5$SHxy~)khvPMkGksCQkN;A5w=4MC3UWRvBAF(z;8LUn02j#|rNS;nuybbF3 zxC6Yq3eu`4>Rzz-`N**dYLz42w?Eso@|ekwXbZ^-YZ2tM#5FT~-QM|)yBmeU`9@p{ zI1(n&%7ZbPP#Go5hieF??_?q=`vD8{{~m#0L@%#mfgZ>V)FS`g1C1OU{?7q{p7*aU zD{0z#g%NoG=zv7g>uAkKl%zw{wk=AF1E5k*ekqd?#k4R-R*5e+JcF~YE!~Fp?svYg z&(~7w3t{sZ1cf%i-}?I7&>_6gtWMlhJQn@3eKEx-ey}xdR-;OcZEO2K=gw1!%9=7^ z;cq#U--8R;pI#a)*-IH=Y43V9HohX5;id5i!#Z@MolAl zv9}Uh6w7f4BW@R-jypI4m@h0V-I2xTN@#UGP4+7Wc98uw@xLBz`5Q zCdVc-^#i-&9rVlhc|(=?(tJQzs4#WSOTV>F_%;mlEoaK%d#xXRn(ek0^7T>aiLa37 z4~IGUv(hz2*f}F5?%FE~UXIKTj&`SDkjR)5D2=K3`=7^gQD36&`xt*9a&pwd+wL9| zg_EA1ZOpPHyE7Psum=e+?`ZlqzH$ll**1aoEckv#nlnR!vF6Xa z>8{!7^@{$ua>@A&`nYYSub=AkBPl)6K27fp^4~uE4wMd9&i?ZPzJK@GzxV&JoKI2a zKLP%8Rnfli=;rjYai literal 0 HcmV?d00001 diff --git a/Pilot/code/generate_sequencing_plan.py b/Pilot/code/generate_sequencing_plan.py new file mode 100644 index 0000000..3ce66c0 --- /dev/null +++ b/Pilot/code/generate_sequencing_plan.py @@ -0,0 +1,88 @@ +""" +Generate sample_sequencing_plan.xlsx +Labels every sample as Already sequenced / To be sequenced / Skip +at the chosen concentration threshold, with colour-coded rows. +""" + +import numpy as np +import pandas as pd +from openpyxl import load_workbook +from openpyxl.styles import PatternFill + +THRESHOLD = 0.25 +OUT_FILE = "sample_sequencing_plan.xlsx" + +PILOT_IDS = {("A", n) for n in [1, 2, 3, 4, 5, 6]} | \ + {("B", n) for n in [1, 2, 15, 16, 17, 18]} + +FILLS = { + "Already sequenced": PatternFill("solid", fgColor="C6EFCE"), # green + "To be sequenced": PatternFill("solid", fgColor="FFEB9C"), # yellow + "Skip": PatternFill("solid", fgColor="FFC7CE"), # red/pink +} + +# ── load data ───────────────────────────────────────────────────────────────── +patients = pd.read_excel("patients.xlsx", usecols=["Number", "Exf"]) +patients = patients.dropna(subset=["Number"]) +patients["Number"] = patients["Number"].astype(int) +patients["Exf"] = patients["Exf"].astype(int) + +def parse_conc(v): + if isinstance(v, str): return np.nan + try: return float(v) + except: return np.nan + +qa = pd.read_excel("BEA25P077_RP.xlsx", sheet_name="A-samples") +qb = pd.read_excel("BEA25P077_RP.xlsx", sheet_name="B-samples") +qa["conc_val"] = qa["conc (ng/ul)"].apply(parse_conc) +qb["conc_val"] = qb["Conc [ng/ul]"].apply(parse_conc) +qa["Number"] = pd.to_numeric(qa["ID"].str.extract(r"^(\d+)A")[0], errors="coerce") +qb["Number"] = pd.to_numeric(qb["ID"].str.extract(r"^(\d+)B")[0], errors="coerce") +qa = qa.dropna(subset=["Number"]); qa["Number"] = qa["Number"].astype(int); qa["tissue"] = "A" +qb = qb.dropna(subset=["Number"]); qb["Number"] = qb["Number"].astype(int); qb["tissue"] = "B" +qa = qa.merge(patients, on="Number", how="left") +qb = qb.merge(patients, on="Number", how="left") + +# ── build plan ──────────────────────────────────────────────────────────────── +rows = [] +for _, r in pd.concat([qa, qb], ignore_index=True).iterrows(): + tissue, num, conc = r["tissue"], r["Number"], r["conc_val"] + if (tissue, num) in PILOT_IDS: + status = "Already sequenced" + elif pd.isna(conc) or conc < THRESHOLD: + status = "Skip" + else: + status = "To be sequenced" + rows.append({ + "Sample": f"{num}{tissue}_G2", + "Patient": num, + "Tissue": tissue, + "Conc (ng/ul)": conc, + "Status": status, + }) + +df = pd.DataFrame(rows).sort_values(["Tissue", "Patient"]) + +# ── write Excel ─────────────────────────────────────────────────────────────── +df.to_excel(OUT_FILE, index=False) + +wb = load_workbook(OUT_FILE) +ws = wb.active + +# auto-width columns +for col_cells in ws.columns: + width = max(len(str(c.value)) if c.value is not None else 0 for c in col_cells) + ws.column_dimensions[col_cells[0].column_letter].width = width + 4 + +# colour every row based on Status (Status is the last column) +status_col = df.columns.get_loc("Status") + 1 # 1-indexed +for row in ws.iter_rows(min_row=2): + status = row[status_col - 1].value + fill = FILLS.get(status) + if fill: + for cell in row: + cell.fill = fill + +wb.save(OUT_FILE) +print(f"Saved {OUT_FILE} ({len(df)} samples)") +print(df["Status"].value_counts().to_string()) diff --git a/Pilot/code/miRNA_high_expression.py b/Pilot/code/miRNA_high_expression.py new file mode 100644 index 0000000..100f4e4 --- /dev/null +++ b/Pilot/code/miRNA_high_expression.py @@ -0,0 +1,589 @@ +#!/usr/bin/env python3 +import argparse +import csv +import os +import re +import ssl +import statistics +import sys +import urllib.error +import urllib.parse +import urllib.request +import zipfile +import xml.etree.ElementTree as ET +from collections import defaultdict + + +def col_to_index(col): + idx = 0 + for c in col: + idx = idx * 26 + (ord(c.upper()) - ord("A") + 1) + return idx + + +def parse_shared_strings(zf): + try: + xml = zf.read("xl/sharedStrings.xml") + except KeyError: + return [] + root = ET.fromstring(xml) + ns = {"a": root.tag.split("}")[0].strip("{")} + shared = [] + for si in root.findall(".//a:si", ns): + text_parts = [] + for t in si.findall(".//a:t", ns): + text_parts.append(t.text or "") + shared.append("".join(text_parts)) + return shared + + +def list_sheets(zf): + wb = ET.fromstring(zf.read("xl/workbook.xml")) + ns = {"a": wb.tag.split("}")[0].strip("{")} + sheets = [] + for sh in wb.findall(".//a:sheets/a:sheet", ns): + sheets.append( + (sh.attrib["name"], sh.attrib["{http://schemas.openxmlformats.org/officeDocument/2006/relationships}id"]) + ) + + rels = ET.fromstring(zf.read("xl/_rels/workbook.xml.rels")) + relmap = {} + for rel in rels.findall(".//{http://schemas.openxmlformats.org/package/2006/relationships}Relationship"): + relmap[rel.attrib["Id"]] = rel.attrib["Target"] + + return [(name, "xl/" + relmap[rid]) for name, rid in sheets] + + +def read_xlsx_sheet(path, sheet_name): + with zipfile.ZipFile(path) as zf: + shared = parse_shared_strings(zf) + sheets = dict(list_sheets(zf)) + if sheet_name not in sheets: + raise ValueError(f"Sheet '{sheet_name}' not found. Available: {', '.join(sheets.keys())}") + + root = ET.fromstring(zf.read(sheets[sheet_name])) + ns = {"a": root.tag.split("}")[0].strip("{")} + row_dicts = [] + max_col_idx = 0 + + for row in root.findall(".//a:sheetData/a:row", ns): + row_cells = {} + for c in row.findall("a:c", ns): + ref = c.attrib.get("r", "") + col = "".join(ch for ch in ref if ch.isalpha()) + if not col: + continue + v = c.find("a:v", ns) + value = v.text if v is not None else "" + if c.attrib.get("t") == "s": + try: + value = shared[int(value)] + except Exception: + pass + row_cells[col] = value + max_col_idx = max(max_col_idx, col_to_index(col)) + if row_cells: + row_dicts.append(row_cells) + + if not row_dicts: + return [] + + rows = [] + for row_cells in row_dicts: + row_list = [""] * max_col_idx + for col, val in row_cells.items(): + row_list[col_to_index(col) - 1] = val + rows.append(row_list) + + header = rows[0] + ncols = len(header) + while ncols > 0 and header[ncols - 1] == "": + ncols -= 1 + + normalized = [] + for row in rows: + if len(row) < ncols: + row = row + [""] * (ncols - len(row)) + elif len(row) > ncols: + row = row[:ncols] + normalized.append(row) + return normalized + + +def score_counts(values, metric): + if not values: + return 0.0 + if metric == "mean": + return sum(values) / len(values) + if metric == "median": + return statistics.median(values) + if metric == "sum": + return sum(values) + if metric == "max": + return max(values) + raise ValueError(f"Unknown metric: {metric}") + + +def canonical_mirna(name): + s = name.strip().lower() + s = re.sub(r"^[a-z]{3}-", "", s) + return s + + +def _detect_delimiter(sample): + if "\t" in sample: + return "\t" + if "," in sample: + return "," + return "\t" + + +def read_delimited_rows(path): + with open(path, "r", newline="") as f: + sample = f.readline() + delim = _detect_delimiter(sample) + f.seek(0) + try: + reader = csv.reader(f, delimiter=delim) + rows = list(reader) + except csv.Error: + f.seek(0) + reader = csv.reader( + f, + delimiter=delim, + quoting=csv.QUOTE_NONE, + escapechar="\\", + ) + rows = list(reader) + return rows + + +def iter_delimited_rows(path): + with open(path, "r", newline="") as f: + sample = f.readline() + delim = _detect_delimiter(sample) + f.seek(0) + try: + reader = csv.reader(f, delimiter=delim) + for row in reader: + yield row + except csv.Error: + f.seek(0) + reader = csv.reader( + f, + delimiter=delim, + quoting=csv.QUOTE_NONE, + escapechar="\\", + ) + for row in reader: + yield row + + +def read_targets(path): + rows = read_delimited_rows(path) + return parse_targets_rows(rows) + + +def normalize_header(col): + return re.sub(r"[^a-z0-9]+", "", col.strip().lower()) + + +def normalize_species(value): + v = re.sub(r"[^a-z]+", " ", value.strip().lower()) + v = re.sub(r"\s+", " ", v).strip() + if v in {"human", "homo sapiens", "homo sapienst"}: + return "homo sapiens" + return v + + +def download_url_to_file(url, dest_path, allow_insecure=False): + os.makedirs(os.path.dirname(dest_path) or ".", exist_ok=True) + req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"}) + context = ssl._create_unverified_context() if allow_insecure else None + if context: + response = urllib.request.urlopen(req, context=context) + else: + response = urllib.request.urlopen(req) + with response as resp, open(dest_path, "wb") as f: + while True: + chunk = resp.read(1024 * 1024) + if not chunk: + break + f.write(chunk) + + +def find_mirtarbase_mti_url(download_page_url): + req = urllib.request.Request(download_page_url, headers={"User-Agent": "Mozilla/5.0"}) + with urllib.request.urlopen(req) as resp: + html = resp.read().decode("utf-8", errors="ignore") + links = re.findall(r'href=["\\\']([^"\\\']+)["\\\']', html, flags=re.IGNORECASE) + scored = [] + for link in links: + if "mirtarbase" not in link.lower(): + continue + if "mti" not in link.lower(): + continue + if not re.search(r"\\.(txt|tsv|csv|xls|xlsx|zip)$", link, flags=re.IGNORECASE): + continue + scored.append(link) + if not scored: + return None + scored.sort(key=lambda s: (0 if s.lower().endswith(".txt") else 1, len(s))) + return urllib.parse.urljoin(download_page_url, scored[0]) + + +def read_mirtarbase( + path, + species="Homo sapiens", + support=None, + sheet=None, + include_mirnas=None, +): + species_norm = normalize_species(species) if species else None + ext = os.path.splitext(path)[1].lower() + if ext == ".xlsx": + if sheet is None: + with zipfile.ZipFile(path) as zf: + sheets = list_sheets(zf) + if not sheets: + raise ValueError("miRTarBase .xlsx has no sheets.") + sheet = sheets[0][0] + rows = read_xlsx_sheet(path, sheet) + if not rows: + return {"raw": defaultdict(set), "canonical": defaultdict(set)} + header = rows[0] + row_iter = iter(rows[1:]) + else: + row_iter = iter_delimited_rows(path) + try: + header = next(row_iter) + except StopIteration: + return {"raw": defaultdict(set), "canonical": defaultdict(set)} + + idx_mirna = None + idx_target = None + idx_species_target = None + idx_species_mirna = None + idx_support = None + + for i, col in enumerate(header): + key = normalize_header(col) + if key in {"mirna", "mirnaname", "mirnaid", "mirname"} and idx_mirna is None: + idx_mirna = i + if key in {"targetgene", "target", "gene", "genesymbol"} and idx_target is None: + idx_target = i + if key in {"speciestargetgene", "speciestarget", "targetspecies"} and idx_species_target is None: + idx_species_target = i + if key in {"speciesmirna", "speciessourcemiRNA", "speciesmir"} and idx_species_mirna is None: + idx_species_mirna = i + if key in {"supporttype", "support", "evidence"} and idx_support is None: + idx_support = i + + if idx_mirna is None or idx_target is None: + raise ValueError( + "miRTarBase format not recognized. Expected columns like 'miRNA' and 'Target Gene'." + ) + + mapping = defaultdict(set) + support_norm = support.strip().lower() if support else None + + include_canonical = None + if include_mirnas: + include_canonical = {canonical_mirna(m) for m in include_mirnas} + + for row in row_iter: + if len(row) <= max(idx_mirna, idx_target): + continue + mirna = row[idx_mirna].strip() + target = row[idx_target].strip() + if not mirna or not target: + continue + + if include_canonical is not None and canonical_mirna(mirna) not in include_canonical: + continue + + if species_norm: + species_value = "" + if idx_species_target is not None and idx_species_target < len(row): + species_value = row[idx_species_target] + elif idx_species_mirna is not None and idx_species_mirna < len(row): + species_value = row[idx_species_mirna] + if species_value: + if normalize_species(species_value) != species_norm: + continue + + if support_norm and idx_support is not None and idx_support < len(row): + if support_norm not in row[idx_support].lower(): + continue + + mapping[mirna].add(target) + + canonical = defaultdict(set) + for mirna, targets in mapping.items(): + canonical[canonical_mirna(mirna)].update(targets) + + return {"raw": mapping, "canonical": canonical} +def parse_targets_rows(rows): + if not rows: + return {} + + header = rows[0] + idx_mirna = None + idx_target = None + for i, col in enumerate(header): + key = normalize_header(col) + if key in {"mirna", "mir", "mirname", "mirid"} and idx_mirna is None: + idx_mirna = i + if key in {"target", "gene", "protein", "symbol", "targetgene", "targets"} and idx_target is None: + idx_target = i + + if idx_mirna is None or idx_target is None: + idx_mirna, idx_target = 0, 1 + + mapping = defaultdict(set) + for row in rows[1:]: + if len(row) <= max(idx_mirna, idx_target): + continue + mirna = row[idx_mirna].strip() + target_cell = row[idx_target].strip() + if not mirna or not target_cell: + continue + targets = [target_cell] + if ";" in target_cell: + targets = [t.strip() for t in target_cell.split(";") if t.strip()] + elif "," in target_cell: + targets = [t.strip() for t in target_cell.split(",") if t.strip()] + + for target in targets: + mapping[mirna].add(target) + + canonical = defaultdict(set) + for mirna, targets in mapping.items(): + canonical[canonical_mirna(mirna)].update(targets) + + return {"raw": mapping, "canonical": canonical} + + +def select_high_expression(rows, metric, top_n, min_score, quantile): + header = rows[0] + sample_cols = header[1:] + entries = [] + + for row in rows[1:]: + if not row or not row[0]: + continue + counts = [] + for val in row[1:]: + try: + counts.append(float(val)) + except Exception: + counts.append(0.0) + score = score_counts(counts, metric) + entries.append((row[0], counts, score)) + + entries.sort(key=lambda x: x[2], reverse=True) + scores = [e[2] for e in entries] + selected = entries + + if top_n is not None: + selected = entries[: max(0, top_n)] + elif quantile is not None: + if not scores: + selected = [] + else: + idx = int(round((len(scores) - 1) * quantile)) + thresh = sorted(scores)[idx] + selected = [e for e in entries if e[2] >= thresh] + elif min_score is not None: + selected = [e for e in entries if e[2] >= min_score] + else: + selected = entries[:50] + + return sample_cols, selected + + +def write_high_expression(path, sample_cols, selected, metric): + os.makedirs(os.path.dirname(path), exist_ok=True) + with open(path, "w", newline="") as f: + writer = csv.writer(f, delimiter="\t") + writer.writerow(["miRNA", f"{metric}_score", *sample_cols]) + for mirna, counts, score in selected: + writer.writerow([mirna, f"{score:.6g}", *counts]) + + +def write_targets(outdir, selected, target_map, metric): + edges_path = os.path.join(outdir, "miRNA_protein_edges.tsv") + summary_path = os.path.join(outdir, "protein_targets_summary.tsv") + os.makedirs(outdir, exist_ok=True) + + target_stats = defaultdict(lambda: {"miRNAs": set(), "score_sum": 0.0}) + with open(edges_path, "w", newline="") as f: + writer = csv.writer(f, delimiter="\t") + writer.writerow(["miRNA", "target", f"{metric}_score", "edge_weight"]) + for mirna, _counts, score in selected: + targets = set() + targets.update(target_map["raw"].get(mirna, set())) + targets.update(target_map["canonical"].get(canonical_mirna(mirna), set())) + for target in sorted(targets): + writer.writerow([mirna, target, f"{score:.6g}", f"{score:.6g}"]) + target_stats[target]["miRNAs"].add(mirna) + target_stats[target]["score_sum"] += score + + with open(summary_path, "w", newline="") as f: + writer = csv.writer(f, delimiter="\t") + writer.writerow(["target", "miRNA_count", f"{metric}_score_sum"]) + for target, stats in sorted( + target_stats.items(), key=lambda item: (-len(item[1]["miRNAs"]), item[0]) + ): + writer.writerow([target, len(stats["miRNAs"]), f"{stats['score_sum']:.6g}"]) + + return edges_path, summary_path + + +def main(): + parser = argparse.ArgumentParser(description="Select high-expression miRNAs and build a target map.") + parser.add_argument("--counts", default="miRNA_counts.xlsx", help="Path to miRNA counts .xlsx file.") + parser.add_argument("--sheet", default="Mature", help="Sheet name to use (e.g., Mature or Hairpin).") + parser.add_argument("--metric", choices=["mean", "median", "sum", "max"], default="mean") + parser.add_argument("--top", type=int, default=None, help="Select top N miRNAs by metric.") + parser.add_argument( + "--min-score", + type=float, + default=5000.0, + help="Select miRNAs with metric >= value. Default: 5000.", + ) + parser.add_argument("--quantile", type=float, default=None, help="Select miRNAs with metric >= quantile (0-1).") + parser.add_argument("--targets", default=None, help="TSV/CSV file with miRNA-to-target mappings.") + parser.add_argument("--mirtarbase", default=None, help="miRTarBase TSV/CSV/XLSX file to build targets.") + parser.add_argument( + "--fetch-mirtarbase", + action="store_true", + help="Auto-download miRTarBase MTI file if missing (best-effort).", + ) + parser.add_argument( + "--mirtarbase-url", + default=None, + help="Direct URL to miRTarBase MTI file (overrides auto-detection).", + ) + parser.add_argument( + "--mirtarbase-download-page", + default="http://mirtarbase.cuhk.edu.cn/php/download.php", + help="miRTarBase download page URL to discover MTI file.", + ) + parser.add_argument( + "--mirtarbase-insecure", + action="store_true", + help="Allow insecure SSL (disable certificate verification) for downloads.", + ) + parser.add_argument( + "--mirtarbase-species", + default="Homo sapiens", + help="Species filter for miRTarBase (default: Homo sapiens).", + ) + parser.add_argument( + "--mirtarbase-support", + default=None, + help="Optional miRTarBase support filter (e.g., Strong).", + ) + parser.add_argument( + "--mirtarbase-sheet", + default=None, + help="Sheet name for miRTarBase .xlsx (defaults to first sheet).", + ) + parser.add_argument("--outdir", default="out", help="Output directory.") + args = parser.parse_args() + + if not os.path.exists(args.counts): + print(f"Counts file not found: {args.counts}", file=sys.stderr) + return 2 + + try: + rows = read_xlsx_sheet(args.counts, args.sheet) + except Exception as exc: + print(f"Failed to read sheet: {exc}", file=sys.stderr) + return 2 + + if not rows: + print("No data found in sheet.", file=sys.stderr) + return 2 + + sample_cols, selected = select_high_expression( + rows, args.metric, args.top, args.min_score, args.quantile + ) + if not selected: + print("No miRNAs selected with the current criteria.", file=sys.stderr) + return 2 + + outdir = args.outdir + high_path = os.path.join(outdir, "high_expression_miRNAs.tsv") + write_high_expression(high_path, sample_cols, selected, args.metric) + + edges_path = summary_path = None + target_map = None + if args.mirtarbase or args.fetch_mirtarbase: + if args.mirtarbase is None: + args.mirtarbase = os.path.join(args.outdir, "miRTarBase_MTI.txt") + + if not os.path.exists(args.mirtarbase) and args.fetch_mirtarbase: + try: + url = args.mirtarbase_url + if url is None: + url = find_mirtarbase_mti_url(args.mirtarbase_download_page) + if url is None: + print( + "Could not auto-detect miRTarBase MTI file URL from the download page.", + file=sys.stderr, + ) + return 2 + print(f"Downloading miRTarBase MTI file from: {url}") + try: + download_url_to_file(url, args.mirtarbase, allow_insecure=args.mirtarbase_insecure) + except urllib.error.URLError as exc: + reason = getattr(exc, "reason", None) + if isinstance(reason, ssl.SSLError) and url.startswith("https://"): + http_url = "http://" + url[len("https://") :] + print(f"SSL failed, retrying over HTTP: {http_url}") + download_url_to_file( + http_url, args.mirtarbase, allow_insecure=args.mirtarbase_insecure + ) + else: + raise + except (urllib.error.URLError, ValueError, OSError) as exc: + print(f"Failed to download miRTarBase file: {exc}", file=sys.stderr) + return 2 + + if not os.path.exists(args.mirtarbase): + print(f"miRTarBase file not found: {args.mirtarbase}", file=sys.stderr) + return 2 + + try: + target_map = read_mirtarbase( + args.mirtarbase, + species=args.mirtarbase_species, + support=args.mirtarbase_support, + sheet=args.mirtarbase_sheet, + include_mirnas=[m for m, _c, _s in selected], + ) + except Exception as exc: + print(f"Failed to read miRTarBase file: {exc}", file=sys.stderr) + return 2 + elif args.targets: + if not os.path.exists(args.targets): + print(f"Targets file not found: {args.targets}", file=sys.stderr) + return 2 + target_map = read_targets(args.targets) + + if target_map: + edges_path, summary_path = write_targets(outdir, selected, target_map, args.metric) + + print(f"Wrote high-expression list: {high_path}") + if edges_path: + print(f"Wrote target edges: {edges_path}") + print(f"Wrote target summary: {summary_path}") + else: + print("No target map generated (provide --mirtarbase or --targets).") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/Pilot/code/mirna_threshold_analysis.py b/Pilot/code/mirna_threshold_analysis.py new file mode 100644 index 0000000..411ad19 --- /dev/null +++ b/Pilot/code/mirna_threshold_analysis.py @@ -0,0 +1,256 @@ +""" +miRNA threshold analysis +------------------------ +For each tissue (A = Schirmer strips, B = Lens tissues): + - Derives concentration thresholds from the pilot group's actual measured values + - At each threshold shows: # samples passing from full cohort (healthy / diseased) + and the mean ± SD total miRNA counts from pilot samples that pass that cut-off +Outputs: summary Excel table + multi-panel figure +""" + +import re +import numpy as np +import pandas as pd +import matplotlib.pyplot as plt +import matplotlib.gridspec as gridspec +from matplotlib.lines import Line2D + +# ── file paths ──────────────────────────────────────────────────────────────── +QC_FILE = "BEA25P077_RP.xlsx" +PATIENTS_FILE = "patients.xlsx" +COUNTS_FILE = "miRNA_counts.xlsx" +OUT_EXCEL = "mirna_threshold_summary.xlsx" +OUT_FIG = "mirna_threshold_analysis.png" + +# ── colours ─────────────────────────────────────────────────────────────────── +COL_HEALTHY = "#4393c3" # blue +COL_DISEASED = "#d6604d" # red +COL_PILOT_A = "#2ca02c" # green +COL_PILOT_B = "#9467bd" # purple + +# ── helpers ─────────────────────────────────────────────────────────────────── +def parse_conc(val): + if isinstance(val, str): + return np.nan + try: + return float(val) + except (TypeError, ValueError): + return np.nan + + +def extract_num(series, letter): + """Pull the patient number from IDs like '4A_G2'.""" + extracted = series.str.extract(rf"^(\d+){letter}")[0] + return pd.to_numeric(extracted, errors="coerce") + + +# ── load data ───────────────────────────────────────────────────────────────── +patients = pd.read_excel(PATIENTS_FILE, usecols=["Number", "Exf"]) +patients = patients.dropna(subset=["Number"]) +patients["Number"] = patients["Number"].astype(int) +patients["Exf"] = patients["Exf"].astype(int) + +counts = pd.read_excel(COUNTS_FILE, sheet_name="Mature", index_col=0) +totals = counts.sum(axis=0).rename("total_miRNA") # total per sample + +qa = pd.read_excel(QC_FILE, sheet_name="A-samples") +qb = pd.read_excel(QC_FILE, sheet_name="B-samples") + +qa["conc_val"] = qa["conc (ng/ul)"].apply(parse_conc) +qb["conc_val"] = qb["Conc [ng/ul]"].apply(parse_conc) +qa["Number"] = extract_num(qa["ID"], "A").dropna().astype(int) +qb["Number"] = extract_num(qb["ID"], "B").dropna().astype(int) + +qa = qa.dropna(subset=["Number"]).merge(patients, on="Number", how="left") +qb = qb.dropna(subset=["Number"]).merge(patients, on="Number", how="left") + + +# ── build pilot lookup: number → (conc, total_miRNA) ───────────────────────── +def build_pilot(qc_df, tissue_letter): + """Join QC concentrations with miRNA totals for pilot samples.""" + pilot_rows = [] + for col in totals.index: + m = re.match(rf"s(\d+){tissue_letter}_G2", col) + if not m: + continue + num = int(m.group(1)) + conc_row = qc_df[qc_df["Number"] == num] + if conc_row.empty: + continue + pilot_rows.append({ + "Number": num, + "sample_id": col, + "conc_val": conc_row["conc_val"].values[0], + "total_miRNA": totals[col], + "Exf": conc_row["Exf"].values[0], + }) + return pd.DataFrame(pilot_rows).sort_values("conc_val") + + +pilot_A = build_pilot(qa, "A") +pilot_B = build_pilot(qb, "B") + + +# ── derive thresholds from pilot concentrations ─────────────────────────────── +def pilot_thresholds(pilot_df): + """ + Use the sorted unique measured concentrations as tier cut-offs, + prepend 0 (all samples) and append a step just above the max. + """ + measured = sorted(pilot_df["conc_val"].dropna().unique()) + # round to 2 dp to keep labels clean + thresholds = [0.0] + [round(v, 2) for v in measured] + return thresholds + + +thresholds_A = pilot_thresholds(pilot_A) +thresholds_B = pilot_thresholds(pilot_B) + + +# ── per-threshold summary ───────────────────────────────────────────────────── +def threshold_summary(qc_df, pilot_df, thresholds): + rows = [] + for t in thresholds: + # full cohort + if t == 0: + cohort = qc_df.copy() + else: + cohort = qc_df[qc_df["conc_val"] >= t] + n_total = len(cohort) + n_healthy = (cohort["Exf"] == 0).sum() + n_diseased = (cohort["Exf"] == 1).sum() + + # pilot subset + if t == 0: + pilot_pass = pilot_df.copy() + else: + pilot_pass = pilot_df[pilot_df["conc_val"] >= t] + n_pilot = len(pilot_pass) + mean_mirna = pilot_pass["total_miRNA"].mean() if n_pilot else np.nan + sd_mirna = pilot_pass["total_miRNA"].std() if n_pilot > 1 else np.nan + + rows.append({ + "threshold (ng/µl)": t, + "cohort_total": n_total, + "cohort_healthy": n_healthy, + "cohort_diseased": n_diseased, + "pilot_n": n_pilot, + "pilot_mean_miRNA": mean_mirna, + "pilot_sd_miRNA": sd_mirna, + }) + return pd.DataFrame(rows) + + +summary_A = threshold_summary(qa, pilot_A, thresholds_A) +summary_B = threshold_summary(qb, pilot_B, thresholds_B) + + +# ── save Excel ──────────────────────────────────────────────────────────────── +with pd.ExcelWriter(OUT_EXCEL, engine="openpyxl") as writer: + for df, sheet in [(summary_A, "A - Schirmer strips"), + (summary_B, "B - Lens tissues")]: + df.to_excel(writer, sheet_name=sheet, index=False) + ws = writer.sheets[sheet] + # widen columns + for col_cells in ws.columns: + max_len = max(len(str(c.value)) if c.value else 0 for c in col_cells) + ws.column_dimensions[col_cells[0].column_letter].width = max_len + 4 +print(f"Excel saved → {OUT_EXCEL}") + + +# ── figure ──────────────────────────────────────────────────────────────────── +fig = plt.figure(figsize=(16, 10)) +fig.suptitle("miRNA QC Threshold Analysis", fontsize=14, fontweight="bold", y=0.98) + +# 2 tissues × 3 columns: scatter | cohort counts | expected miRNA +gs = gridspec.GridSpec(2, 3, figure=fig, hspace=0.45, wspace=0.38, + left=0.07, right=0.97, top=0.92, bottom=0.08) + + +def plot_tissue(row, tissue_label, pilot_df, summary_df, pilot_col): + thresholds = summary_df["threshold (ng/µl)"].tolist() + x_labels = [str(t) for t in thresholds] + x = np.arange(len(thresholds)) + bar_w = 0.38 + + # ── panel 1: scatter concentration vs total miRNA ───────────────────────── + ax1 = fig.add_subplot(gs[row, 0]) + healthy_mask = pilot_df["Exf"] == 0 + diseased_mask = pilot_df["Exf"] == 1 + + ax1.scatter(pilot_df.loc[healthy_mask, "conc_val"], + pilot_df.loc[healthy_mask, "total_miRNA"] / 1e6, + color=COL_HEALTHY, edgecolors="k", linewidths=0.5, + s=70, zorder=3, label="Healthy") + ax1.scatter(pilot_df.loc[diseased_mask, "conc_val"], + pilot_df.loc[diseased_mask, "total_miRNA"] / 1e6, + color=COL_DISEASED, edgecolors="k", linewidths=0.5, + s=70, zorder=3, label="Diseased") + + # annotate patient numbers + for _, r in pilot_df.iterrows(): + if pd.notna(r["conc_val"]): + ax1.annotate(str(int(r["Number"])), + (r["conc_val"], r["total_miRNA"] / 1e6), + textcoords="offset points", xytext=(4, 3), + fontsize=7, color="dimgray") + + # vertical lines at each threshold (skip 0) + for t in thresholds[1:]: + ax1.axvline(t, color="gray", lw=0.7, ls="--", alpha=0.5) + + ax1.set_xlabel("Concentration (ng/µl)", fontsize=9) + ax1.set_ylabel("Total miRNA counts (×10⁶)", fontsize=9) + ax1.set_title(f"{tissue_label}\nPilot: conc vs miRNA yield", fontsize=9) + ax1.legend(fontsize=8, framealpha=0.7) + ax1.tick_params(labelsize=8) + + # ── panel 2: cohort counts per threshold ────────────────────────────────── + ax2 = fig.add_subplot(gs[row, 1]) + ax2.bar(x - bar_w / 2, summary_df["cohort_healthy"], + width=bar_w, color=COL_HEALTHY, label="Healthy", alpha=0.85) + ax2.bar(x + bar_w / 2, summary_df["cohort_diseased"], + width=bar_w, color=COL_DISEASED, label="Diseased", alpha=0.85) + ax2.set_xticks(x) + ax2.set_xticklabels(x_labels, rotation=45, ha="right", fontsize=8) + ax2.set_xlabel("Min. concentration threshold (ng/µl)", fontsize=9) + ax2.set_ylabel("N samples passing", fontsize=9) + ax2.set_title(f"{tissue_label}\nCohort samples at each threshold", fontsize=9) + ax2.legend(fontsize=8, framealpha=0.7) + ax2.tick_params(labelsize=8) + # add count labels + for bar in ax2.patches: + h = bar.get_height() + if h > 0: + ax2.text(bar.get_x() + bar.get_width() / 2, h + 0.3, + str(int(h)), ha="center", va="bottom", fontsize=7) + + # ── panel 3: expected miRNA from pilot ──────────────────────────────────── + ax3 = fig.add_subplot(gs[row, 2]) + means = summary_df["pilot_mean_miRNA"] / 1e6 + sds = summary_df["pilot_sd_miRNA"].fillna(0) / 1e6 + ns = summary_df["pilot_n"] + + ax3.bar(x, means, width=0.55, color=pilot_col, alpha=0.8, label="Mean ± SD") + ax3.errorbar(x, means, yerr=sds, fmt="none", color="k", + capsize=4, linewidth=1.2, zorder=4) + + # label n= above each bar + for i, (m, n) in enumerate(zip(means, ns)): + ax3.text(i, (m + sds.iloc[i]) + 0.05, f"n={int(n)}", + ha="center", va="bottom", fontsize=7, color="dimgray") + + ax3.set_xticks(x) + ax3.set_xticklabels(x_labels, rotation=45, ha="right", fontsize=8) + ax3.set_xlabel("Min. concentration threshold (ng/µl)", fontsize=9) + ax3.set_ylabel("Total miRNA counts (×10⁶)", fontsize=9) + ax3.set_title(f"{tissue_label}\nExpected miRNA yield (pilot mean ± SD)", fontsize=9) + ax3.tick_params(labelsize=8) + + +plot_tissue(0, "A — Schirmer strips", pilot_A, summary_A, COL_PILOT_A) +plot_tissue(1, "B — Lens tissues", pilot_B, summary_B, COL_PILOT_B) + +fig.savefig(OUT_FIG, dpi=150, bbox_inches="tight") +print(f"Figure saved → {OUT_FIG}") +plt.show() diff --git a/Pilot/code/mirna_threshold_unified.py b/Pilot/code/mirna_threshold_unified.py new file mode 100644 index 0000000..3542c09 --- /dev/null +++ b/Pilot/code/mirna_threshold_unified.py @@ -0,0 +1,370 @@ +""" +miRNA threshold analysis — unified thresholds +---------------------------------------------- +Round-number concentration thresholds applied to both tissues. +Each threshold panel shows cumulative cohort samples passing (healthy / diseased), +annotated with the approximate expected miRNA yield from pilot samples whose +concentration falls in that bucket (>= threshold, < next threshold), +combining both tissues. +Outputs: summary Excel + figure +""" + +import re +import numpy as np +import pandas as pd +import matplotlib.pyplot as plt +from matplotlib.patches import Patch + +# ── config ──────────────────────────────────────────────────────────────────── +QC_FILE = "BEA25P077_RP.xlsx" +PATIENTS_FILE = "patients.xlsx" +COUNTS_FILE = "miRNA_counts.xlsx" +OUT_EXCEL = "mirna_threshold_unified_summary.xlsx" +OUT_FIG = "mirna_threshold_unified.png" + +THRESHOLDS = [0.0, 0.25, 1.0] + +YIELD_THRESHOLDS = THRESHOLDS +YIELD_BUCKET_MAP = {t: t for t in THRESHOLDS} + +COL_HEALTHY = "#4393c3" +COL_DISEASED = "#d6604d" + +# ── helpers ─────────────────────────────────────────────────────────────────── +def parse_conc(val): + if isinstance(val, str): + return np.nan + try: + return float(val) + except (TypeError, ValueError): + return np.nan + + +def extract_num(series, letter): + return pd.to_numeric( + series.str.extract(rf"^(\d+){letter}")[0], errors="coerce" + ) + + +# ── load data ───────────────────────────────────────────────────────────────── +patients = pd.read_excel(PATIENTS_FILE, usecols=["Number", "Exf"]) +patients = patients.dropna(subset=["Number"]) +patients["Number"] = patients["Number"].astype(int) +patients["Exf"] = patients["Exf"].astype(int) + +counts_mat = pd.read_excel(COUNTS_FILE, sheet_name="Mature", index_col=0) +totals = counts_mat.sum(axis=0).rename("total_miRNA") + +qa = pd.read_excel(QC_FILE, sheet_name="A-samples") +qb = pd.read_excel(QC_FILE, sheet_name="B-samples") +qa["conc_val"] = qa["conc (ng/ul)"].apply(parse_conc) +qb["conc_val"] = qb["Conc [ng/ul]"].apply(parse_conc) +qa["Number"] = extract_num(qa["ID"], "A") +qb["Number"] = extract_num(qb["ID"], "B") +qa = qa.dropna(subset=["Number"]).copy(); qa["Number"] = qa["Number"].astype(int) +qb = qb.dropna(subset=["Number"]).copy(); qb["Number"] = qb["Number"].astype(int) +qa = qa.merge(patients, on="Number", how="left") +qb = qb.merge(patients, on="Number", how="left") + + +# ── pilot dataframe (both tissues) ─────────────────────────────────────────── +def build_pilot(qc_df, tissue_letter): + rows = [] + for col in totals.index: + m = re.match(rf"s(\d+){tissue_letter}_G2", col) + if not m: + continue + num = int(m.group(1)) + row = qc_df[qc_df["Number"] == num] + if row.empty: + continue + rows.append({ + "Number": num, + "sample_id": col, + "tissue": tissue_letter, + "conc_val": row["conc_val"].values[0], + "total_miRNA": totals[col], + "Exf": row["Exf"].values[0], + }) + return pd.DataFrame(rows) + +pilot = pd.concat([build_pilot(qa, "A"), build_pilot(qb, "B")], ignore_index=True) + + +# ── bucketed yield: pilot samples in [threshold_i, threshold_i+1) ───────────── +def assign_bucket(conc): + if pd.isna(conc): + return -1 # "Too low" / invalid excluded from all buckets + for i in range(len(YIELD_THRESHOLDS) - 1, -1, -1): + if conc >= YIELD_THRESHOLDS[i]: + return i + return -1 + +pilot["bucket"] = pilot["conc_val"].apply(assign_bucket) + +bucket_yield = {} +for i, t in enumerate(YIELD_THRESHOLDS): + grp = pilot[pilot["bucket"] == i] + n = len(grp) + mean = grp["total_miRNA"].mean() if n else np.nan + sd = grp["total_miRNA"].std() if n > 1 else np.nan + mn = grp["total_miRNA"].min() if n else np.nan + mx = grp["total_miRNA"].max() if n else np.nan + lbl = f"≥{t}" if i == len(YIELD_THRESHOLDS) - 1 else f"{t} – <{YIELD_THRESHOLDS[i+1]}" + bucket_yield[t] = { + "mean": mean, "sd": sd, "min": mn, "max": mx, "n": n, + "label": lbl, + "first": i == 0, + "last": i == len(THRESHOLDS) - 1, + "samples": ", ".join(grp["sample_id"].tolist()), + } + + +def fmt_yield(yld): + n, mean, sd, mn, mx = yld["n"], yld["mean"], yld["sd"], yld["min"], yld["max"] + if n == 0 or np.isnan(mean): + return "no pilot data" + if yld["first"]: + return f"<{mx/1e6:.2f}M (n={n})" + if yld["last"]: + return f">{mn/1e6:.2f}M (n={n})" + # middle buckets: mean ± SD + sd_str = f" ± {sd/1e6:.2f}M" if not np.isnan(sd) else "" + return f"~{mean/1e6:.2f}M{sd_str} (n={n})" + + +# ── cumulative cohort counts per threshold ──────────────────────────────────── +def cohort_counts(qc_df): + rows = [] + valid = qc_df[qc_df["conc_val"].notna()] # "Too low" / invalid always excluded + for t in THRESHOLDS: + passing = valid if t == 0 else valid[valid["conc_val"] >= t] + rows.append({ + "threshold": t, + "total": len(passing), + "healthy": (passing["Exf"] == 0).sum(), + "diseased": (passing["Exf"] == 1).sum(), + }) + return pd.DataFrame(rows) + +counts_A = cohort_counts(qa) +counts_B = cohort_counts(qb) + +# ── pilot failures per threshold (already-run samples excluded at each cutoff) ─ +def pilot_failures(pilot_tissue_df): + """For each threshold, count pilot samples that FAIL (conc < threshold or NaN).""" + rows = [] + for t in THRESHOLDS: + if t == 0: + failing = pilot_tissue_df.iloc[0:0] # nothing fails at 0 + else: + failing = pilot_tissue_df[ + pilot_tissue_df["conc_val"].isna() | (pilot_tissue_df["conc_val"] < t) + ] + rows.append({ + "threshold": t, + "fail_healthy": (failing["Exf"] == 0).sum(), + "fail_diseased": (failing["Exf"] == 1).sum(), + }) + return pd.DataFrame(rows) + +pilot_A = pilot[pilot["tissue"] == "A"] +pilot_B = pilot[pilot["tissue"] == "B"] +pilot_fail_A = pilot_failures(pilot_A) +pilot_fail_B = pilot_failures(pilot_B) + +def pilot_passes(pilot_tissue_df): + """For each threshold, count pilot samples that PASS (conc >= threshold).""" + rows = [] + valid = pilot_tissue_df[pilot_tissue_df["conc_val"].notna()] + for t in THRESHOLDS: + passing = valid if t == 0 else valid[valid["conc_val"] >= t] + rows.append({ + "threshold": t, + "pass_healthy": (passing["Exf"] == 0).sum(), + "pass_diseased": (passing["Exf"] == 1).sum(), + }) + return pd.DataFrame(rows) + +pilot_pass_A = pilot_passes(pilot_A) +pilot_pass_B = pilot_passes(pilot_B) + + +# ── save Excel ──────────────────────────────────────────────────────────────── +bucket_rows = [ + {"threshold": t, "bucket": d["label"], "pilot_n": d["n"], + "mean_miRNA": d["mean"], "sd_miRNA": d["sd"], "samples": d["samples"]} + for t, d in bucket_yield.items() +] + +with pd.ExcelWriter(OUT_EXCEL, engine="openpyxl") as writer: + counts_A.to_excel(writer, sheet_name="A cohort counts", index=False) + counts_B.to_excel(writer, sheet_name="B cohort counts", index=False) + pd.DataFrame(bucket_rows).to_excel(writer, sheet_name="Bucketed yield", index=False) + for ws in writer.sheets.values(): + for col_cells in ws.columns: + w = max(len(str(c.value)) if c.value else 0 for c in col_cells) + ws.column_dimensions[col_cells[0].column_letter].width = w + 4 +print(f"Excel saved → {OUT_EXCEL}") + + +# ── figure ──────────────────────────────────────────────────────────────────── +# 4 bars per threshold: A-healthy | A-diseased | B-healthy | B-diseased +# Each bar is stacked into 3 segments (bottom → top): +# 1. New samples passing — solid colour +# 2. Pilot samples passing — same colour, hatched "////" +# 3. Pilot samples failing — light grey, hatched "xxxx" (already run, excluded) + +fig, ax = plt.subplots(figsize=(18, 6)) +fig.suptitle("miRNA QC Threshold Analysis", + fontsize=13, fontweight="bold", y=1.01) + +n_thresh = len(THRESHOLDS) +group_w = 0.72 +bar_w = group_w / 4 +offsets = np.array([-1.5, -0.5, 0.5, 1.5]) * bar_w +x = np.arange(n_thresh) + +COL_PILOT_FAIL = "#bbbbbb" # muted grey for already-run failures + +# For each of the 4 bar positions we need three value arrays (length = n_thresh): +# new_pass, pilot_pass, pilot_fail +def split_counts(counts_df, ppass_df, pfail_df, diagnosis): + """Return (new_pass, pilot_pass, pilot_fail) arrays across thresholds.""" + total = counts_df[diagnosis].values + pp = ppass_df[f"pass_{diagnosis}"].values + pf = pfail_df[f"fail_{diagnosis}"].values + new_pass = total - pp # non-pilot samples passing + return new_pass, pp, pf + +specs = [ + # (counts_df, ppass_df, pfail_df, diag, color, tissue_hatch, label_base) + (counts_A, pilot_pass_A, pilot_fail_A, "healthy", COL_HEALTHY, False, "A — Healthy"), + (counts_A, pilot_pass_A, pilot_fail_A, "diseased", COL_DISEASED, False, "A — Diseased"), + (counts_B, pilot_pass_B, pilot_fail_B, "healthy", COL_HEALTHY, True, "B — Healthy"), + (counts_B, pilot_pass_B, pilot_fail_B, "diseased", COL_DISEASED, True, "B — Diseased"), +] + +# Track whether we've added each legend entry already +legend_added = set() + +for (c_df, pp_df, pf_df, diag, color, is_B, lbl), offset in zip(specs, offsets): + new_pass, pilot_pass, pilot_fail = split_counts(c_df, pp_df, pf_df, diag) + xpos = x + offset + tissue_hatch = "////" if is_B else "" + + # ── segment 1: new samples passing ─────────────────────────────────────── + key1 = f"new_{diag}_{'B' if is_B else 'A'}" + ax.bar(xpos, new_pass, width=bar_w, + color=color, hatch=tissue_hatch, alpha=0.85, + edgecolor="white" if is_B else color, linewidth=0.5, zorder=3, + label=lbl if key1 not in legend_added else "_nolegend_") + legend_added.add(key1) + + # ── segment 2: pilot samples passing (stacked, same colour, denser hatch) ─ + pilot_hatch = "////////" if is_B else "////" + ax.bar(xpos, pilot_pass, width=bar_w, bottom=new_pass, + color=color, hatch=pilot_hatch, alpha=0.55, + edgecolor="white", linewidth=0.5, zorder=3, + label="Pilot — passing (already run)" if "pilot_pass" not in legend_added else "_nolegend_") + legend_added.add("pilot_pass") + + # ── segment 3: pilot samples failing (stacked above, grey) ─────────────── + pilot_fail_bottom = new_pass + pilot_pass + ax.bar(xpos, pilot_fail, width=bar_w, bottom=pilot_fail_bottom, + color=COL_PILOT_FAIL, hatch="xxxx", alpha=0.7, + edgecolor="white", linewidth=0.5, zorder=3, + label="Pilot — failing (already run)" if "pilot_fail" not in legend_added else "_nolegend_") + legend_added.add("pilot_fail") + +# yield annotations +y_max = max( + (counts_A[["healthy","diseased"]] + pilot_fail_A[["fail_healthy","fail_diseased"]].values).max().max(), + (counts_B[["healthy","diseased"]] + pilot_fail_B[["fail_healthy","fail_diseased"]].values).max().max(), +) +annot_y = y_max * 1.08 + +for i, t in enumerate(THRESHOLDS): + yld = bucket_yield[YIELD_BUCKET_MAP[t]] + txt = fmt_yield(yld) + ax.text(i, annot_y, txt, + ha="center", va="bottom", fontsize=6.5, color="#444444", + bbox=dict(boxstyle="round,pad=0.25", fc="lightyellow", + ec="goldenrod", alpha=0.8, lw=0.6)) + +ax.set_xticks(x) +ax.set_xticklabels([str(t) for t in THRESHOLDS], fontsize=9) +ax.set_xlabel("Min. concentration threshold (ng/µl)", fontsize=10) +ax.set_ylabel("N samples", fontsize=10) +ax.set_ylim(0, annot_y * 1.22) +ax.tick_params(labelsize=9) + +legend_handles = [ + Patch(facecolor="gray", edgecolor="gray", hatch="", label="A — Schirmer strips"), + Patch(facecolor="gray", edgecolor="white", hatch="////", label="B — Lens tissues"), + Patch(facecolor=COL_HEALTHY, label="Healthy"), + Patch(facecolor=COL_DISEASED, label="Diseased"), + Patch(facecolor="gray", edgecolor="white", hatch="////", alpha=0.55, label="Pilot — passing"), + Patch(facecolor=COL_PILOT_FAIL,edgecolor="white", hatch="xxxx", alpha=0.7, label="Pilot — failing"), +] +ax.legend(handles=legend_handles, fontsize=7.5, framealpha=0.85, + loc="upper right", ncol=2) + +ax.text(0.01, 0.99, + "Bars: new samples passing (solid) + pilot passing (light hatch) + pilot failing (grey, above)\n" + "Annotations: approx. expected miRNA yield (pilot bucket mean, A + B combined)", + transform=ax.transAxes, fontsize=6, va="top", color="gray", linespacing=1.5) + +# ── per-threshold report table below x-axis ─────────────────────────────────── +# Uses blended transform: data coords for x, axes fraction for y (negative = below axis) +from matplotlib.transforms import blended_transform_factory +trans = blended_transform_factory(ax.transData, ax.transAxes) + +table_rows = [] +for i in range(len(THRESHOLDS)): + ah_tot = int(counts_A["healthy"].iloc[i]) + bh_tot = int(counts_B["healthy"].iloc[i]) + ag_tot = int(counts_A["diseased"].iloc[i]) + bg_tot = int(counts_B["diseased"].iloc[i]) + ah_rem = ah_tot - int(pilot_pass_A["pass_healthy"].iloc[i]) + bh_rem = bh_tot - int(pilot_pass_B["pass_healthy"].iloc[i]) + ag_rem = ag_tot - int(pilot_pass_A["pass_diseased"].iloc[i]) + bg_rem = bg_tot - int(pilot_pass_B["pass_diseased"].iloc[i]) + table_rows.append((ah_tot, bh_tot, ag_tot, bg_tot, + ah_rem, bh_rem, ag_rem, bg_rem)) + +for i, t in enumerate(THRESHOLDS): + ah_tot, bh_tot, ag_tot, bg_tot, ah_rem, bh_rem, ag_rem, bg_rem = table_rows[i] + + def cell(val, prev=None): + if prev is None: + return f"{val:2d} " + d = val - prev + return f"{val:2d} (Δ{d:+d})" if d != 0 else f"{val:2d} (Δ0) " + + if i == 0: + line1 = (f"Total: AH: {cell(ah_tot)} | BH: {cell(bh_tot)} | " + f"AG: {cell(ag_tot)} | BG: {cell(bg_tot)}") + line2 = (f"Remaining: AH: {cell(ah_rem)} | BH: {cell(bh_rem)} | " + f"AG: {cell(ag_rem)} | BG: {cell(bg_rem)}") + else: + p = table_rows[i - 1] + line1 = (f"Total: AH: {cell(ah_tot, p[0])} | BH: {cell(bh_tot, p[1])} | " + f"AG: {cell(ag_tot, p[2])} | BG: {cell(bg_tot, p[3])}") + line2 = (f"Remaining: AH: {cell(ah_rem, p[4])} | BH: {cell(bh_rem, p[5])} | " + f"AG: {cell(ag_rem, p[6])} | BG: {cell(bg_rem, p[7])}") + total_rem = ah_rem + bh_rem + ag_rem + bg_rem + line3 = f"Total remaining to sequence: {total_rem}" + table_txt = line1 + "\n" + line2 + "\n" + line3 + + ax.text(i, -0.18, table_txt, + transform=trans, ha="center", va="top", + fontsize=7, family="monospace", color="#222222", + bbox=dict(boxstyle="round,pad=0.4", fc="white", + ec="#cccccc", lw=0.8), + clip_on=False) + +fig.subplots_adjust(bottom=0.28) +fig.savefig(OUT_FIG, dpi=150, bbox_inches="tight") +print(f"Figure saved → {OUT_FIG}") +plt.show() diff --git a/Pilot/code/qc_threshold_analysis.py b/Pilot/code/qc_threshold_analysis.py new file mode 100644 index 0000000..d9ed09e --- /dev/null +++ b/Pilot/code/qc_threshold_analysis.py @@ -0,0 +1,62 @@ +import pandas as pd +import numpy as np + +QC_FILE = "BEA25P077_RP.xlsx" +PATIENTS_FILE = "patients.xlsx" +THRESHOLDS = [0, 0.1, 0.25, 0.5, 0.75, 1.0, 1.5, 2.0, 5.0] + +# Load patient data +patients = pd.read_excel(PATIENTS_FILE, usecols=["Number", "Exf"]) +patients = patients.dropna(subset=["Number"]) +patients["Number"] = patients["Number"].astype(int) +patients["Exf"] = patients["Exf"].astype(int) + +def parse_conc(val): + """Return float concentration, or NaN for 'Too low' / missing.""" + if isinstance(val, str): + return np.nan + try: + return float(val) + except (TypeError, ValueError): + return np.nan + +def analyse_sheet(sheet_name, conc_col): + df = pd.read_excel(QC_FILE, sheet_name=sheet_name, usecols=["ID", conc_col]) + df["conc_val"] = df[conc_col].apply(parse_conc) + # Extract patient number from ID like "4A_G2" or "4B_G2" + extracted = df["ID"].str.extract(r"^(\d+)[AB]")[0] + df["Number"] = pd.to_numeric(extracted, errors="coerce") + df = df.dropna(subset=["Number"]) + df["Number"] = df["Number"].astype(int) + # Merge with patient diagnosis + df = df.merge(patients, on="Number", how="left") + return df + +sheets = { + "A (Schirmer strips)": ("A-samples", "conc (ng/ul)"), + "B (Lens tissues)": ("B-samples", "Conc [ng/ul]"), +} + +print(f"{'Threshold':>10} {'Sheet':<22} {'Total':>6} {'Healthy (Exf=0)':>15} {'Diseased (Exf=1)':>16}") +print("-" * 80) + +for label, (sheet_name, conc_col) in sheets.items(): + df = analyse_sheet(sheet_name, conc_col) + total_samples = len(df) + n_too_low = df["conc_val"].isna().sum() + print(f"\n {label} ({total_samples} samples total, {n_too_low} 'Too low')") + print(f" {'Threshold':>10} {'Passing':>7} {'Healthy':>8} {'Diseased':>9} {'Unknown dx':>10}") + print(f" {'-'*55}") + for t in THRESHOLDS: + if t == 0: + # threshold=0: include all samples (Too low counts as passing) + passing = df.copy() + else: + # Only samples with a numeric conc >= threshold pass + passing = df[df["conc_val"] >= t] + healthy = (passing["Exf"] == 0).sum() + diseased = (passing["Exf"] == 1).sum() + unknown = passing["Exf"].isna().sum() + print(f" {t:>10.2f} {len(passing):>7} {healthy:>8} {diseased:>9} {unknown:>10}") + +print() diff --git a/Pilot/miRNA_counts.xlsx b/Pilot/miRNA_counts.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..e960683dacc91d22d1fa8440f62831bb3912dd21 GIT binary patch literal 23305 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 P#n}ZsQ@rMk&sYBgME_VW literal 0 HcmV?d00001 diff --git a/Pilot/mirna_threshold_summary.xlsx b/Pilot/mirna_threshold_summary.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..1306c2fdd471ed95199b86b4ca599bd3c1165047 GIT binary patch literal 6236 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 Ls9N15zn=aNF6Q78 literal 0 HcmV?d00001 diff --git a/Pilot/mirna_threshold_unified_summary.xlsx b/Pilot/mirna_threshold_unified_summary.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..0c6e15f7766074978edc14d3fb215a3cd069e58c GIT binary patch literal 6482 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 J7s8)!{|5+jM{588 literal 0 HcmV?d00001 diff --git a/Pilot/patients.xlsx b/Pilot/patients.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..9e42d58313eeda6cd0812582ab3a0a9b2d27fff3 GIT binary patch literal 12106 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 H{=feNF&Poq literal 0 HcmV?d00001 diff --git a/Pilot/sequencing_plan.xlsx b/Pilot/sequencing_plan.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..9ab494ec8691cee10e9d954222aa8016d00ad979 GIT binary patch literal 8251 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≺A-m;+|!$DC*#p}Sd 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

+-t9cZa*W$s?(d51%C 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 literal 0 HcmV?d00001