Logo ProcessCore

Using DataContext

DataContext describes what a data file or selected data fragment represents. The Data object still owns file-location information such as path, selector, selectorFormat, and encodingFormat. The DataContext adds semantic context around that target, such as its explication, object type, and unit.

This split lets process graphs stay focused on provenance while Datamap entries describe how to interpret selected data regions. Typical workflows use DataContext to answer questions such as:

Create DataContext Entries

Start with ontology-backed terms for the meanings you need to recover later. DefinedTerm.SemanticallyEquals prefers TAN equality when both terms have TANs, and otherwise falls back to exact term equality.

let proteinIdentifier = DefinedTerm("protein identifier", tan = "http://purl.obolibrary.org/obo/NCIT_C165059")
let lfqIntensity = DefinedTerm("LFQ intensity", tan = "http://purl.obolibrary.org/obo/MS_1001902")
let arbitraryUnit = DefinedTerm("arbitrary unit")

proteinIdentifier.SemanticallyEquals(DefinedTerm("protein accession", tan = "http://purl.obolibrary.org/obo/NCIT_C165059"))
true

A dataset can contain whole-file Data entries and fragment-level DataContext entries. For CSV and TSV fragments, register the RFC 7111 selector provider before asking containment questions.

let contextDemo = Dataset("datacontext-demo")
contextDemo.RegisterFragmentSelectorProvider(CsvFragmentSelectorProvider())

let resultPath = "results/proteins.tsv"
let selectorFormat = CsvFragmentSelectorProvider.SelectorFormatUri
let tabularEncoding = "text/tab-separated-values"

let resultFile = Data(resultPath, encodingFormat = tabularEncoding)
let measuredColumn = Data(resultPath, selector = "#col=3", selectorFormat = selectorFormat, encodingFormat = tabularEncoding)

let analysis = Process("analysis")
analysis.SetOutputData(measuredColumn)

contextDemo.AddDataFile(resultFile)
contextDemo.AddProcess(analysis)

contextDemo.AddDataContext(
    DataContext(
        Data(resultPath, selector = "#col=1", selectorFormat = selectorFormat, encodingFormat = tabularEncoding),
        explication = proteinIdentifier,
        objectType = DefinedTerm("String")))

contextDemo.AddDataContext(
    DataContext(
        Data(resultPath, selector = "#col=2-5", selectorFormat = selectorFormat, encodingFormat = tabularEncoding),
        explication = lfqIntensity,
        objectType = DefinedTerm("Float"),
        unit = arbitraryUnit))

Find Contexts By File Path

Dataset.DataContextsForPath ignores selectors and returns every context attached to a file path. This is useful when you know which file you will read, but still need to discover which fragments carry which meaning.

let contextsForFile =
    contextDemo.DataContextsForPath(resultPath)
    |> Seq.choose (fun dc -> dc.Explication |> Option.map (fun term -> term.Name))
    |> Seq.toList

contextsForFile
["protein identifier"; "LFQ intensity"]

Use DataContext.ExplicationEquals, ObjectTypeEquals, and UnitEquals when matching semantic terms.

let identifierContext =
    contextDemo.DataContextsForPath(resultPath)
    |> Seq.find (fun dc -> dc.ExplicationEquals(proteinIdentifier))

let identifierColumn =
    identifierContext.Data.Selector
    |> Option.bind CsvFragmentSelectorProvider.TryGetZeroBasedColumnIndex

identifierColumn
Some 0

Match Contexts To Data Fragments

Dataset.DataContextsCoveringData compares a queried Data node with the Data targets on registered data contexts. It returns exact matches and contexts whose selector contains the queried selector. In the example below, the process graph produced column 3, and the LFQ context covers columns 2-5.

let coveringContexts =
    contextDemo.DataContextsCoveringData(measuredColumn)
    |> Seq.choose (fun dc -> dc.Explication |> Option.map (fun term -> term.Name))
    |> Seq.toList

coveringContexts
["LFQ intensity"]

If you want the data nodes themselves, Dataset.DataWithDataContextByExplication scans AllData() and returns pairs of process data and matching contexts.

let abundanceData =
    contextDemo.DataWithDataContextByExplication(lfqIntensity)
    |> Seq.map (fun (data, _) -> data.Selector |> Option.defaultValue "")
    |> Seq.toList

abundanceData
["#col=3"]

ARC Core stops at identifying paths, selectors, and metadata. It does not load dataframes, compute correlations, or render plots. After ARC Core identifies resultPath, identifierColumn, and abundanceData, pass those values to the table or plotting library of your choice.

Metadata-Powered Analysis

This applied example follows the metadata-powered data analysis pattern from the fragment-level FAIRness paper: combine process metadata with Datamap entries to find data columns of interest. The setup below creates a small process graph and Datamap. Column 1 contains protein identifiers, while columns 2-5 contain LFQ intensity values.

Show example data setup

let temperature = DefinedTerm("temperature", tan = "https://bioregistry.io/NCRO:0000029")
let biologicalReplicate = DefinedTerm("biological replicate group", tan = "https://bioregistry.io/DPBO:1000183")
let technicalReplicate = DefinedTerm("technical replicate group", tan = "https://bioregistry.io/DPBO:1000184")
let proteinIdentifier = DefinedTerm("protein identifier", tan = "http://purl.obolibrary.org/obo/NCIT_C165059")
let lfqIntensity = DefinedTerm("LFQ intensity", tan = "http://purl.obolibrary.org/obo/MS_1001902")

let dataset = Dataset("metadata-powered-analysis")
dataset.RegisterFragmentSelectorProvider(CsvFragmentSelectorProvider())

dataset.AddDataFile(Data("proteomics_result.tsv", encodingFormat = "text/tab-separated-values"))
dataset.AddDataContext(
    DataContext(
        Data("proteomics_result.tsv", selector = "#col=1", selectorFormat = CsvFragmentSelectorProvider.SelectorFormatUri, encodingFormat = "text/tab-separated-values"),
        explication = proteinIdentifier,
        objectType = DefinedTerm("String")))
dataset.AddDataContext(
    DataContext(
        Data("proteomics_result.tsv", selector = "#col=2-5", selectorFormat = CsvFragmentSelectorProvider.SelectorFormatUri, encodingFormat = "text/tab-separated-values"),
        explication = lfqIntensity,
        objectType = DefinedTerm("Float")))

let source = Sample("Base culture", additionalType = "Source")

let addResult condition bioRep techRep selector =
    let culture = Sample($"Culture {condition} C replicate {bioRep}", additionalType = "Sample")
    culture.AddAdditionalProperty(Annotation("temperature", value = condition, unit = "degree Celsius", nameTAN = temperature.TAN.Value, additionalType = "FactorValue"))

    let aliquot = Sample($"Aliquot {condition} C replicate {bioRep}.{techRep}", additionalType = "Sample")
    aliquot.AddAdditionalProperty(Annotation("biological replicate group", value = bioRep, nameTAN = biologicalReplicate.TAN.Value, additionalType = "CharacteristicValue"))
    aliquot.AddAdditionalProperty(Annotation("technical replicate group", value = techRep, nameTAN = technicalReplicate.TAN.Value, additionalType = "CharacteristicValue"))

    let data = Data("proteomics_result.tsv", selector = selector, selectorFormat = CsvFragmentSelectorProvider.SelectorFormatUri, encodingFormat = "text/tab-separated-values")

    let growth = Process($"Growth {condition} C {bioRep}.{techRep}")
    growth.SetInputSample(source)
    growth.SetOutputSample(culture)

    let preparation = Process($"Prepare sample {condition} C {bioRep}.{techRep}")
    preparation.SetInputSample(culture)
    preparation.SetOutputSample(aliquot)

    let analysis = Process($"Computational proteome analysis {condition} C {bioRep}.{techRep}")
    analysis.SetInputSample(aliquot)
    analysis.SetOutputData(data)

    dataset.AddProcess(growth)
    dataset.AddProcess(preparation)
    dataset.AddProcess(analysis)
    data

addResult "35" "1" "1" "#col=2" |> ignore
addResult "35" "1" "2" "#col=3" |> ignore
addResult "40" "1" "1" "#col=4" |> ignore
addResult "35" "2" "1" "#col=5" |> ignore

Select Data By Process Metadata

The selected data nodes are final data fragments whose upstream process graph contains both temperature 35 and biological replicate group 1.

let hasUpstreamValue term value data =
    dataset.UpstreamAnnotationsForNode(DataNode data)
    |> Seq.exists (fun pv -> pv.NameEquals(term) && pv.Value = Some value)

let selectedData =
    dataset.FinalData()
    |> Seq.filter (fun data -> hasUpstreamValue temperature "35" data)
    |> Seq.filter (fun data -> hasUpstreamValue biologicalReplicate "1" data)
    |> Seq.toList

let selectedSelectors =
    selectedData
    |> List.map (fun data -> data.Selector.Value)

selectedSelectors
["#col=2"; "#col=3"]

Resolve Datamap Selectors

Find the index column by explication, then find the LFQ intensity context that covers each selected data fragment.

let indexColumn =
    dataset.DataContextsForPath("proteomics_result.tsv")
    |> Seq.find (fun dc -> dc.ExplicationEquals(proteinIdentifier))
    |> fun dc -> dc.Data.Selector
    |> Option.bind CsvFragmentSelectorProvider.TryGetZeroBasedColumnIndex

indexColumn
Some 0
let abundanceColumns =
    selectedData
    |> Seq.collect (fun data ->
        dataset.DataContextsCoveringData(data)
        |> Seq.filter (fun dc -> dc.ExplicationEquals(lfqIntensity))
        |> Seq.map (fun _ -> data.Selector.Value))
    |> Seq.toList

abundanceColumns
["#col=2"; "#col=3"]

At this point, an analysis script can read proteomics_result.tsv, use indexColumn as the row index, and keep abundanceColumns for the correlation or heatmap workflow. ARC Core deliberately stops at identifying and relating metadata-backed file fragments; it does not load dataframes, compute correlations, or render plots.

Label Selected Columns

Because the selected data nodes remain connected to their process graph, plotting labels can come from upstream process metadata instead of file-internal headers.

let labels =
    selectedData
    |> List.map (fun data ->
        let technicalReplicateValue =
            dataset.UpstreamAnnotationsForNode(DataNode data)
            |> Seq.find (fun pv -> pv.NameEquals(technicalReplicate))
        data.Selector.Value, technicalReplicateValue.ValueText)

labels
[("#col=2", "1"); ("#col=3", "2")]

What To Use When

Task

API

Compare ontology-backed terms

DefinedTerm.SemanticallyEquals

Match annotations by ontology-backed name

Annotation.NameEquals

Match DataContext semantics

DataContext.ExplicationEquals

Find contexts for one file path

Dataset.DataContextsForPath

Find contexts that cover a data fragment

Dataset.DataContextsCoveringData

Find data nodes by context explication

Dataset.DataWithDataContextByExplication

Convert #col=N to a dataframe index

CsvFragmentSelectorProvider.TryGetZeroBasedColumnIndex

namespace ProcessCore
val fsharpCodeBlock: summary: string -> text: string -> string
val summary: string
val text: string
Multiple items
val string: value: 'T -> string

--------------------
type string = System.String
namespace System
namespace System.Net
type WebUtility = static member HtmlDecode: value: string -> string + 1 overload static member HtmlEncode: value: string -> string + 1 overload static member UrlDecode: encodedValue: string -> string static member UrlDecodeToBytes: encodedValue: byte array * offset: int * count: int -> byte array static member UrlEncode: value: string -> string static member UrlEncodeToBytes: value: byte array * offset: int * count: int -> byte array
<summary>Provides methods for encoding and decoding URLs when processing Web requests.</summary>
System.Net.WebUtility.HtmlEncode(value: string) : string
System.Net.WebUtility.HtmlEncode(value: string, output: System.IO.TextWriter) : unit
val sprintf: format: Printf.StringFormat<'T> -> 'T
val proteinIdentifier: DefinedTerm
Multiple items
type DefinedTerm = inherit DynamicObj new: name: string * ?tan: string * ?inDefinedTermSet: string -> DefinedTerm override Equals: obj: obj -> bool override GetHashCode: unit -> int member SemanticallyEquals: other: DefinedTerm -> bool member TermAccessionShort: unit -> string member TryGetTSR: unit -> string option member InDefinedTermSet: string option with get, set member Name: string with get, set member TAN: string option with get, set
<summary> Ontology annotation referencing a term in a controlled vocabulary or ontology. schema.org/DefinedTerm </summary>

--------------------
new: name: string * ?tan: string * ?inDefinedTermSet: string -> DefinedTerm
val tan: value: 'T -> 'T (requires member Tan)
val lfqIntensity: DefinedTerm
val arbitraryUnit: DefinedTerm
member DefinedTerm.SemanticallyEquals: other: DefinedTerm -> bool
val contextDemo: Dataset
Multiple items
type Dataset = inherit DynamicObj new: identifier: string * ?title: string * ?description: string * ?additionalType: string * ?license: string * ?datePublished: string * ?dateCreated: string * ?dateModified: string * ?processes: Process seq * ?hasPart: Dataset seq * ?dataFiles: Data seq * ?agents: Agent seq * ?citations: ScholarlyArticle seq * ?dataContexts: DataContext seq * ?additionalProperty: Annotation seq -> Dataset member AddAdditionalProperty: pv: Annotation -> unit member AddAgent: agent: Agent -> unit member AddCitation: article: ScholarlyArticle -> unit member AddDataContext: dataContext: DataContext -> unit member AddDataFile: data: Data -> unit member AddPart: child: Dataset -> unit member AddProcess: proc: Process -> unit member AllAgents: unit -> ResizeArray<Agent> ...
<summary> Container and context for data, processes, administrative metadata, and datamap entries. schema.org/Dataset </summary>

--------------------
new: identifier: string * ?title: string * ?description: string * ?additionalType: string * ?license: string * ?datePublished: string * ?dateCreated: string * ?dateModified: string * ?processes: Process seq * ?hasPart: Dataset seq * ?dataFiles: Data seq * ?agents: Agent seq * ?citations: ScholarlyArticle seq * ?dataContexts: DataContext seq * ?additionalProperty: Annotation seq -> Dataset
member Dataset.RegisterFragmentSelectorProvider: provider: IFragmentSelectorProvider -> unit
Multiple items
type CsvFragmentSelectorProvider = inherit FragmentSelectorProviderBase<CsvFragmentSelector> new: unit -> CsvFragmentSelectorProvider override Relate: container: CsvFragmentSelector -> candidate: CsvFragmentSelector -> FragmentRelation override ToSelectorString: selector: CsvFragmentSelector -> string override TryParse: text: string -> CsvFragmentSelector option static member TryGetZeroBasedColumnIndex: selector: string -> int option override SelectorFormat: string static member SelectorFormatUri: string
<summary> RFC 7111 fragment selector provider for text/csv row, column, and cell fragments. </summary>

--------------------
new: unit -> CsvFragmentSelectorProvider
val resultPath: string
val selectorFormat: string
property CsvFragmentSelectorProvider.SelectorFormatUri: string with get
val tabularEncoding: string
val resultFile: Data
Multiple items
namespace Microsoft.FSharp.Data

--------------------
type Data = inherit DynamicObj new: path: string * ?selector: string * ?selectorFormat: string * ?encodingFormat: string * ?additionalType: string * ?hasPart: Data seq * ?additionalProperty: Annotation seq -> Data member AddAdditionalProperty: pv: Annotation -> unit member AddPart: data: Data -> unit member AllAnnotations: ?scope: ResizeArray<Process> -> ResizeArray<Annotation> member AllConnectedNodes: ?scope: ResizeArray<Process> -> ResizeArray<IONode> member AllConnectedProcesses: ?scope: ResizeArray<Process> -> ResizeArray<Process> member ConnectedData: ?scope: ResizeArray<Process> -> ResizeArray<Data> member ConnectedSamples: ?scope: ResizeArray<Process> -> ResizeArray<Sample> member DownstreamAnnotations: ?recipeName: string * ?scope: ResizeArray<Process> -> ResizeArray<Annotation> ...
<summary> Data file or selected fragment produced or consumed by processes. schema.org/MediaObject or File </summary>

--------------------
new: path: string * ?selector: string * ?selectorFormat: string * ?encodingFormat: string * ?additionalType: string * ?hasPart: Data seq * ?additionalProperty: Annotation seq -> Data
val measuredColumn: Data
val analysis: Process
Multiple items
type Process = inherit DynamicObj new: name: string * ?executesRecipe: Recipe * ?additionalType: string * ?input: IONode * ?output: IONode * ?parameterValue: Annotation seq -> Process member AddParameterValue: pv: Annotation -> unit member AnnotationsByName: name: string -> ResizeArray<Annotation> member CanonicalizeAllNodes: ds: Dataset -> unit member ClearInput: unit -> unit member ClearOutput: unit -> unit override Equals: obj: obj -> bool override GetHashCode: unit -> int member GetParameterValue: name: string -> Annotation ...
<summary> Core transformation node. Connects inputs to outputs by executing a recipe. bioschemas.org/LabProcess </summary>

--------------------
new: name: string * ?executesRecipe: Recipe * ?additionalType: string * ?input: IONode * ?output: IONode * ?parameterValue: Annotation seq -> Process
member Process.SetOutputData: d: Data -> unit
member Dataset.AddDataFile: data: Data -> unit
member Dataset.AddProcess: proc: Process -> unit
member Dataset.AddDataContext: dataContext: DataContext -> unit
Multiple items
type DataContext = inherit DynamicObj new: data: Data * ?explication: DefinedTerm * ?objectType: DefinedTerm * ?unit: DefinedTerm * ?label: string * ?description: string * ?generatedBy: string -> DataContext override Equals: obj: obj -> bool member ExplicationEquals: term: DefinedTerm -> bool override GetHashCode: unit -> int member ObjectTypeEquals: term: DefinedTerm -> bool member UnitEquals: term: DefinedTerm -> bool member Data: Data with get, set member Description: string option with get, set member Explication: DefinedTerm option with get, set ...
<summary> Datamap descriptor for a data object or selected data fragment. </summary>

--------------------
new: data: Data * ?explication: DefinedTerm * ?objectType: DefinedTerm * ?unit: DefinedTerm * ?label: string * ?description: string * ?generatedBy: string -> DataContext
type unit = Unit
val contextsForFile: string list
member Dataset.DataContextsForPath: path: string -> ResizeArray<DataContext>
module Seq from Microsoft.FSharp.Collections
val choose: chooser: ('T -> 'U option) -> source: 'T seq -> 'U seq
val dc: DataContext
property DataContext.Explication: DefinedTerm option with get, set
module Option from Microsoft.FSharp.Core
val map: mapping: ('T -> 'U) -> option: 'T option -> 'U option
val term: DefinedTerm
property DefinedTerm.Name: string with get, set
val toList: source: 'T seq -> 'T list
val identifierContext: DataContext
val find: predicate: ('T -> bool) -> source: 'T seq -> 'T
member DataContext.ExplicationEquals: term: DefinedTerm -> bool
val identifierColumn: int option
property DataContext.Data: Data with get, set
property Data.Selector: string option with get, set
<summary> Fragment selector </summary>
val bind: binder: ('T -> 'U option) -> option: 'T option -> 'U option
static member CsvFragmentSelectorProvider.TryGetZeroBasedColumnIndex: selector: string -> int option
val coveringContexts: string list
member Dataset.DataContextsCoveringData: data: Data -> ResizeArray<DataContext>
val abundanceData: string list
member Dataset.DataWithDataContextByExplication: term: DefinedTerm -> ResizeArray<Data * DataContext>
val map: mapping: ('T -> 'U) -> source: 'T seq -> 'U seq
val data: Data
val defaultValue: value: 'T -> option: 'T option -> 'T
val paperSetupSource: string
val temperature: DefinedTerm
val biologicalReplicate: DefinedTerm
val technicalReplicate: DefinedTerm
val dataset: Dataset
val source: Sample
Multiple items
type Sample = inherit DynamicObj new: name: string * ?additionalType: string * ?additionalProperty: Annotation seq -> Sample member AddAdditionalProperty: pv: Annotation -> unit member AllAnnotations: ?scope: ResizeArray<Process> -> ResizeArray<Annotation> member AllConnectedNodes: ?scope: ResizeArray<Process> -> ResizeArray<IONode> member AllConnectedProcesses: ?scope: ResizeArray<Process> -> ResizeArray<Process> member ConnectedData: ?scope: ResizeArray<Process> -> ResizeArray<Data> member ConnectedSamples: ?scope: ResizeArray<Process> -> ResizeArray<Sample> member DownstreamAnnotations: ?recipeName: string * ?scope: ResizeArray<Process> -> ResizeArray<Annotation> member DownstreamData: ?scope: ResizeArray<Process> -> ResizeArray<Data> ...
<summary> Input or output biological, chemical, or digital sample in the process graph. bioschemas.org/Sample </summary>

--------------------
new: name: string * ?additionalType: string * ?additionalProperty: Annotation seq -> Sample
val addResult: condition: string -> bioRep: string -> techRep: string -> selector: string -> Data
val condition: string
val bioRep: string
val techRep: string
val selector: string
val culture: Sample
member Sample.AddAdditionalProperty: pv: Annotation -> unit
Multiple items
type Annotation = inherit DynamicObj new: name: string * ?value: string * ?unit: string * ?nameTAN: string * ?valueTAN: string * ?unitTAN: string * ?additionalType: string * ?instanceOf: FormalParameter -> Annotation override Equals: obj: obj -> bool override GetHashCode: unit -> int member NameEquals: term: DefinedTerm -> bool member AdditionalType: string option with get, set member InstanceOf: FormalParameter option with get, set member Name: string with get, set member NameTAN: string option with get, set member NameText: string ...
<summary> Extensible key-value-unit triple. Primary extension mechanism of ProcessCore. schema.org/PropertyValue </summary>

--------------------
new: name: string * ?value: string * ?unit: string * ?nameTAN: string * ?valueTAN: string * ?unitTAN: string * ?additionalType: string * ?instanceOf: FormalParameter -> Annotation
property DefinedTerm.TAN: string option with get, set
<summary> Term Accession Number – identifier within the ontology </summary>
property Option.Value: string with get
val aliquot: Sample
val growth: Process
member Process.SetInputSample: m: Sample -> unit
member Process.SetOutputSample: m: Sample -> unit
val preparation: Process
val ignore: value: 'T -> unit
val hasUpstreamValue: term: DefinedTerm -> value: string -> data: Data -> bool
val value: string
member Dataset.UpstreamAnnotationsForNode: node: IONode * ?recipeName: string -> ResizeArray<Annotation>
union case IONode.DataNode: Data -> IONode
val exists: predicate: ('T -> bool) -> source: 'T seq -> bool
val pv: Annotation
member Annotation.NameEquals: term: DefinedTerm -> bool
property Annotation.Value: string option with get, set
union case Option.Some: Value: 'T -> Option<'T>
val selectedData: Data list
member Dataset.FinalData: unit -> ResizeArray<Data>
val filter: predicate: ('T -> bool) -> source: 'T seq -> 'T seq
val selectedSelectors: string list
Multiple items
module List from Microsoft.FSharp.Collections

--------------------
type List<'T> = | op_Nil | op_ColonColon of Head: 'T * Tail: 'T list interface IReadOnlyList<'T> interface IReadOnlyCollection<'T> interface IEnumerable interface IEnumerable<'T> member GetReverseIndex: rank: int * offset: int -> int member GetSlice: startIndex: int option * endIndex: int option -> 'T list static member Cons: head: 'T * tail: 'T list -> 'T list member Head: 'T member IsEmpty: bool member Item: index: int -> 'T with get ...
val map: mapping: ('T -> 'U) -> list: 'T list -> 'U list
val indexColumn: int option
val abundanceColumns: string list
val collect: mapping: ('T -> #seq<'U>) -> source: 'T seq -> 'U seq
val labels: (string * string) list
val technicalReplicateValue: Annotation
property Annotation.ValueText: string with get

Type something to start searching.