PracticumYandex / feature_builder.py
Iliy47's picture
Upload 2 files
7241e34 verified
Raw
History Blame Contribute Delete
6.75 kB
import pandas as pd
def build_df(
visits_df: pd.DataFrame,
ads_activity_df: pd.DataFrame,
surf_depth_df: pd.DataFrame,
primary_device_df: pd.DataFrame,
cloud_usage_df: pd.DataFrame,
users_df: pd.DataFrame = pd.DataFrame(),
) -> pd.DataFrame:
# 1. Создаём копии, чтобы не изменять исходные датафреймы
visits_df = visits_df.copy()
ads_df = ads_activity_df.copy()
surf_df = surf_depth_df.copy()
device_df = primary_device_df.copy()
cloud_df = cloud_usage_df.copy()
users_df = users_df.copy()
users_df_empty = not users_df.empty
# 2. Базовая очистка данных
visits_df = visits_df.drop_duplicates(
subset=["date", "daytime", "session_id", "user_id", "website_category"]
)
ads_df = ads_df.drop_duplicates(subset=["user_id"])
if users_df_empty:
users_df = users_df.drop_duplicates(subset=["user_id"])
# 3. Нормализация значений visits_df
daytime_map = {
"утро": "morning",
"день": "day",
"вечер": "evening",
"ночь": "night",
}
visits_df["daytime"] = visits_df["daytime"].replace(daytime_map)
# Нормализуем категории сайтов: "Category 18" -> "category_18"
visits_df["website_category"] = (
visits_df["website_category"]
.astype(str)
.str.strip()
.str.replace(" ", "_", regex=False)
.str.lower()
)
# Преобразуем date в datetime.date, чтобы корректно считать количество уникальных дней активности
visits_df["date"] = pd.to_datetime(visits_df["date"], errors="coerce").dt.date
# 4. Генерация признаков из visits_df на уровне пользователя (1 user_id = 1 строка)
# 4.1. Признаки интенсивности
base_stats_df = visits_df.groupby("user_id").agg(
total_sessions=("session_id", "nunique"),
unique_days=("date", "nunique"),
n_unique_categories=("website_category", "nunique")
)
# Среднее число сессий в день (страховка от деления на 0)
base_stats_df["sessions_per_day"] = base_stats_df["total_sessions"] / base_stats_df[
"unique_days"
].replace(0, 1)
# 4.2. Доли активности по времени суток (shares)
daytime_shares_df = pd.crosstab(
visits_df["user_id"],
visits_df["daytime"],
normalize="index",
)
# Фиксируем набор колонок, чтобы при отсутствии какого-то времени суток в данных структура не менялась
expected_daytimes = ["morning", "day", "evening", "night"]
daytime_shares_df = daytime_shares_df.reindex(
columns=expected_daytimes, fill_value=0.0
)
daytime_shares_df.columns = [
f"daytime_{c}_share" for c in daytime_shares_df.columns
]
# Наиболее активное время суток (категориальный признак на основе максимум-доли)
base_stats_df["most_active_daytime"] = (
daytime_shares_df.idxmax(axis=1)
.str.replace("daytime_", "", regex=False)
.str.replace("_share", "", regex=False)
)
# 4.3. Доли активности по категориям сайтов
webcat_shares_df = pd.crosstab(
visits_df["user_id"],
visits_df["website_category"],
normalize="index",
)
# Стабильный порядок колонок (для воспроизводимости результатов)
webcat_cols = sorted(webcat_shares_df.columns)
webcat_shares_df = webcat_shares_df.reindex(columns=webcat_cols, fill_value=0.0)
webcat_shares_df.columns = [f"web_{c}_share" for c in webcat_shares_df.columns]
# Максимальная доля среди категорий - признак концентрации интересов пользователя
base_stats_df["top_category_share"] = webcat_shares_df.max(axis=1)
# 4.4. Финальная сборка visits-признаков
visits_features_df = pd.concat(
[base_stats_df, daytime_shares_df, webcat_shares_df], axis=1
).reset_index()
# 5. Нормализация ads_activity (приводим к единым англоязычным категориям)
ads_activity_map = {
"очень редко": "very_rarely",
"редко": "rarely",
"умеренно": "moderately",
"часто": "often",
"очень часто": "very_often",
}
ads_df["ads_activity"] = ads_df["ads_activity"].replace(ads_activity_map)
# 6. Нормализация surf_depth (унификация категорий глубины просмотра)
surf_depth_map = {
"поверхностно": "shallow",
"средне": "medium",
"глубоко": "deep",
}
surf_df["surf_depth"] = surf_df["surf_depth"].replace(surf_depth_map)
# 7. Нормализация primary_device (унификация категорий устройства)
primary_device_map = {
"смартфон": "smartphone",
"ПК": "pc",
"ноутбук": "laptop",
"планшет": "tablet",
}
device_df["primary_device"] = device_df["primary_device"].replace(
primary_device_map
)
# 7. Нормализация cloud_usage (bool -> category)
cloud_usage_map = {
False: "not",
True: "yes",
}
cloud_df["cloud_usage"] = cloud_df["cloud_usage"].replace(cloud_usage_map)
# 8. Объединение всех таблиц по user_id
# Если передан users_df - используем его как базу (там целевая переменная), иначе собираем только признаки
if users_df_empty:
features_df = users_df.merge(visits_features_df, on="user_id", how="left")
features_df = features_df.merge(ads_df, on="user_id", how="left")
else:
features_df = ads_df.merge(visits_features_df, on="user_id", how="left")
features_df = features_df.merge(surf_df, on="user_id", how="left")
features_df = features_df.merge(device_df, on="user_id", how="left")
features_df = features_df.merge(cloud_df, on="user_id", how="left")
return features_df