3. Input data structure

Here we will describe the structure of the files required to create and run an INCA model.

[1]:
import pandas as pd
import numpy as np
import pathlib
import ast
import incawrapper
from incawrapper import utils

The INCAWrapper does not directly read any files, but takes pandas dataframes as input. Thus, the user can make these dataframes using there preferred methods. In the end of this guide, we have a guide to write and read .csv or excel files to import them as correctly formatted dataframes.

The data is validated using the Pandera python package. The validation covers both columns names and data types, thus column names must be specified exactly as shown in these examples. However the dataframes are allowed to contain more columns than the once required, though these extra columns are not parsed to INCA or to the INCA results object. To view the exact validation criteria for a specific data type inspect the data schema’s found in the dataschema module.

As an example we will show how to inspect the ReactionsSchema, which further described in the section Reactions data

[2]:
incawrapper.ReactionsSchema.columns
[2]:
{'rxn_id': <Schema Column(name=rxn_id, type=DataType(str))>,
 'rxn_eqn': <Schema Column(name=rxn_eqn, type=DataType(str))>}

3.1. Reactions data

This is were the reactions and the atom map in the model are defined. INCA requires the reactions to be defined with arrows -> for irreversible reactions and <-> for reversible reactions. There are two different syntaxes for the atom maps. First and simples is just to use letters, e.g. abc. If one requires more fine-grained control INCA also supports a syntax which specifies the individual atoms more explicit: C1:a C2:b C3:c.

[3]:
utils.present_schema_overview(incawrapper.ReactionsSchema)
column name dtype required nullable description
0 rxn_id str True False The unique id of the reaction
1 rxn_eqn str True False The reaction equation with atom map. Allowed reaction arrows: ->, <->.
[4]:
model_reactions_example = pd.DataFrame(
    {
        "rxn_id": ["R1", "R2", "R3"],
        "rxn_eqn": ["A (abc) -> B (ab) + D (c)", "B (C1:a C1:b) <-> C (C1:b C2:a)", "C -> D"],
    }
)
model_reactions_example.head()
[4]:
rxn_id rxn_eqn
0 R1 A (abc) -> B (ab) + D (c)
1 R2 B (C1:a C1:b) <-> C (C1:b C2:a)
2 R3 C -> D

Notice that is is allowed to mix the atom mapping syntax and the model may contain reactions without an atom map.

3.2. Tracer data

The tracer data specify the labelled compounds added to the experiment. The dataframe has a row for each experiment-tracer-labelling group combination, more on this later. For most users it will be sufficient to consider each row one experiment-tracer combination. The tracer dataframe has the following required columns:

[5]:
utils.present_schema_overview(incawrapper.TracerSchema)
column name dtype required nullable description
0 experiment_id str True False ID of the experiment. Must be a valid MATLAB variable name, legal characters are a-z, A-Z, 0-9, and the underscore character.
1 tracer_id str True False The unique id of the tracer compound.
2 met_id str True False The metabolite id of the labelled compound.
3 atom_ids object True False Ids of the labelled atoms in the labelled atom group (equivalent to columns of the same name in the INCA GUI)
4 atom_mdv object True False mass/isotopomer distribution vector of the \nlabelled atom group (equivalent to columns of the same name in the INCA GUI). The simplest way to use this column is to specify the purity of \nthe labelling group. This is done supplying a list two numbers, e.g. [0.01, 0.99].
5 enrichment float64 True False mass/isotopomer distribution vector of the labelled atom \ngroup (equivalent to columns of the same name in the INCA GUI). The simplest way to use this column is to specify the purity of the labelling \ngroup. This is done supplying a list two numbers, e.g. `[0.5, 0.95]` specifies 95% of the compound will be fully labelled in this labelling \ngroup. If different atom positions has different purity create a different labelling group for each position. For further description please \nrefer to the INCA manual. Currently, the incawrapper only supports `atom_mdv` of length 2 for each labelling group.

Lets look at a simple and common example.

[6]:
simple_tracer = pd.DataFrame(
    {
        "experiment_id": ["exp1","exp1"],
        "met_id": ["glc", "glc"],
        "tracer_id": ["[1,2-13C]glucose","[U-13C]glucose"],
        "atom_ids" : [[1,2], [1,2,3,4,5,6]],
        "atom_mdv" : [[0.02, 0.98], [0.001, 0.999]],
        "enrichment" : [0.2, 0.8],
    }
)
simple_tracer.head()
[6]:
experiment_id met_id tracer_id atom_ids atom_mdv enrichment
0 exp1 glc [1,2-13C]glucose [1, 2] [0.02, 0.98] 0.2
1 exp1 glc [U-13C]glucose [1, 2, 3, 4, 5, 6] [0.001, 0.999] 0.8

The specification above specifies a single experiment were a mixture of two types of labelled glucose was used. The enrichment specifies that the labelled medium contained 20% of [1,2-13C]glucose and 80% [U-13C]glucose. The purity of the used tracers (atom_mdv) is [1,2-13C]glucose was 98% pure, i.e. 98% of the of the 20% glucose is labelled on the carbon 1 and 2, while 2% of the 20% glucose is has no labels (i.e. not considered the naturally abundant labelling).

3.2.1. Parallel isotopomer labelling experiment

To specify the a set of parallel isotopomer labelling experiment, i.e. different experiments that which should fitted simultaneously. In the following example, specify the experiment from above in parallel with an experiment conducted with 100% [1-13C]glucose.

[7]:
parallel_experiment_tracer = pd.DataFrame(
    {
        "experiment_id": ["exp1","exp1","exp2"],
        "met_id": ["glc", "glc", "glc"],
        "tracer_id": ["[1,2-13C]glucose","[U-13C]glucose","[1-13C]glucose"],
        "atom_ids" : [[1,2], [1,2,3,4,5,6],[1]],
        "atom_mdv" : [[0.02, 0.98], [0.001, 0.999],[0.05, 0.95]],
        "enrichment" : [0.2, 0.8,1],
    }
)
parallel_experiment_tracer.head()
[7]:
experiment_id met_id tracer_id atom_ids atom_mdv enrichment
0 exp1 glc [1,2-13C]glucose [1, 2] [0.02, 0.98] 0.2
1 exp1 glc [U-13C]glucose [1, 2, 3, 4, 5, 6] [0.001, 0.999] 0.8
2 exp2 glc [1-13C]glucose [1] [0.05, 0.95] 1.0

3.2.2. Using labelling groups

The important part for different atom labelling groups is that the tracer_id is the same. In the following, we specify that we used a [1,2-13C]glucose tracer where 98% is labelled at carbon atom 1, and the 95% is labelled carbon atom 2. In this case the enrichment has to be the same for each labelling group.

[8]:
two_labelling_groups_tracer = pd.DataFrame(
    {
        "experiment_id": ["exp1","exp1"],
        "met_id": ["glc", "glc"],
        "tracer_id": ["[1,2-13C]glucose","[1,2-13C]glucose"],
        "atom_ids" : [[1], [2]],
        "atom_mdv" : [[0.02, 0.98], [0.05, 0.95]],
        "enrichment" : [1, 1],
    }
)
two_labelling_groups_tracer.head()
[8]:
experiment_id met_id tracer_id atom_ids atom_mdv enrichment
0 exp1 glc [1,2-13C]glucose [1] [0.02, 0.98] 1
1 exp1 glc [1,2-13C]glucose [2] [0.05, 0.95] 1

3.3. Flux measurement data

Flux measurements are typically uptake or secretion rates which this does not require labelling. Therefore this data is also quite simple to define. The required columns are the following:

[9]:
utils.present_schema_overview(incawrapper.FluxMeasurementsSchema)
column name dtype required nullable description
0 experiment_id str True False ID of the experiment. Must be a valid MATLAB variable name, legal characters are a-z, A-Z, 0-9, and the underscore character.
1 rxn_id str True False The unique id of the reaction
2 flux float64 True False Measured/estimated rate typically in mmol/gDW/h
3 flux_std_error float64 True False Standard error of the measured/estimated rate

The units has to be consistent for all measurements, because INCA assumes that all rates have the same units. Notice that it is not possible supply a time point for the rate estimates. This is because INCA supports steady state and isotopically non-stationary labelling analysis. Both of these methods assumes that all rates are constant over the time duration and only isotopomer distribution vector are allowed to change over time.

[10]:
flux_measurements_example = pd.DataFrame(
    {
        "experiment_id": ["exp1", "exp1", "exp1", "exp2", "exp2", "exp2"],
        "rxn_id": ["R1", "R2", "R3", "R1", "R2", "R3"],
        "flux": [1.0, 2.0, 3.0, 1.2, 1.8, 2.8],
        "flux_std_error": [0.1, 0.5, 0.2, 0.1, 0.5, 0.2],
    }
)
flux_measurements_example.head()
[10]:
experiment_id rxn_id flux flux_std_error
0 exp1 R1 1.0 0.1
1 exp1 R2 2.0 0.5
2 exp1 R3 3.0 0.2
3 exp2 R1 1.2 0.1
4 exp2 R2 1.8 0.5

3.4. Mass spectrometry measurements

Mass spectrometry measurements are given as isotopomer distribution vectors and the measurement standard error. These can be corrected for natural abundance or not, but by default INCA performs natural abundance correction, thus this needs to be turned of in the options if it is not required (See XX). The required columns for ms measurements are:

[11]:
utils.present_schema_overview(incawrapper.MSMeasurementsSchema)
column name dtype required nullable description
0 experiment_id str True False ID of the experiment. Must be a valid MATLAB variable name, legal characters are a-z, A-Z, 0-9, and the underscore character.
1 met_id str True False Metabolite ID of metabolite which is directly measured or from which the fragment is derived through a derivatization method.
2 ms_id str True False ID of the measured ms fragment - often multiple fragment can be measured from the same metabolite
3 measurement_replicate int64 True False Replicate number of the measurement of the same fragment in the same experiment. \n"In most cases, the data will only have one measurement per fragment per experiment.
4 labelled_atom_ids object True False List of atom ids of the labelled atoms in the metabolite.
5 unlabelled_atoms str False True The molecular formula of the all atoms that cannot be labelled through \nthe introduced labels in the tracers. This typically includes non-carbon elements of the fragment and all elements originating from derivatization agent. \nINCA uses the unlabelled atoms to correct for natural abundance.
6 mass_isotope int64 True False The mass isotopomer of the fragment.\nE.g. M0, M+1, etc. Specified as an integer. It is allowed to have gaps in the isotopmer of a given fragment, e.g. 0, 2, 3. In this case the intensity and \nstd error of missing isotopomers are filled with NaN before inserted in INCA.
7 intensity float64 True True The measured intensity of the fragment mass isotope.
8 intensity_std_error float64 True True The standard error of the measured intensity of the fragment mass isotope.
9 time float64 True False Time point of measurement only relevant for isotopically non-stationary MFA analysis
[12]:
import numpy as np
n_isotopes = [3, 4, 3]
ms_measurements_example = pd.DataFrame(
    {
        "experiment_id": np.repeat(["exp1"], sum(n_isotopes)),
        "met_id": np.repeat(["tyr", "ser", "ser"], n_isotopes),
        "ms_id": np.repeat(["tyr_302", "ser_390", "ser_362"], n_isotopes),
        "measurement_replicate": np.repeat([1], sum(n_isotopes)),
        "labelled_atom_ids": np.repeat(np.array([[1,2], [1,2,3], [2,3]], dtype=object), n_isotopes),
        "unlabelled_atoms": np.repeat(["C12H32O2NSi2", "C14H40O3NSi3", "C14H40O2NSi3"], n_isotopes),
        "mass_isotope": [0, 1, 2, 0, 1, 2, 3, 0, 1, 2],
        "intensity": [0.1, 0.2, 0.4, 0.2, 0.0001, 0.003, 0.3, 0.4, 0.1, 0.5],
        "intensity_std_error": [0.01, 0.02, 0.04, 0.02, 0.0001, 0.003, 0.03, 0.04, 0.01, 0.05],
        "time": np.repeat([0], sum(n_isotopes)),
    }
)
ms_measurements_example
[12]:
experiment_id met_id ms_id measurement_replicate labelled_atom_ids unlabelled_atoms mass_isotope intensity intensity_std_error time
0 exp1 tyr tyr_302 1 [1, 2] C12H32O2NSi2 0 0.1000 0.0100 0
1 exp1 tyr tyr_302 1 [1, 2] C12H32O2NSi2 1 0.2000 0.0200 0
2 exp1 tyr tyr_302 1 [1, 2] C12H32O2NSi2 2 0.4000 0.0400 0
3 exp1 ser ser_390 1 [1, 2, 3] C14H40O3NSi3 0 0.2000 0.0200 0
4 exp1 ser ser_390 1 [1, 2, 3] C14H40O3NSi3 1 0.0001 0.0001 0
5 exp1 ser ser_390 1 [1, 2, 3] C14H40O3NSi3 2 0.0030 0.0030 0
6 exp1 ser ser_390 1 [1, 2, 3] C14H40O3NSi3 3 0.3000 0.0300 0
7 exp1 ser ser_362 1 [2, 3] C14H40O2NSi3 0 0.4000 0.0400 0
8 exp1 ser ser_362 1 [2, 3] C14H40O2NSi3 1 0.1000 0.0100 0
9 exp1 ser ser_362 1 [2, 3] C14H40O2NSi3 2 0.5000 0.0500 0

3.5. Note about formatting when reading csv and and excel files

Some of the data inputs requires the element of a cell to be a python list. This can cause issues when reading the data from .csv or excel files. To accommodate the issue simply write a list in python syntax as a string in the csv or excel file. When the file is read using pandas you will evaluate the columns the list strings, which will convert the strings in to python lists.

3.5.1. CSV files

For .csv files we can use the convert argument in the pd.read_csv functions. Lets use the ms data as an example. The csv file would look as follows

[13]:
csv_illtration_file = pathlib.Path("./examples/Literature data/simple model/ms_measurement_csv_input_example.csv")
with open(csv_illtration_file, "r") as f:
    print(f.read())
experiment_id,met_id,ms_id,measurement_replicate,labelled_atom_ids,unlabelled_atoms,mass_isotope,intensity,intensity_std_error,time
exp1,tyr,tyr_302,1,"[1, 2]",C12H32O2NSi2,0,0.1000,0.0100,0
exp1,tyr,tyr_302,1,"[1, 2]",C12H32O2NSi2,1,0.2000,0.0200,0
exp1,tyr,tyr_302,1,"[1, 2]",C12H32O2NSi2,2,0.4000,0.0400,0
exp1,ser,ser_390,1,"[1, 2, 3]",C14H40O3NSi3,0,0.2000,0.0200,0
exp1,ser,ser_390,1,"[1, 2, 3]",C14H40O3NSi3,1,0.0001,0.0001,0
exp1,ser,ser_390,1,"[1, 2, 3]",C14H40O3NSi3,2,0.0030,0.0030,0
exp1,ser,ser_390,1,"[1, 2, 3]",C14H40O3NSi3,3,0.3000,0.0300,0
exp1,ser,ser_362,1,"[2, 3]",C14H40O2NSi3,0,0.4000,0.0400,0
exp1,ser,ser_362,1,"[2, 3]",C14H40O2NSi3,1,0.1000,0.0100,0
exp1,ser,ser_362,1,"[2, 3]",C14H40O2NSi3,2,0.5000,0.0500,0

Notice that the list definitions ([]) are inclosed in double quotes, but importantly when the lists contains strings these strings should be also be inclosed in single quotes, e.g. "['C3','C4']". When this csv file is read through pd.read_csv the string lists are correctly read.

[14]:
from_csv = pd.read_csv(csv_illtration_file, converters={"labelled_atom_ids": ast.literal_eval})
from_csv.head()
[14]:
experiment_id met_id ms_id measurement_replicate labelled_atom_ids unlabelled_atoms mass_isotope intensity intensity_std_error time
0 exp1 tyr tyr_302 1 [1, 2] C12H32O2NSi2 0 0.1000 0.0100 0
1 exp1 tyr tyr_302 1 [1, 2] C12H32O2NSi2 1 0.2000 0.0200 0
2 exp1 tyr tyr_302 1 [1, 2] C12H32O2NSi2 2 0.4000 0.0400 0
3 exp1 ser ser_390 1 [1, 2, 3] C14H40O3NSi3 0 0.2000 0.0200 0
4 exp1 ser ser_390 1 [1, 2, 3] C14H40O3NSi3 1 0.0001 0.0001 0

We can verify that the data type of the idv is a list.

[15]:
type(from_csv["labelled_atom_ids"][0])
[15]:
list

And check that the dataframe passes the schema validation

[16]:
incawrapper.MSMeasurementsSchema.validate(from_csv)
[16]:
experiment_id met_id ms_id measurement_replicate labelled_atom_ids unlabelled_atoms mass_isotope intensity intensity_std_error time
0 exp1 tyr tyr_302 1 [1, 2] C12H32O2NSi2 0 0.1000 0.0100 0.0
1 exp1 tyr tyr_302 1 [1, 2] C12H32O2NSi2 1 0.2000 0.0200 0.0
2 exp1 tyr tyr_302 1 [1, 2] C12H32O2NSi2 2 0.4000 0.0400 0.0
3 exp1 ser ser_390 1 [1, 2, 3] C14H40O3NSi3 0 0.2000 0.0200 0.0
4 exp1 ser ser_390 1 [1, 2, 3] C14H40O3NSi3 1 0.0001 0.0001 0.0
5 exp1 ser ser_390 1 [1, 2, 3] C14H40O3NSi3 2 0.0030 0.0030 0.0
6 exp1 ser ser_390 1 [1, 2, 3] C14H40O3NSi3 3 0.3000 0.0300 0.0
7 exp1 ser ser_362 1 [2, 3] C14H40O2NSi3 0 0.4000 0.0400 0.0
8 exp1 ser ser_362 1 [2, 3] C14H40O2NSi3 1 0.1000 0.0100 0.0
9 exp1 ser ser_362 1 [2, 3] C14H40O2NSi3 2 0.5000 0.0500 0.0

3.5.2. Excel files

The excel file can written more straight forward without double quotes around the lists. For example a cell in labelled_atom_ids can be ['C3','C4']. Notice, that the atom ids are still wrapped in single quotes because they are defined using the C notation.

Reading from an excel file is slightly more verbose. Here we first read the file and then use ast.literal_eval() to convert the strings to python-lists.

[17]:
excel_illtration_file = pathlib.Path("./examples/Literature data/simple model/ms_measurement_csv_input_example.xlsx")
from_excel = pd.read_excel(excel_illtration_file)
from_excel[['labelled_atom_ids']] = from_excel[['labelled_atom_ids']].applymap(lambda x: ast.literal_eval(x))

Again, we see that the data read from excel passes the schema validation.

[18]:
incawrapper.MSMeasurementsSchema.validate(from_excel)
[18]:
experiment_id met_id ms_id measurement_replicate labelled_atom_ids unlabelled_atoms mass_isotope intensity intensity_std_error time
0 exp1 tyr tyr_302 1 [1, 2] C12H32O2NSi2 0 0.1000 0.0100 0.0
1 exp1 tyr tyr_302 1 [1, 2] C12H32O2NSi2 1 0.2000 0.0200 0.0
2 exp1 tyr tyr_302 1 [1, 2] C12H32O2NSi2 2 0.4000 0.0400 0.0
3 exp1 ser ser_390 1 [1, 2, 3] C14H40O3NSi3 0 0.2000 0.0200 0.0
4 exp1 ser ser_390 1 [1, 2, 3] C14H40O3NSi3 1 0.0001 0.0001 0.0
5 exp1 ser ser_390 1 [1, 2, 3] C14H40O3NSi3 2 0.0030 0.0030 0.0
6 exp1 ser ser_390 1 [1, 2, 3] C14H40O3NSi3 3 0.3000 0.0300 0.0
7 exp1 ser ser_362 1 [2, 3] C14H40O2NSi3 0 0.4000 0.0400 0.0
8 exp1 ser ser_362 1 [2, 3] C14H40O2NSi3 1 0.1000 0.0100 0.0
9 exp1 ser ser_362 1 [2, 3] C14H40O2NSi3 2 0.5000 0.0500 0.0

Now we have showed the expected structure of the input data you can move on to the next section to read about the how to use the INCAWrapper.