Chapter 3. Defining machine learning features
As part of the Feature Store workflow, machine learning (ML) engineers or data scientists are responsible for identifying data sources and defining features of interest.
3.1. Setting up your working environment Copy linkLink copied to clipboard!
You must set up your Red Hat OpenShift AI working environment so that you can use features in your machine learning workflow.
Prerequisites
- You have access to the OpenShift AI project in which your cluster administrator has set up the Feature Store instance.
Procedure
- From the OpenShift AI dashboard, click Projects.
- Click the name of the project in which your cluster administrator has set up the Feature Store instance.
- In the project in which the cluster administrator set up Feature Store, create a workbench, as described in Creating a workbench.
-
To open the IDE (for example, JuypterLab), in a new window, click the open icon (
) next to the workbench.
-
Add a
feature_store.yamlfile to your notebook environment. For example, upload a local file or clone a Git repo that contains the file, as described in Uploading an existing notebook file to JupyterLab from a Git repository by using the CLI. - Open a new Python notebook.
In a cell, run the following command to install the
feastCLI:! pip install feast
Verification
Run the following command to list the available features:
! feast features listThe output should show a list of features, Feature View and data type similar to the following:
Feature Feature View Data Type credit_card_due credit_history Int64 mortgage_due credit_history Int64 student_loan_due credit_history Int64 vehicle_loan_due credit_history Int64 city zipcode_features String state zipcode_features String location_type zipcode_features StringOptionally, run the following commands to list the registered feast projects, feature views, and entities.
! feast projects list ! feast feature-views list ! feast entities list
3.2. About feature definitions Copy linkLink copied to clipboard!
A machine learning feature is a measurable property or field within a data set that a machine learning model can analyze to learn patterns and make decisions. In Feature Store, you define a feature by defining the name and data type of a field.
A feature definition is a schema that includes the field name and data type, as shown in the following example:
from feast import Field
from feast.types import Int64
credit_card_amount_due = Field(
name="credit_card_amount_due",
dtype=Int64
)
For a list of supported data types for fields in Feature Store, see the feast.types module in the Feast documentation.
In addition to field name and data type, a feature definition can include additional metadata, specified as descriptions of features, as shown in the following example:
from feast import Field
from feast.types import Int64
credit_card_amount_due = Field(
name="credit_card_amount_due",
dtype=Int64,
description="Credit card amount due for user",
tags={"team": "loan_department"},
)
3.3. Understanding Feature Store data types Copy linkLink copied to clipboard!
Feature Store supports multiple data types for building features for real-time model serving and maintaining compliance with data handling regulations.
3.3.1. Data type categories Copy linkLink copied to clipboard!
Feature Store organizes data types into six categories:
- Primitive types
- Basic scalar values such as integers, floats, strings, booleans, timestamps, and UUIDs.
- Array types
- Ordered lists of values for any primitive type.
- JSON types
- Opaque JSON data stored as strings at the protocol buffer level, with native JSON support in compatible backends.
- Map types
- Dictionary-like structures with string keys and values of any supported Feature Store type, including nested maps.
- Set types
- Collections of unique values for any primitive type. Duplicates are automatically removed.
- Struct types
- Schema-aware structured types with named, typed fields. Unlike maps, structs declare field names and types for schema validation.
3.3.2. Choosing data types Copy linkLink copied to clipboard!
Consider backend compatibility and performance when selecting data types. Different backends provide varying levels of native support for complex types such as JSON, Map, and Struct. For complete type specifications, backend support tables, and usage examples, see Feature Store data type reference.
Additional resources
3.4. Feature Store data type reference Copy linkLink copied to clipboard!
You can reference the data type specifications, backend support tables, and usage examples for Feature Store.
3.4.1. Primitive data types Copy linkLink copied to clipboard!
The following table represents all the primitive data types that Feature Store supports:
| Feature Store Type | Python Type | Description |
|---|---|---|
| Int32 | int | 32-bit signed integer |
| Int64 | int | 64-bit signed integer |
| Float32 | float | 32-bit floating point |
| Float64 | float | 64-bit floating point |
| String | str | string/text value |
| Bytes | bytes | Binary data |
| Bool | bool | Boolean value |
| UnixTimestamp | datetime | Unix timestamp (nullable) |
| Uuid | uuid.UUID | UUID (any version) |
| TimeUuid | uuid.UUID | Time-based UUID (version 1) |
| Decimal | decimal.Decimal | Arbitrary-precision decimal number |
3.4.2. Array data types Copy linkLink copied to clipboard!
All primitive types have corresponding array types for storing lists of values. The following table shows the available array data types:
| Feature Store type | Python type | Description |
|---|---|---|
| Array(Int32) | List[int] | A list of 32-bit integers. |
| Array(Int64) | List[int] | List of 64-bit integers |
| Array(Float32) | List[float] | List of 32-bit floats |
| Array(Float64) | List[float] | List of 64-bit floats |
| Array(String) | List[str] | List of strings |
| Array(Bytes) | List[bytes] | List of binary data |
| Array(Bool) | List[bool] | List of booleans |
| Array(UnixTimestamp) | List[datetime] | List of timestamps |
| Array(Uuid) | List[uuid.UUID] | A list of UUIDs. |
| Array(TimeUuid) | List[uuid.UUID] | List of time-based UUIDs |
| Array(Decimal) | List[decimal.Decimal] | List of arbitrary-precision decimals |
3.4.3. JSON data types Copy linkLink copied to clipboard!
The JSON type represents opaque JSON data. Unlike a Map, which provides schema-free key-value storage, the system stores JSON as a string at the protobuf level. However, backends use native JSON types when available. The following table provides the type, Python type and description:
| Feature Store Type | Python Type | Description |
|---|---|---|
| JSON | str (JSON-encoded) | JSON data stored as a string in protocol buffers. |
| Array(Json) | List[str] | A list of JSON-encoded strings. |
3.4.3.1. Backend support for JSON data types Copy linkLink copied to clipboard!
The following table shows the native types that each backend uses to support JSON data types:
| Backend | Native type |
|---|---|
| PostgreSQL | jsonb |
| Snowflake | JSON / VARIANT |
| Redshift | json |
| BigQuery | JSON |
| Spark | Not natively distinguished from String |
| MSSQL | nvarchar(max) |
If the native type of a backend is ambiguous, such as when PostgreSQL jsonb could be Map or JSON, the schema-declared Feature Store type takes precedence. Feature Store uses the backend-to-Feast mappings only during schema inference when you do not provide an explicit type.
3.4.4. Map data types Copy linkLink copied to clipboard!
You can store dictionary-like data structures using map data types. The following table shows the available map data types:
| Feature Store Type | Python Type | Description |
|---|---|---|
| Map | Dict[str, Any] | A dictionary with string keys and values of any supported Feature Store type, including nested maps. |
| Array(Map) | List[Dict[str, Any]] | A list of dictionaries. |
You must use strings for map keys, but you can use any supported Feature Store type for map values, such as primitives, arrays, or nested maps.
3.4.4.1. Backend support for map data types Copy linkLink copied to clipboard!
The following table shows the native types that each backend uses to support map data types:
| Backend | Native Type | Notes |
|---|---|---|
| PostgreSQL | jsonb, jsonb[] |
jsonb |
| Snowflake | VARIANT, OBJECT | Inferred as Map |
| Redshift | SUPER | Inferred as Map |
| Spark | map<string,string> |
map<> |
| Athena | map | Inferred as Map |
| MSSQL | nvarchar(max) | Serialized as string |
| DynamoDB / Redis | Proto bytes | Full proto Map support |
3.4.4.2. Map type usage examples Copy linkLink copied to clipboard!
You can use maps to store complex nested data structures. The following table shows several map options:
# Simple map
user_preferences = {
"theme": "dark",
"language": "en",
"notifications_enabled": True,
"font_size": 14
}
# Nested map
metadata = {
"profile": {
"bio": "Software engineer",
"location": "San Francisco"
},
"stats": {
"followers": 1000,
"posts": 250
}
}
# List of maps
activity_log = [
{"action": "login", "timestamp": "2024-01-01T10:00:00", "ip": "192.168.1.1"},
{"action": "purchase", "timestamp": "2024-01-01T11:30:00", "amount": 99.99},
{"action": "logout", "timestamp": "2024-01-01T12:00:00"}
]
3.4.5. Set data types Copy linkLink copied to clipboard!
All primitive types have corresponding set types to store unique values. The following table shows the available set data types:
| Feature Store type | Python type | Description |
|---|---|---|
| Set(Int32) | Set[int] | Set of unique 32-bit integers |
| Set(Int64) | Set[int] | Set of unique 64-bit integers |
| Set(Float32) | Set[float] | Set of unique 32-bit floats |
| Set(Float64) | Set[float] | Set of unique 64-bit floats |
| Set(String) | Set[str] | Set of unique strings |
| Set(Bytes) | Set[bytes] | Set of unique binary data |
| Set(Bool) | Set[bool] | Set of unique booleans |
| Set(UnixTimestamp) | Set[datetime] | Set of unique timestamps |
| Set(Uuid) | Set[uuid.UUID] | Set of unique UUIDs |
| Set(TimeUuid) | Set[uuid.UUID] | Set of unique time-based UUIDs |
| Set(Decimal) | Set[decimal.Decimal] | Set of unique arbitrary-precision decimals |
When you convert lists or other iterables to sets, the set automatically removes duplicate values.
3.4.6. Struct data types Copy linkLink copied to clipboard!
The Struct type provides a data structure with named, typed fields. You must declare field names and their types to enable schema validation. The following table shows the available struct data types:
| Feature Store Type | Python Type | Description |
|---|---|---|
| Struct({"field": Type, …}) | Dict[str, Any] | This includes named fields with typed values. |
| Array(Struct({"field": Type, …})) | List[Dict[str, Any]] | A list of structs. |
Struct data types example
from feast.types import Struct, String, Int32, Array
# Struct with named, typed fields
address_type = Struct({"street": String, "city": String, "zip": Int32})
Field(name="address", dtype=address_type)
# Array of structs
items_type = Array(Struct({"name": String, "quantity": Int32}))
Field(name="order_items", dtype=items_type)
3.4.6.1. Backend support for struct data types Copy linkLink copied to clipboard!
The following table shows the native types that each backend uses to support struct data types:
| Backend | Native Type |
|---|---|
| BigQuery | STRUCT / RECORD |
| Spark | struct<…> / array<struct<…>> |
| PostgreSQL | jsonb (serialized) |
| Snowflake | VARIANT (serialized) |
| MSSQL | nvarchar(max) (serialized) |
| DynamoDB / Redis | Proto bytes |
3.4.7. Complete feature view example Copy linkLink copied to clipboard!
The following example demonstrates a feature view that uses multiple data types:
from datetime import timedelta
from feast import Entity, FeatureView, Field, FileSource
from feast.types import (
Int32, Int64, Float32, Float64, String, Bytes, Bool, UnixTimestamp,
Uuid, TimeUuid, Decimal, Json, Array, Set, Map, Struct
)
# Define a data source
user_features_source = FileSource(
path="data/user_features.parquet",
timestamp_field="event_timestamp",
)
# Define an entity
user = Entity(
name="user_id",
description="User identifier",
)
# Define a feature view with all supported types
user_features = FeatureView(
name="user_features",
entities=[user],
ttl=timedelta(days=1),
schema=[
# Primitive types
Field(name="age", dtype=Int32),
Field(name="account_balance", dtype=Int64),
Field(name="transaction_amount", dtype=Float32),
Field(name="credit_score", dtype=Float64),
Field(name="username", dtype=String),
Field(name="profile_picture", dtype=Bytes),
Field(name="is_active", dtype=Bool),
Field(name="last_login", dtype=UnixTimestamp),
Field(name="session_id", dtype=Uuid),
Field(name="event_id", dtype=TimeUuid),
Field(name="price", dtype=Decimal),
# Array types
Field(name="daily_steps", dtype=Array(Int32)),
Field(name="transaction_history", dtype=Array(Int64)),
Field(name="ratings", dtype=Array(Float32)),
Field(name="portfolio_values", dtype=Array(Float64)),
Field(name="favorite_items", dtype=Array(String)),
Field(name="document_hashes", dtype=Array(Bytes)),
Field(name="notification_settings", dtype=Array(Bool)),
Field(name="login_timestamps", dtype=Array(UnixTimestamp)),
Field(name="related_session_ids", dtype=Array(Uuid)),
Field(name="event_chain", dtype=Array(TimeUuid)),
Field(name="historical_prices", dtype=Array(Decimal)),
# Set types (unique values only)
Field(name="visited_pages", dtype=Set(String)),
Field(name="unique_categories", dtype=Set(Int32)),
Field(name="tag_ids", dtype=Set(Int64)),
Field(name="preferred_languages", dtype=Set(String)),
Field(name="unique_device_ids", dtype=Set(Uuid)),
Field(name="unique_event_ids", dtype=Set(TimeUuid)),
Field(name="unique_prices", dtype=Set(Decimal)),
# Map types
Field(name="user_preferences", dtype=Map),
Field(name="metadata", dtype=Map),
Field(name="activity_log", dtype=Array(Map)),
# Nested collection types
Field(name="weekly_scores", dtype=Array(Array(Float64))),
Field(name="unique_tags_per_category", dtype=Array(Set(String))),
# JSON type
Field(name="raw_event", dtype=Json),
# Struct type
Field(name="address", dtype=Struct({"street": String, "city": String, "zip": Int32})),
Field(name="order_items", dtype=Array(Struct({"name": String, "qty": Int32}))),
],
source=user_features_source,
)
3.5. Specifying the data source for features Copy linkLink copied to clipboard!
As an ML engineer or a data scientist, you must specify the data source for the features that you want to define.
The data source differs depending on whether you are using an offline store, for batch data and training data sets, or an online store, for model inference. Optionally, you can use a Parquet or a Delta-formatted file as the data source. You can specify a local file or a file in storage, such as Amazon Simple Storage Service (S3).
For offline stores, specify a batch data source. You can specify a data warehouse, such as BigQuery, Snowflake, Redshift, or a data lake, such as Amazon S3 or Google Cloud Platform (GCP). You can use Feature Store to ingest and query data across both types of data sources.
For online stores, specify a database backend, such as Redis, GCP Datastore, or DynamoDB.
Prerequisites
- You know the location of the data source for your ML workflow.
Procedure
- In the editor of your choice, create a new Python file.
At the beginning of the file, specify the data source for the features that you want to define within the file.
For example, use the following code to specify the data source as a Parquet-formatted file:
from feast import FileSource from feast.data_format import ParquetFormat parquet_file_source = FileSource( file_format=ParquetFormat(), path="file:///feast/customer.parquet", )- Save the file.
3.6. About organizing features by using entities Copy linkLink copied to clipboard!
Within a feature view, you can group features that share a conceptual link or relationship together to define an entity. You can think of an entity as a primary key that you can use to fetch features. Typically, an entity maps to the domain of your use case. For example, a fraud detection use case could have customers and transactions as their entities, with group-related features that correspond to these customers and transactions.
A feature does not have to be associated with an entity. For example, a feature of a customer entity could be the number of transactions they have made on an average month, while a feature that is not observed on a specific entity could be the total number of transactions made by all users in the last month.
customer = Entity(name='dob_ssn', join_keys=['dob_ssn'])
The entity name uniquely identifies the entity. The join key identifies the physical primary key on which feature values are joined together for feature retrieval.
The following table shows example data with a single entity column (dob_ssn) and two feature columns (credit_card_due and bankruptcies).
| row | timestamp | dob_ssn | credit_card_due | bankruptcies |
|---|---|---|---|---|
| 1 | 5/22/2025 0:00:00 | 19530219_5179 | 833 | 0 |
| 2 | 5/22/2025 0:00:00 | 19500806_6783 | 1297 | 0 |
| 3 | 5/22/2025 0:00:00 | 19690214_3370 | 3912 | 1 |
| 4 | 5/22/2025 0:00:00 | 19570513_7405 | 8840 | 0 |
3.7. Creating feature views Copy linkLink copied to clipboard!
You define features within a feature view. A feature view is an object that represents a logical group of time-series feature data in a data source. Feature views indicate to Feature Store where to find your feature values, for example, in a parquet file or a BigQuery table.
By using feature views, you define the existing feature data in a consistent way for both an offline environment, when you train your models, and an online environment, when you want to serve features to models in production.
Feature Store uses feature views during the following tasks:
- Generating training datasets by querying the data source of feature views to find historical feature values. A single training data set can consist of features from multiple feature views.
- Loading feature values into an online or offline store. Feature views determine the storage schema in the online or offline store. Feature values can be loaded from batch sources or from stream sources.
- Retrieving features from the online or offline store. Feature views provide the schema definition for looking up features from the online or offline store.
When you create a feature project, the feature_repo subfolder includes a Python file that includes example feature definitions (for example, example_features.py) .
To define new features, you can edit the code in the example file or add a new file to the feature repository.
Note: Feature views only work with timestamped data. If your data does not contain timestamps, insert dummy timestamps. The following example shows how to create a table with dummy timestamps for PostgreSQL-based data:
CREATE TABLE employee_metadata (
employee_id INT PRIMARY KEY,
department TEXT,
dummy_event_timestamp TIMESTAMP DEFAULT '2024-01-01'
);
INSERT INTO employee_metadata (employee_id, department)
VALUES (1, 'Advanced'), (2, 'New');
Prerequisites
- You know what data is relevant to your use case.
- You have identified attributes in your data that you want to use as features in your ML models.
Procedure
-
In your IDE, such as JupyterLab, open the
feature_repo/example_features.pyfile that contains example feature definitions or create a new Python (.py) file in thefeature_repodirectory. Create a feature view that is relevant to your use case based on the structure shown in the following example:
credit_history_source = FileSource(1 name="Credit history", path="data/credit_history.parquet", file_format=ParquetFormat(), timestamp_field="event_timestamp", created_timestamp_column="created_timestamp", ) credit_history = FeatureView(2 name="credit_history", entities=[dob_ssn],3 ttl=timedelta(days=90),4 schema=[5 Field(name="credit_card_due", dtype=Int64), Field(name="mortgage_due", dtype=Int64), Field(name="student_loan_due", dtype=Int64), Field(name="vehicle_loan_due", dtype=Int64), Field(name="hard_pulls", dtype=Int64), Field(name="missed_payments_2y", dtype=Int64), Field(name="missed_payments_1y", dtype=Int64), Field(name="missed_payments_6m", dtype=Int64), Field(name="bankruptcies", dtype=Int64), ], source=credit_history_source,6 tags={"origin": "internet"},7 )- 1
- A data source that provides time-stamped tabular data. A feature view must always have a data source for the generation of training datasets and when materializing feature values into the online store. Possible data sources are batch data sources from data warehouses (BigQuery, Snowflake, Redshift), data lakes (S3, GCS), or stream sources. Users can push features from data sources into Feature Store, and make the features available for training or batch scoring ("offline"), for realtime feature serving ("online"), or both.
- 2
- A name that identifies the feature view in the project. Within a feature view, feature names must be unique.
- 3
- Zero or more entities. Feature views generally contain features that are properties of a specific object, in which case that object is defined as an entity and included in the feature view. If the features are not related to a specific object, the feature view might not have entities.
- 4
- (Optional) Time-to-live (TTL) to limit how far back to look when Feature Store generates historical datasets.
- 5
- One or more feature definitions.
- 6
- A reference to the data source.
- 7
- (Optional) You can add metadata, such as tags that enable filtering of features when viewing them in the UI, listing them by using a CLI command, or by querying the registry directly.
- Save the file.