Arrow and Parquet#

pyOpenMS hands spectra, features and identifications to Apache Arrow as tables, and reads and writes the Parquet versions of the OpenMS result formats. Arrow tables convert to pandas or Polars data frames, and are what Parquet files hold. The to_arrow() functions on this page need the pyarrow package, which the arrow extra of pyOpenMS installs:

pip install "pyopenms[arrow]"

Spectra as Arrow tables#

MSExperiment.to_arrow() returns the peaks of all spectra as one table, with one row per peak:

 1import pyopenms as oms
 2from urllib.request import urlretrieve
 3
 4url = "https://raw.githubusercontent.com/OpenMS/OpenMS/develop/doc/pyopenms/src/data/"
 5urlretrieve(url + "BSA1.mzML", "BSA1.mzML")
 6exp = oms.MSExperiment()
 7oms.MzMLFile().load("BSA1.mzML", exp)
 8
 9peaks = exp.to_arrow()
10print(peaks.num_rows)
11print(peaks.column_names)
479455
['mz', 'intensity', 'rt', 'spectrum_index', 'ms_level', 'native_id', 'precursor_mz', 'precursor_charge', 'precursor_intensity', 'isolation_lower', 'isolation_upper']

The table has an ion_mobility column only if the spectra carry ion mobility, which those of BSA1.mzML do not; include_ion_mobility=False leaves it out in any case. The slower Python export, which pyOpenMS uses for an empty experiment and, with a warning, when its compiled Arrow export is missing, always adds the column, with NaN for spectra without ion mobility.

With format="semi_wide", the table has one row per spectrum instead, with the m/z and intensity values of its peaks as lists. ms_levels, min_rt, max_rt, min_mz and max_mz select what goes into the table, for example only the MS1 peaks between m/z 400 and 800:

1ms1 = exp.to_arrow(ms_levels=[1], min_mz=400, max_mz=800)
2ms1_df = ms1.to_pandas()

data="chromatograms" or data="both" export chromatograms, and a single MSSpectrum or MSChromatogram has a to_arrow() of its own.

Features and identifications#

A FeatureMap, a ConsensusMap and a PeptideIdentificationList convert the same way. Their to_arrow() has the columns of their to_df() (see Export to pandas DataFrame); for features and consensus features, the ID that to_df() uses as the index is an extra column.

1urlretrieve(url + "BSA1_F1_idmapped.featureXML", "BSA1_F1_idmapped.featureXML")
2fmap = oms.FeatureMap()
3oms.FeatureXMLFile().load("BSA1_F1_idmapped.featureXML", fmap)
4
5features = fmap.to_arrow()
6print(features.num_rows, features.column_names[:6])
256 ['peptide_sequence', 'peptide_score', 'ID_filename', 'ID_native_id', 'charge', 'rt']

For identifications, PeptideIdentificationList.to_psm_arrow() gives one row per peptide-spectrum match, with the peptidoform in ProForma notation, its modifications, the scores and the protein accessions.

Parquet bundles#

OpenMS stores identifications, feature maps and consensus maps as Parquet bundles: directories named .idparquet, .featureparquet or .consensusparquet that hold one Parquet file per table. The File formats page of the OpenMS documentation lists the files, and the TOPP tools that read and write the bundles. FileHandler writes and reads them like the XML formats:

1oms.FileHandler().storeFeatures("BSA1_F1.featureparquet", fmap)
2same = oms.FileHandler().loadFeatures("BSA1_F1.featureparquet")
3print(same.size())
256

storeIdentifications() and loadIdentifications() do the same for .idparquet, and storeConsensusFeatures() and loadConsensusFeatures() for .consensusparquet. These functions do not need pyarrow.

Each table of a bundle is an ordinary Parquet file, so other programs can read it without pyOpenMS, for example pandas:

1import pandas as pd
2
3table = pd.read_parquet("BSA1_F1.featureparquet/features.parquet")
4print(table.shape)
(256, 17)