multiqc-pivot 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ * @clintval
@@ -0,0 +1,133 @@
1
+ name: publish multiqc-pivot
2
+
3
+ on:
4
+ push:
5
+ tags: '[0-9]+.[0-9]+.[0-9]+'
6
+
7
+ permissions: {}
8
+
9
+ env:
10
+ UV_VERSION: 0.11.16
11
+
12
+ jobs:
13
+ on-main-branch-check:
14
+ runs-on: ubuntu-latest
15
+ permissions:
16
+ contents: read
17
+ outputs:
18
+ on_main: ${{ steps.contains_tag.outputs.retval }}
19
+ steps:
20
+ # action-contains-tag lists remote branches to find the tag; current git follows the remote
21
+ # HEAD by default, which breaks that lookup: https://github.com/rickstaa/action-contains-tag/pull/18
22
+ - name: git config --global remote.origin.followRemoteHEAD never
23
+ shell: bash
24
+ run: git config --global remote.origin.followRemoteHEAD never
25
+
26
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
27
+ with:
28
+ fetch-depth: 0
29
+ persist-credentials: false
30
+
31
+ - uses: rickstaa/action-contains-tag@a9ff27d505ba2bf074a2ebb48b208e76d35ff308 # v1.2.10
32
+ id: contains_tag
33
+ with:
34
+ reference: "main"
35
+ tag: "${{ github.ref_name }}"
36
+
37
+ unit-tests:
38
+ needs: on-main-branch-check
39
+ if: ${{ needs.on-main-branch-check.outputs.on_main == 'true' }}
40
+ permissions:
41
+ contents: read
42
+ uses: "./.github/workflows/tests.yml"
43
+
44
+ build-wheels:
45
+ needs: unit-tests
46
+ permissions:
47
+ contents: read
48
+ uses: "./.github/workflows/wheels.yml"
49
+
50
+ build-sdist:
51
+ name: build source distribution
52
+ needs: unit-tests
53
+ runs-on: ubuntu-latest
54
+ permissions:
55
+ contents: read
56
+ steps:
57
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
58
+ with:
59
+ fetch-depth: 0
60
+ persist-credentials: false
61
+
62
+ - name: Install uv
63
+ uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
64
+ with:
65
+ version: ${{ env.UV_VERSION }}
66
+ python-version: "3.11"
67
+ enable-cache: 'false'
68
+
69
+ - name: Build package
70
+ run: uv build --sdist --out-dir dist
71
+
72
+ - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
73
+ with:
74
+ name: multiqc-pivot-sdist
75
+ path: dist/*.tar.gz
76
+
77
+ publish-to-pypi:
78
+ runs-on: ubuntu-latest
79
+ needs: [build-wheels, build-sdist]
80
+ environment: pypi
81
+ permissions:
82
+ id-token: write
83
+ steps:
84
+ - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
85
+ with:
86
+ path: packages
87
+ pattern: 'multiqc-pivot-*'
88
+ merge-multiple: true
89
+
90
+ - uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
91
+ with:
92
+ packages-dir: packages/
93
+ skip-existing: true
94
+ verbose: true
95
+
96
+ make-changelog:
97
+ runs-on: ubuntu-latest
98
+ needs: publish-to-pypi
99
+ permissions:
100
+ contents: read
101
+ outputs:
102
+ release_body: ${{ steps.git-cliff.outputs.content }}
103
+ steps:
104
+ - name: Checkout the Repository at the Tagged Commit
105
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
106
+ with:
107
+ fetch-depth: 0
108
+ ref: ${{ github.ref_name }}
109
+ persist-credentials: false
110
+
111
+ - name: Generate a Changelog
112
+ uses: orhun/git-cliff-action@3d96a18cc4ec17e9dc69ddcc424ccafaf1f78ce2 # v4.9.0
113
+ id: git-cliff
114
+ with:
115
+ config: pyproject.toml
116
+ args: --latest --verbose
117
+ env:
118
+ GITHUB_REPO: ${{ github.repository }}
119
+
120
+ make-github-release:
121
+ runs-on: ubuntu-latest
122
+ environment: github
123
+ permissions:
124
+ contents: write
125
+ pull-requests: read
126
+ needs: make-changelog
127
+ steps:
128
+ - name: Create Release
129
+ env:
130
+ GH_TOKEN: ${{ github.token }}
131
+ TAG: ${{ github.ref_name }}
132
+ NOTES: ${{ needs.make-changelog.outputs.release_body }}
133
+ run: gh release create "$TAG" --repo "$GITHUB_REPOSITORY" --title "$TAG" --notes "$NOTES"
@@ -0,0 +1,37 @@
1
+ name: tests
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - "**"
7
+ tags:
8
+ - "!**"
9
+ workflow_call:
10
+
11
+ permissions:
12
+ contents: read
13
+
14
+ env:
15
+ UV_VERSION: 0.11.16
16
+
17
+ jobs:
18
+ unit-tests:
19
+ runs-on: ubuntu-latest
20
+ strategy:
21
+ matrix:
22
+ PYTHON_VERSION: ["3.10", "3.11", "3.12", "3.13"]
23
+ steps:
24
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
25
+ with:
26
+ persist-credentials: false
27
+
28
+ - name: Install uv
29
+ uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
30
+ with:
31
+ version: ${{ env.UV_VERSION }}
32
+ python-version: ${{ matrix.PYTHON_VERSION }}
33
+ enable-cache: 'false'
34
+
35
+ - name: Test the library
36
+ run: |
37
+ uv run --locked poe check-all
@@ -0,0 +1,37 @@
1
+ name: wheels
2
+
3
+ on:
4
+ pull_request:
5
+ workflow_call:
6
+ workflow_dispatch:
7
+
8
+ permissions:
9
+ contents: read
10
+
11
+ env:
12
+ UV_VERSION: 0.11.16
13
+
14
+ jobs:
15
+ build-wheels:
16
+ runs-on: ubuntu-latest
17
+ steps:
18
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
19
+ with:
20
+ persist-credentials: false
21
+
22
+ - name: Install uv
23
+ uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
24
+ with:
25
+ version: ${{ env.UV_VERSION }}
26
+ python-version: "3.11"
27
+ enable-cache: 'false'
28
+
29
+ - name: Build wheels
30
+ run: uv build --wheel --out-dir wheelhouse
31
+
32
+ - name: Upload wheels
33
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
34
+ with:
35
+ name: multiqc-pivot-wheels
36
+ path: ./wheelhouse/multiqc_pivot*.whl
37
+ if-no-files-found: error
@@ -0,0 +1,161 @@
1
+ *.html
2
+ .DS_Store
3
+ .vscode/
4
+
5
+ # Byte-compiled / optimized / DLL files
6
+ __pycache__/
7
+ *.py[cod]
8
+ *$py.class
9
+
10
+ # C extensions
11
+ *.so
12
+
13
+ # Distribution / packaging
14
+ .Python
15
+ build/
16
+ develop-eggs/
17
+ dist/
18
+ downloads/
19
+ eggs/
20
+ .eggs/
21
+ lib64/
22
+ parts/
23
+ sdist/
24
+ var/
25
+ wheels/
26
+ share/python-wheels/
27
+ *.egg-info/
28
+ .installed.cfg
29
+ *.egg
30
+ MANIFEST
31
+
32
+ # PyInstaller
33
+ # Usually these files are written by a python script from a template
34
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
35
+ *.manifest
36
+ *.spec
37
+
38
+ # Installer logs
39
+ pip-log.txt
40
+ pip-delete-this-directory.txt
41
+
42
+ # Unit test / coverage reports
43
+ htmlcov/
44
+ .tox/
45
+ .nox/
46
+ .coverage
47
+ .coverage.*
48
+ .cache
49
+ nosetests.xml
50
+ coverage.xml
51
+ *.cover
52
+ *.py,cover
53
+ .hypothesis/
54
+ .pytest_cache/
55
+ cover/
56
+
57
+ # Translations
58
+ *.mo
59
+ *.pot
60
+
61
+ # Django stuff:
62
+ *.log
63
+ local_settings.py
64
+ db.sqlite3
65
+ db.sqlite3-journal
66
+
67
+ # Flask stuff:
68
+ instance/
69
+ .webassets-cache
70
+
71
+ # Scrapy stuff:
72
+ .scrapy
73
+
74
+ # Sphinx documentation
75
+ docs/_build/
76
+
77
+ # PyBuilder
78
+ .pybuilder/
79
+ target/
80
+
81
+ # Jupyter Notebook
82
+ .ipynb_checkpoints
83
+
84
+ # IPython
85
+ profile_default/
86
+ ipython_config.py
87
+
88
+ # pyenv
89
+ # For a library or package, you might want to ignore these files since the code is
90
+ # intended to run in multiple environments; otherwise, check them in:
91
+ # .python-version
92
+
93
+ # pipenv
94
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
95
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
96
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
97
+ # install all needed dependencies.
98
+ #Pipfile.lock
99
+
100
+ # poetry
101
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
102
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
103
+ # commonly ignored for libraries.
104
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
105
+ #poetry.lock
106
+
107
+ # pdm
108
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
109
+ #pdm.lock
110
+ # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
111
+ # in version control.
112
+ # https://pdm.fming.dev/#use-with-ide
113
+ .pdm.toml
114
+
115
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
116
+ __pypackages__/
117
+
118
+ # Celery stuff
119
+ celerybeat-schedule
120
+ celerybeat.pid
121
+
122
+ # SageMath parsed files
123
+ *.sage.py
124
+
125
+ # Environments
126
+ .env
127
+ .venv
128
+ venv/
129
+ env.bak/
130
+ venv.bak/
131
+
132
+ # Spyder project settings
133
+ .spyderproject
134
+ .spyproject
135
+
136
+ # Rope project settings
137
+ .ropeproject
138
+
139
+ # mkdocs documentation
140
+ /site
141
+
142
+ # mypy
143
+ .mypy_cache/
144
+ .dmypy.json
145
+ dmypy.json
146
+
147
+ # Pyre type checker
148
+ .pyre/
149
+
150
+ # pytype static type analyzer
151
+ .pytype/
152
+
153
+ # Cython debug symbols
154
+ cython_debug/
155
+
156
+ # PyCharm
157
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
158
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
159
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
160
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
161
+ .idea/
@@ -0,0 +1,125 @@
1
+ # Development and Testing
2
+
3
+ ## Primary Development Commands
4
+
5
+ To check and resolve linting issues in the codebase, run:
6
+
7
+ ```console
8
+ uv run ruff check --fix
9
+ ```
10
+
11
+ To check and resolve formatting issues in the codebase, run:
12
+
13
+ ```console
14
+ uv run ruff format
15
+ ```
16
+
17
+ To check the unit tests in the codebase, run:
18
+
19
+ ```console
20
+ uv run pytest
21
+ ```
22
+
23
+ To check the typing in the codebase, run:
24
+
25
+ ```console
26
+ uv run mypy
27
+ ```
28
+
29
+ To generate a code coverage report after testing locally, run:
30
+
31
+ ```console
32
+ uv run coverage html
33
+ ```
34
+
35
+ To check the lock file is up to date:
36
+
37
+ ```console
38
+ uv lock --check
39
+ ```
40
+
41
+ The lock file is resolved without any user-level uv configuration, which is how CI resolves it. If your `~/.config/uv/uv.toml` sets `exclude-newer` or similar, uv will want to re-resolve and will rewrite `uv.lock`. Set `UV_NO_CONFIG=1` in your shell while working here to match CI; the environment variable matters because `poe` runs each task through its own `uv run`, which a `--no-config` flag on the outer command does not reach.
42
+
43
+ ## Shortcut Task Commands
44
+
45
+ ###### For Running Individual Checks
46
+
47
+ ```console
48
+ uv run poe check-format
49
+ uv run poe check-lint
50
+ uv run poe check-tests
51
+ uv run poe check-typing
52
+ ```
53
+
54
+ ###### For Running All Checks
55
+
56
+ ```console
57
+ uv run poe check-all
58
+ ```
59
+
60
+ ###### For Running Individual Fixes
61
+
62
+ ```console
63
+ uv run poe fix-format
64
+ uv run poe fix-lint
65
+ ```
66
+
67
+ ###### For Running All Fixes
68
+
69
+ ```console
70
+ uv run poe fix-all
71
+ ```
72
+
73
+ ###### For Running All Fixes and Checks
74
+
75
+ ```console
76
+ uv run poe fix-and-check-all
77
+ ```
78
+
79
+ ## Trying a change against a real report
80
+
81
+ Point MultiQC at any directory of QC outputs with a config that has a `sample_pivot` block. The
82
+ plugin is picked up automatically once the package is installed in the same environment:
83
+
84
+ ```console
85
+ uv run multiqc --config my_config.yml ./path-to-data
86
+ ```
87
+
88
+ ## Creating a release on PyPI
89
+
90
+ > [!NOTE]
91
+ > This project follows [Semantic Versioning](https://semver.org/), aka SemVer. In brief:
92
+ >
93
+ > - MAJOR version when you make incompatible API changes
94
+ > - MINOR version when you add functionality in a backwards compatible manner
95
+ > - PATCH version when you make backwards compatible bug fixes
96
+
97
+ > [!IMPORTANT]
98
+ > Consider editing the changelog if there are any errors or necessary enhancements.
99
+
100
+ 1. Clone the repository, ensure you are on the main branch, and that the working directory is clean.
101
+ 2. Check out a new branch to prepare the library for release.
102
+ 3. Bump the version of the library to the desired SemVer, e.g.:
103
+ ```console
104
+ # Increment the minor segment
105
+ uv version --bump minor
106
+
107
+ # Update to a specific version number
108
+ uv version 0.2.0
109
+ ```
110
+ 4. Commit the version bump changes with a Git commit message like `chore(release): bump to #.#.#`.
111
+ 5. Push the commit, open a PR, ensure tests pass, and seek reviews.
112
+ 6. Squash merge the PR into the `main` branch.
113
+ 7. Tag the new commit on the main branch with the bumped version number.
114
+
115
+ > [!WARNING]
116
+ > The tag **must** be a valid SemVer version number and **must** match the version set in (3). The [publishing GitHub Action](.github/workflows/publish_multiqc_pivot.yml) is activated by a new tag on the `main` branch containing a valid SemVer version.
117
+
118
+ GitHub Actions will take care of the remainder of the deployment and release process:
119
+
120
+ 1. Unit tests will be re-run.
121
+ 2. A source distribution will be built.
122
+ 3. A wheel distribution will be built.
123
+ 4. Assets will be deployed to PyPI with the new version.
124
+ 5. A [Conventional Commit](https://www.conventionalcommits.org/en/v1.0.0/)-aware changelog will be drafted.
125
+ 6. A GitHub release will be created with the new version tag and the drafted changelog.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright © 2026 Clint Valentine
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,142 @@
1
+ Metadata-Version: 2.5
2
+ Name: multiqc-pivot
3
+ Version: 0.1.0
4
+ Summary: A MultiQC plugin that folds related samples into one General Statistics row per group with labelled metric columns.
5
+ Project-URL: homepage, https://github.com/clintval/multiqc-pivot
6
+ Project-URL: repository, https://github.com/clintval/multiqc-pivot
7
+ Project-URL: Bug Tracker, https://github.com/clintval/multiqc-pivot/issues
8
+ Author-email: Clint Valentine <valentine.clint@gmail.com>
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Intended Audience :: Science/Research
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Natural Language :: English
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3.13
23
+ Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
24
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
25
+ Classifier: Typing :: Typed
26
+ Requires-Python: >=3.10
27
+ Requires-Dist: multiqc<2,>=1.35
28
+ Requires-Dist: pydantic>=2
29
+ Description-Content-Type: text/markdown
30
+
31
+ # multiqc-pivot
32
+
33
+ [![CI](https://github.com/clintval/multiqc-pivot/actions/workflows/tests.yml/badge.svg?branch=main)](https://github.com/clintval/multiqc-pivot/actions/workflows/tests.yml?query=branch%3Amain)
34
+ [![Python Versions](https://img.shields.io/badge/python-3.10_|_3.11_|_3.12_|_3.13-blue)](https://github.com/clintval/multiqc-pivot)
35
+ [![basedpyright](https://img.shields.io/badge/basedpyright-checked-42b983)](https://docs.basedpyright.com/latest/)
36
+ [![mypy](https://www.mypy-lang.org/static/mypy_badge.svg)](https://mypy-lang.org/)
37
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://docs.astral.sh/ruff/)
38
+
39
+ A [MultiQC](https://multiqc.info) plugin that folds related samples into one General Statistics row per group.
40
+
41
+ ## Installation
42
+
43
+ ```console
44
+ pip install multiqc-pivot
45
+ ```
46
+ ## Introduction
47
+
48
+ MultiQC gives every sample its own row.
49
+ When one subject yields several samples that are measured by different methods, say a tumour and a normal, or two tissues and a paired-genotype check, the General Statistics table ends up with a block of half-empty rows per subject.
50
+ MultiQC's own [sample grouping](https://docs.seqera.io/multiqc/reports/customisation#sample-grouping) only fills the group's row for the handful of modules that know how to merge their metrics.
51
+
52
+ This plugin runs after every module has reported and rebuilds the table:
53
+
54
+ 1. Rows for one group fold into a single row, and every folded column is prefixed by which method it came from.
55
+ 2. The original rows stay beneath the group row, so they still can be viewed.
56
+ 3. Rows for a level that does not belong in the table, such as per-library read QC, move out into their own table under General Statistics with whatever grouping they already had.
57
+ 4. Hover text, color scales, formats and hidden-by-default state carry over from the module that produced each column.
58
+
59
+ ## Example
60
+
61
+ The report below comes from the [test fixtures](tests/data/report) and the [configuration](tests/data/multiqc_config.yml) shown in the usage section:
62
+
63
+ ![General Statistics with one row per subject and a Library statistics table beneath it](docs/pivot.png)
64
+
65
+ ```yaml
66
+ sample_pivot:
67
+ group: '^(?P<group>[^. ]+)\.'
68
+ levels:
69
+ - match: '\.subject$'
70
+ - match: '\.(?P<analyte>tissueA|tissueB)$'
71
+ label: '{analyte}'
72
+ - match: '\.(?P<analyte>tissueA|tissueB) \(filtered\)$'
73
+ label: '{analyte} (filtered)'
74
+ - match: '\.library\.'
75
+ table: Library statistics
76
+ label_order: [tissueA, tissueB, tissueB (filtered)]
77
+ tables:
78
+ Library statistics:
79
+ description: Per-library read QC.
80
+ ```
81
+
82
+ ## Usage
83
+
84
+ Add a `sample_pivot` block to any MultiQC config, for example with `--config my_config.yml`.
85
+
86
+ So, with these sample names:
87
+
88
+ ```text
89
+ 101.subject
90
+ 101.tissueA
91
+ 101.tissueB
92
+ 101.tissueB (filtered data, though)
93
+ 101.tissueA.library.L1
94
+ 101.tissueA.library.L2
95
+ ```
96
+
97
+ This configuration produces one row named `101` carrying `Concordance`, `TissueA Median`, `TissueB Median`, `TissueB (filtered) % Aligned` and so on, and moves the library rows into a separate table:
98
+
99
+ ```yaml
100
+ sample_pivot:
101
+ group: '^(?P<group>[^. ]+)\.'
102
+ levels:
103
+ - match: '\.subject$'
104
+ - match: '\.(?P<analyte>tissueA|tissueB)$'
105
+ label: '{analyte}'
106
+ - match: '\.(?P<analyte>tissueA|tissueB) \(filtered\)$'
107
+ label: '{analyte} (filtered)'
108
+ - match: '\.library\.'
109
+ table: Library statistics
110
+ label_order: [tissueA, tissueB, tissueB (filtered)]
111
+ tables:
112
+ Library statistics:
113
+ description: Per-library read QC; read pairs nest under their library.
114
+ ```
115
+
116
+ ### YAML Configuration Reference
117
+
118
+ | Key | Meaning |
119
+ | --- | --- |
120
+ | `group` | A regular expression searched in every matched sample name. Its `(?P<group>...)` capture names the folded row. Required. |
121
+ | `levels` | An ordered list; the first level whose `match` is found in a sample name wins. Required. |
122
+ | `levels[].match` | A regular expression searched in the sample name. Named captures are available to `label`. |
123
+ | `levels[].label` | A format string built from the captures of `match`. Columns of matching rows are renamed with it and folded onto the group row; the row itself stays beneath. Omit it, and omit `table`, to fold the row's columns onto the group row unchanged. |
124
+ | `levels[].table` | The name of a table that receives matching rows instead of General Statistics. Rows keep their grouping, so paired reads stay nested under their library. |
125
+ | `column_title` | How a pivoted column is titled. `{label}` is the label as written, `{Label}` has its first letter upper-cased, `{title}` is the module's title. Default `{Label} {title}`. |
126
+ | `label_order` | Labels in the order their column blocks should appear. Labels not listed follow in order of first appearance. |
127
+ | `tables` | Presentation of the tables named by `levels[].table`, currently a `description` each. |
128
+
129
+ > [!TIP]
130
+ > A sample that matches no level is left where it was.
131
+ > A sample that matches a level but not `group` is left alone as well, with a warning in the log.
132
+ > Columns that a module did not declare a header for are dropped from folded rows, as MultiQC would have dropped them anyway.
133
+
134
+ ### Limitations
135
+
136
+ 1. Sample names are matched after MultiQC has cleaned them, so you must write patterns against the names you see in an un-pivoted report.
137
+ 2. Only one level of nesting exists in a MultiQC table. Rows moved into a secondary table keep the nesting they already had; rows folded into a group row become its children, and cannot nest further.
138
+ 3. Two rows in the same group that resolve to the same label collide. The first value is kept and a warning is logged, so make labels specific enough to tell such rows apart.
139
+
140
+ ## Development and Testing
141
+
142
+ See the [contributing guide](./CONTRIBUTING.md) for more information.