# Tagging data

Add [tags](https://dev.nominal.io/platform/reference/glossary.md#tag "A key-value pair on your data points, such as motor_id: 1, that lets you filter a subset of points from a single dataset.") to the points you upload or stream, so one dataset can hold data from many units or executions and still be filtered by each. Tags are the basis of the [tagged approach](https://dev.nominal.io/platform/data-model/organization/tagged-data.md).

## In code

When you upload a file:

**Python**

```python
from nominal.core import NominalClient

client = NominalClient.from_profile("default")
dataset = client.get_dataset("<ri.catalog.instance.dataset.identifier>")

file = dataset.add_tabular_data(
    "sample.csv",
    timestamp_column="timestamp",
    timestamp_type="iso_8601",
    tags={"UUT": "A"},  # the same tag on every row
    # or tag_columns={"UUT": "unit"} to take each row's tag from the file's unit column
)
file.poll_until_ingestion_completed()  # raises NominalIngestError if the ingest fails
```

**MATLAB**

```matlab
client = nominal.Client.fromProfile();
dataset = client.datasetByRid("<ri.catalog.instance.dataset.identifier>");

% The same tag on every row
client.ingest("sample.csv", TimestampColumn="timestamp", Dataset=dataset, Tags=struct(UUT="A"));
```

**Rust**

```rust
use nominal::core::{CsvIngest, Timestamp};

let client = nominal::NominalClient::from_profile("default")?;
let csv = CsvIngest::new(Timestamp::iso8601("timestamp"))
    .additional_file_tag("UUT", "A"); // the same tag on every row
    // or .tag_column("UUT", "unit") to take each row's tag from the file's unit column
client.ingest().upload_csv("sample.csv", "<ri.catalog.instance.dataset.identifier>", csv).await?;
```

**Go**

```go
import (
	"bytes"
	"os"

	ingestapi "github.com/nominal-io/nominal-api-go/ingest/api"
	nominalapi "github.com/nominal-io/nominal-api-go/io/nominal/api"
	uploadapi "github.com/nominal-io/nominal-api-go/upload/api"
	"github.com/palantir/conjure-go-runtime/v2/conjure-go-client/httpclient"
	"github.com/palantir/pkg/rid"
	"github.com/palantir/pkg/safelong"
)

// httpClient is a conjure-go-runtime httpclient.Client for your Nominal REST API URL,
// and token is your API key as a bearertoken.Token
data, _ := os.ReadFile("sample.csv")
size := safelong.SafeLong(len(data))
path, _ := uploadapi.NewUploadServiceClient(httpClient).UploadFile(ctx, token, "sample.csv", &size, nil,
	httpclient.RequestBodyInMemory(bytes.NewReader(data)))
datasetRid, _ := rid.ParseRID("<ri.catalog.instance.dataset.identifier>")
tags := map[nominalapi.TagName]nominalapi.TagValue{"UUT": "A"} // the same tag on every row
ingestapi.NewIngestServiceClient(httpClient).Ingest(ctx, token, ingestapi.IngestRequest{
	Options: ingestapi.NewIngestOptionsFromCsv(ingestapi.CsvOpts{
		Source: ingestapi.NewIngestSourceFromS3(ingestapi.S3IngestSource{Path: string(path)}),
		Target: ingestapi.NewDatasetIngestTargetFromExisting(ingestapi.ExistingDatasetIngestDestination{DatasetRid: datasetRid}),
		TimestampMetadata: ingestapi.TimestampMetadata{
			SeriesName:    "timestamp",
			TimestampType: ingestapi.NewTimestampTypeFromAbsolute(ingestapi.NewAbsoluteTimestampFromIso8601(ingestapi.Iso8601Timestamp{})),
		},
		AdditionalFileTags: &tags,
		// or TagColumns to take each row's tag from a column of the file
	}),
})
```

When you stream points:

**Python**

```python
from datetime import datetime, timezone

from nominal.core import NominalClient

client = NominalClient.from_profile("default")
dataset = client.get_dataset("<ri.catalog.instance.dataset.identifier>")

with dataset.get_write_stream() as stream:
    stream.enqueue("temperature", datetime.now(timezone.utc), 25.0, tags={"UUT": "A"})
```

**MATLAB**

```matlab
client = nominal.Client.fromProfile();
dataset = client.datasetByRid("<ri.catalog.instance.dataset.identifier>");

stream = dataset.stream();
temperature = stream.channel("temperature");
temperature.tag("UUT", "A");  % applies to every point pushed after it
temperature.push(datetime("now", TimeZone="UTC"), 25.0);
```

**Rust**

```rust
use nominal::streaming::prelude::*;
use nominal::streaming::stream::NominalDatasetStreamBuilder;
use std::time::UNIX_EPOCH;

// token: your API key, as a BearerToken
let stream = NominalDatasetStreamBuilder::new()
    .stream_to_core(token, ResourceIdentifier::new("<ri.catalog.instance.dataset.identifier>")?, tokio::runtime::Handle::current())
    .build();
stream
    .double_writer(ChannelDescriptor::with_tags("temperature", [("UUT", "A")]))
    .push(UNIX_EPOCH.elapsed()?, 25.0);
```

**Go**

```go
import (
	"time"

	nominal "github.com/nominal-io/nominal-streaming-go"
)

// apiKey is your API key
streamClient, _ := nominal.NewClient(apiKey)
stream, _, _ := streamClient.NewDatasetStream(ctx, "<ri.catalog.instance.dataset.identifier>")
stream.FloatStream("temperature", nominal.WithTags(nominal.Tags{"UUT": "A"})).Enqueue(time.Now().UnixNano(), 25.0)
stream.Close()
```

## In the Nominal app

In the upload wizard’s **Settings** panel:

- To give every row the same tag, under **Tags** click **ADD TAG**, choose or create the tag key, then click its value and choose or create the value.
- To take each row’s tag from a column of the file, under **Tag columns** click **ADD TAG COLUMN** and pick the column. The column’s name becomes the tag key, and you can rename it. Tag columns work for CSV and Parquet files.

Streamed points get their tags from the code that streams them.
