Personalized Grids Like Netflix
Think about interfaces like Netflix, YouTube, or major e-commerce marketplaces. They often present content not just as a single ranked list, but as a grid. Rows might represent different genres, categories, brands, or topics, while the columns within each row show specific items ordered by relevance. This grid layout is incredibly effective for discovery – it allows users to quickly scan different themes (rows) while also diving into specific relevant items (columns) within those themes.
Building such an interface, however, presents a significant personalization challenge. You need to solve two ranking problems simultaneously:
- Row Ranking: Which attributes (genres, categories, brands) are most relevant to this specific user, and in what order should they appear vertically?
- Item Ranking: Within each selected row attribute, which specific items are most relevant to this user, and how should they be ordered horizontally?
Simply showing globally popular categories or randomly ordered items within rows misses the mark. True personalization requires optimizing both dimensions based on individual user preferences. Building this capability from scratch involves tackling complex, multi-stage modeling and significant engineering efforts, akin to those faced by major platforms trying to optimize similar layouts.
Building a Personalized Grid with Shaped
Let's illustrate creating a personalized grid where rows are defined by category
.
Goal: For test_user
, display a grid with the 10 most relevant categories as rows, and the 10 most relevant items within each of those categories as columns.
1. Ensure Data is Connected: Assume item_metadata
(with an accurate category
column and other features) and user_interactions
datasets are connected and used for model training.
2. Define Your Shaped Model (YAML): A standard model definition works. Crucially, the attribute you want to use for rows (category
in this case) MUST be included as a field in your fetch.items
query.
model:
name: personalized_grid_engine
connectors:
- type: Dataset
name: item_catalog_with_category
id: items
- type: Dataset
name: user_activity
id: interactions
fetch:
items: |
SELECT
item_id,
title,
description,
category, # <-- The attribute defining the rows MUST be selected
image_url,
product_url
FROM items
events: |
SELECT user_id, item_id, timestamp AS created_at, event_type FROM interactions
3. Create the Model:
Run the following command to create a model from the configuration file:
shaped create-model --file grid_ranking_model.yaml
4. Monitor Training: Wait for the model personalized_grid_engine
to become ACTIVE
.
You can run the following command to check the status of the model training.
shaped view-model --model-name personalized_grid_engine
5. Fetch the Personalized Grid (Application Backend Logic): When you need to display the grid for test_user
:
Step A (Your Backend): Identify the
user_id
('test_user'
) and the name of the item attribute column that defines the rows ('category'
).Step B (Your Backend): Call Shaped's
/rank_attribute_grid
API endpoint.
- Python
- JavaScript
- Curl
const { ShapedClient } = require('@shapedai/shaped');
const apiKey = process.env.SHAPED_API_KEY;
const shapedClient = new ShapedClient({ apiKey });
const gridData = await shapedClient.rankAttributeGrid({
modelName: 'personalized_grid_engine',
userId: 'test_user',
attributeName: 'category', // Must match a column name in items data
rowLimit: 10,
colLimit: 10,
returnMetadata: true
});
console.log(`Successfully retrieved grid with ${gridData.rows.length} rows.`);
from shaped import Shaped
api_key = os.environ.get("SHAPED_API_KEY")
shaped_client = Shaped(api_key)
grid_data = shaped_client.rank_attribute_grid(
model_name='personalized_grid_engine',
user_id="test_user,
attribute_name="category", # Must match a column name in items data
row_limit=10,
col_limit=10,
return_metadata=True
)
print(f"Successfully retrieved grid with {len(grid_data.rows)} rows.")
# grid_data structure:
# rows:
# - attribute_value: 'Action'
# items:
# - item_meta_1
# - item_meta_2
# - attribute_value: 'Comedy'
# items:
# - item_meta_3
# - item_meta_4
curl -X POST "https://api.shaped.ai/v1/models/personalized_grid_engine/rank_attribute_grid" \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"user_id": "test_user",
"attribute_name": "category",
"row_limit": 10,
"col_limit": 10,
"return_metadata": true
}'
- Step C (Frontend): The API response contains a structured list of rows. Each row includes the
attribute_value
(e.g., 'Action', 'Comedy') and a list of rankeditems
(with metadata if requested) for that attribute, already personalized for the user. Use this structured data to directly render the grid UI.
6. Clean Up (Optional):
Run the following line of code if you want to delete your model.
shaped delete-model --model-name personalized_grid_engine
Conclusion: Sophisticated Layouts, Radically Simplified
Personalized grid layouts, like those popularized by Netflix, offer a superior discovery experience but traditionally demand substantial engineering effort, involving complex dual-ranking problems, potentially multi-armed bandit systems, and near real-time data infrastructure.
Shaped cuts through this complexity with the dedicated /rank_attribute_grid
API endpoint. By leveraging a single, powerful underlying model and handling the sophisticated optimization internally, Shaped delivers a fully personalized grid structure through one simple API call. Avoid the deep dive into bandit implementation, stream processing, and multi-model orchestration. Build engaging, dynamic grid interfaces efficiently and focus on your core product experience.
Ready to build personalized discovery grids without the engineering headache?
Request a demo of Shaped today to see attribute grid ranking in action. Or, start exploring immediately with our free trial sandbox.