Download src/features/item_features.py from yashb18/deepreads: direct link, hf CLI and curl.
- Browser
- Download file 3.15 kB
-
https://huggingface.co/spaces/yashb18/deepreads/resolve/main/src/features/item_features.py
- Command line
-
hf download hf://spaces/yashb18/deepreads/src/features/item_features.py
-
curl -L -o item_features.py https://huggingface.co/spaces/yashb18/deepreads/resolve/main/src/features/item_features.py
3.15 kB
| """ | |
| Item-level feature engineering. | |
| Computes per-item statistics from the training set: | |
| - popularity (interaction count) | |
| - avg_rating, rating_std | |
| - positive_rate (fraction of ratings >= threshold) | |
| - price (from metadata, normalised) | |
| - category (label-encoded) | |
| - brand (label-encoded) | |
| - title text (raw; used downstream in text_features.py) | |
| """ | |
| from __future__ import annotations | |
| import numpy as np | |
| import pandas as pd | |
| from sklearn.preprocessing import LabelEncoder | |
| from src.utils.logging import get_logger | |
| logger = get_logger(__name__) | |
| def compute_item_features( | |
| train_df: pd.DataFrame, | |
| metadata_df: pd.DataFrame, | |
| positive_threshold: float = 4.0, | |
| ) -> tuple[pd.DataFrame, LabelEncoder, LabelEncoder]: | |
| """ | |
| Compute per-item aggregate features from the training split. | |
| Args: | |
| train_df: Training interactions with [user_idx, item_idx, rating]. | |
| metadata_df: Item metadata with [item_idx, title, category, brand, price]. | |
| positive_threshold: Rating threshold for a positive interaction. | |
| Returns: | |
| Tuple of (item_features_df, category_encoder, brand_encoder). | |
| """ | |
| # Rating statistics from interactions | |
| rating_stats = ( | |
| train_df.groupby("item_idx")["rating"] | |
| .agg(["mean", "std", "count"]) | |
| .rename(columns={ | |
| "mean": "item_avg_rating", | |
| "std": "item_rating_std", | |
| "count": "item_num_ratings", | |
| }) | |
| ) | |
| rating_stats["item_rating_std"] = rating_stats["item_rating_std"].fillna(0.0) | |
| # Positive rate | |
| pos_rate = ( | |
| train_df.groupby("item_idx")["rating"] | |
| .apply(lambda x: (x >= positive_threshold).mean()) | |
| .rename("item_positive_rate") | |
| ) | |
| # Normalised popularity (log scale for heavy tail) | |
| rating_stats["item_popularity"] = np.log1p(rating_stats["item_num_ratings"]) | |
| item_stats = rating_stats.join(pos_rate, how="left").reset_index() | |
| # Merge with metadata | |
| meta_cols = ["item_idx", "title", "category", "brand", "price"] | |
| meta_cols = [c for c in meta_cols if c in metadata_df.columns] | |
| item_features = item_stats.merge(metadata_df[meta_cols], on="item_idx", how="left") | |
| # Encode categorical fields | |
| cat_enc = LabelEncoder() | |
| brand_enc = LabelEncoder() | |
| item_features["category"] = item_features["category"].fillna("Unknown") | |
| item_features["brand"] = item_features["brand"].fillna("Unknown") | |
| item_features["category_idx"] = cat_enc.fit_transform(item_features["category"]) | |
| item_features["brand_idx"] = brand_enc.fit_transform(item_features["brand"]) | |
| # Normalise price: fill NaN with median, then scale to [0, 1] | |
| price_median = item_features["price"].median() | |
| item_features["price"] = item_features["price"].fillna(price_median) | |
| price_max = item_features["price"].max() | |
| if price_max > 0: | |
| item_features["price_norm"] = item_features["price"] / price_max | |
| else: | |
| item_features["price_norm"] = 0.0 | |
| logger.info("Computed item features for %d items, %d features", | |
| len(item_features), len(item_features.columns)) | |
| return item_features, cat_enc, brand_enc | |