Skip to content

Read large Olink CSV exports faster - #46

Merged
ypriverol merged 2 commits into
mainfrom
perf/faster-olink-csv
Oct 7, 2026
Merged

ypriverol merged 2 commits into
mainfrom
perf/faster-olink-csv

Conversation

@ypriverol

@ypriverol ypriverol commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

This makes the Olink CSV reader faster and lighter. It's the last commit of #45 (merged before it was pushed), now reworked.

Result on PAD000061 (1.7 GB Explore HT CSV, 6.1 M rows, 1,118 samples × 5,425 assays)

read time peak memory
main 294 s 4.2 GB
this PR ~6.7 s ~2.5 GB

Peak is the process peak over three runs (2.5–2.7 GB). The previous version of this PR took 43 s and 4.7 GB.

How

  1. Parsing: the file is parsed with pyarrow, which is already a dependency.
    • Only the columns the reader uses are read: 14 of 30 in this file.
    • Identifiers and flags are stored as categories.
    • Files of 256 MB or more are streamed in blocks to bound memory; smaller files use one multithreaded read, because streaming adds about 1 s.
    • Anything pyarrow can't parse (ragged rows, text in NPX) falls back to pandas.
  2. Matrices: NPX and LOD are placed into the sample × assay matrix by position instead of with pivot_table, with the same behaviour: first non-missing value per cell, assays sorted, assays with no values dropped. QC flags stay as integer codes until the final table.

Output

  • Identical to main's reader on all 15 local Olink files (CSV and parquet): expression, samples, features, LOD and QC-flag tables.
  • On PAD000061: samples, features and both QC-flag tables match main exactly. NPX and LOD differ in the last binary digit (|diff| ≤ 9×10⁻¹⁶ NPX, 2×10⁻¹⁵ LOD). pandas' default fast float parser misrounds 87,407 of the 5.9 M NPX values, while pyarrow matches Python's correctly rounded float() for all of them. The new values are the exact ones.
  • One intentional change: sample IDs are always read as text, so numeric-looking IDs keep their leading zeros (0670628553 stayed 670628553 before).

Tests

  • New tests cover:
    • the streamed and single reads agreeing;
    • leading zeros kept, and unused columns skipped;
    • MissingFreq values like 30%;
    • falling back when NPX contains text;
    • delimiter detection;
    • first-value-wins and missing QC flags.
  • With CI's versions: 194 passed; ruff and mypy are clean.

Summary by CodeRabbit

  • Bug Fixes
    • Improved handling of Olink CSV files with different delimiters, duplicate measurements, missing QC flags, and nonnumeric expression values.
    • Preserved leading zeros in sample IDs and text values in feature data.
  • Performance
    • Large files can be processed in streaming mode, and unused columns are skipped when supported.
  • Tests
    • Added coverage for delimiter detection, streamed parsing, value preservation, and QC flag handling.

PAD000061's 1.7 GB Explore HT CSV took 284 s to read; now about 43 s, with
identical expression, sample, feature, LOD and QC-flag tables.

- the delimiter is taken from the header line and the file is parsed with
  pandas' C engine instead of sniffing with the Python engine; files the C
  engine cannot parse fall back to the Python engine
- QC flags are normalised once per distinct value and placed into the
  sample x assay matrix by position instead of pivot_table; the first
  non-missing measurement still wins

Peak memory on that file rises from 3.7 to 4.7 GB (whole-column parsing).
Checked identical against the previous reader on all 15 local Olink files.
@qodo-code-review

Copy link
Copy Markdown

ⓘ Qodo reviews are paused because the subscription is no longer active. Ask your workspace admin to reactivate the subscription to resume reviews. Manage billing

@coderabbitai

coderabbitai Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Note

Currently processing new changes in this PR. This may take a few minutes, please wait...

⚙️ Run configuration
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: dc1fd243-9409-4ac7-a178-0735dad48221
📥 Commits

Reviewing files that changed from the base of the PR and between 05a9c9c and 4d2b622.

📒 Files selected for processing (2)
  • src/pyprideap/io/readers/olink_csv.py
  • tests/test_qc_pipeline.py
 ________________________________
< I bench-press race conditions. >
 --------------------------------
  \
   \   \
        \ /\
        ( )
      .( o ).
✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

PAD000061 (1.7 GB Explore HT CSV, 6.1M rows): read in ~6.7 s with a 2.5 GB
peak, against 294 s and 4.2 GB for the previous reader.

- parse with pyarrow, reading only the columns the reader uses (14 of 30
  here) and storing identifiers and flags as categories; files of 256 MB
  or more are streamed in blocks to bound peak memory; files pyarrow
  cannot parse fall back to pandas
- NPX and LOD matrices are built by position instead of pivot_table, with
  the same semantics (first non-missing value, assays sorted, assays
  without values dropped); QC flags keep integer codes until the final
  table
- sample IDs are always text: numeric-looking IDs keep leading zeros
- NPX is parsed exactly: pandas' default fast float parser was off in the
  last binary digit for 87,407 of 5.9M NPX values in that file (|diff| <=
  9e-16); pyarrow matches Python float() for all of them

Identical output to the previous reader on all 15 local Olink files.
@ypriverol
ypriverol merged commit b80f374 into main Oct 7, 2026
6 of 7 checks passed
@ypriverol
ypriverol deleted the perf/faster-olink-csv branch October 7, 2026 16:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant