online-shoppers-api / features.py
Jennigwen's picture
Upload 6 files
692220f verified
Raw
History Blame Contribute Delete
3.44 kB
import numpy as np
import pandas as pd
from model import model_columns
from schema import ShopperInput
MONTH_ORDER = {
"Jan": 1, "Feb": 2, "Mar": 3, "Apr": 4,
"May": 5, "June": 6, "Jul": 7, "Aug": 8,
"Sep": 9, "Oct": 10, "Nov": 11, "Dec": 12,
}
LOG_COLUMNS = [
"Administrative_Duration",
"Informational_Duration",
"ProductRelated_Duration",
"TotalDuration",
"ProductDurationPerPage",
"AdminDurationPerPage",
"InfoDurationPerPage",
"AvgDurationPerPage",
"ProductEngagement",
"DurationWeightedExit",
"DurationWeightedBounce",
]
def create_model_features(input_data: ShopperInput) -> pd.DataFrame:
"""Create the same engineered features that were used during training."""
df = pd.DataFrame([input_data.model_dump()])
base_numeric_columns = [
"Administrative", "Administrative_Duration",
"Informational", "Informational_Duration",
"ProductRelated", "ProductRelated_Duration",
"BounceRates", "ExitRates", "PageValues", "SpecialDay",
]
for col in base_numeric_columns:
df[col] = pd.to_numeric(df[col], errors="coerce")
# Step 1: totals
df["TotalDuration"] = (
df["Administrative_Duration"].fillna(0)
+ df["Informational_Duration"].fillna(0)
+ df["ProductRelated_Duration"].fillna(0)
)
df["TotalPages"] = (
df["Administrative"].fillna(0)
+ df["Informational"].fillna(0)
+ df["ProductRelated"].fillna(0)
)
# Step 2: averages and ratios
df["ProductDurationPerPage"] = df["ProductRelated_Duration"] / df["ProductRelated"].replace(0, np.nan)
df["AdminDurationPerPage"] = df["Administrative_Duration"] / df["Administrative"].replace(0, np.nan)
df["InfoDurationPerPage"] = df["Informational_Duration"] / df["Informational"].replace(0, np.nan)
df["ProductPageRatio"] = df["ProductRelated"] / df["TotalPages"].replace(0, np.nan)
df["AdminPageRatio"] = df["Administrative"] / df["TotalPages"].replace(0, np.nan)
df["InfoPageRatio"] = df["Informational"] / df["TotalPages"].replace(0, np.nan)
df["ProductDurationRatio"] = df["ProductRelated_Duration"] / df["TotalDuration"].replace(0, np.nan)
df["AdminDurationRatio"] = df["Administrative_Duration"] / df["TotalDuration"].replace(0, np.nan)
df["InfoDurationRatio"] = df["Informational_Duration"] / df["TotalDuration"].replace(0, np.nan)
# Step 3: engagement signals
df["AvgDurationPerPage"] = df["TotalDuration"] / df["TotalPages"].replace(0, np.nan)
df["ExitBounceGap"] = df["ExitRates"] - df["BounceRates"]
df["ExitBounceRatio"] = df["ExitRates"] / df["BounceRates"].replace(0, np.nan)
df["ProductEngagement"] = df["ProductRelated"] * df["ProductDurationPerPage"]
df["DurationWeightedExit"] = df["TotalDuration"] * (1 - df["ExitRates"])
df["DurationWeightedBounce"] = df["TotalDuration"] * (1 - df["BounceRates"])
# Step 4: month features
df["MonthNum"] = df["Month"].map(MONTH_ORDER)
df["IsHolidaySeason"] = df["Month"].isin(["Nov", "Dec"]).astype(int)
# Step 5: log-transform skewed duration features
for col in LOG_COLUMNS:
df[col + "_log"] = np.log1p(df[col].clip(lower=0))
return df[model_columns]