| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167 |
- #!/usr/bin/env python3
- from pathlib import Path
- import sys
- import pandas as pd
- from odps_module import ODPSClient
- BASE = Path(__file__).resolve().parent
- CONFIG = {
- "realtime": (
- BASE / "first_layer_source_metrics_realtime_20260721_apptype4_version1578.sql",
- BASE / "first_layer_source_metrics_realtime_20260721_apptype4_version1578.csv",
- ),
- "offline": (
- BASE / "first_layer_source_metrics_offline_20260716_20260720_apptype4_all_versions.sql",
- BASE / "first_layer_source_metrics_offline_20260716_20260720_apptype4_all_versions.csv",
- ),
- }
- SOURCES = ["头部", "推荐", "全部"]
- SOURCE_KEYS = {"头部": "head", "推荐": "recommend", "全部": "all"}
- FACT_SUFFIXES = ["曝光PV", "曝光UV", "播放PV", "播放UV", "分享PV", "分享UV", "裂变层UV"]
- FACT_COLUMNS = ["首层UV", *[f"{source}{suffix}" for source in SOURCES for suffix in FACT_SUFFIXES]]
- RATE_SUFFIXES = [
- "曝光PV/首层UV", "播放PV/首层UV", "分享PV/首层UV",
- "STR(分享PV/曝光PV)", "T0裂变率(裂变层UV/首层UV)", "裂变层UV/曝光PV",
- ]
- def column_mapping():
- result = {
- "stat_date": "日期", "app_type": "产品类型", "version_code": "版本号",
- "bucket": "尾号", "first_layer_uv": "首层UV",
- }
- fields = {
- "exposure_pv": "曝光PV", "exposure_uv": "曝光UV",
- "play_pv": "播放PV", "play_uv": "播放UV",
- "share_pv": "分享PV", "share_uv": "分享UV", "fission_uv": "裂变层UV",
- }
- for source, key in SOURCE_KEYS.items():
- for field, label in fields.items():
- result[f"{key}_{field}"] = f"{source}{label}"
- return result
- def group_name(bucket):
- return "实验组(0、1)" if bucket in {"0", "1"} else "对照组(其余14桶)"
- def format_percent(value):
- return "" if pd.isna(value) else f"{value * 100:.4f}%"
- def add_rates(data):
- result = data.copy()
- first_layer_uv = result["首层UV"].replace(0, pd.NA)
- for source in SOURCES:
- exposure_pv = result[f"{source}曝光PV"].replace(0, pd.NA)
- result[f"{source}曝光PV/首层UV"] = result[f"{source}曝光PV"] / first_layer_uv
- result[f"{source}播放PV/首层UV"] = result[f"{source}播放PV"] / first_layer_uv
- result[f"{source}分享PV/首层UV"] = result[f"{source}分享PV"] / first_layer_uv
- result[f"{source}STR(分享PV/曝光PV)"] = result[f"{source}分享PV"] / exposure_pv
- result[f"{source}T0裂变率(裂变层UV/首层UV)"] = result[f"{source}裂变层UV"] / first_layer_uv
- result[f"{source}裂变层UV/曝光PV"] = result[f"{source}裂变层UV"] / exposure_pv
- return result
- def add_relative_changes(data):
- result = data.copy()
- result["首层UV相对对照组变化率"] = ""
- exp = result.index[(result["行类型"] == "分组聚合") & (result["分组"] == "实验组(0、1)")]
- ctrl = result.index[(result["行类型"] == "分组聚合") & (result["分组"] == "对照组(其余14桶)")]
- if len(exp) == 1 and len(ctrl) == 1:
- exp_mean = result.loc[exp[0], "首层UV"] / 2
- ctrl_mean = result.loc[ctrl[0], "首层UV"] / 14
- if ctrl_mean:
- change = format_percent(exp_mean / ctrl_mean - 1)
- mask = (result["分组"] == "实验组(0、1)") & result["行类型"].isin(["分组聚合", "每桶均值"])
- result.loc[mask, "首层UV相对对照组变化率"] = change
- for source in SOURCES:
- for suffix in RATE_SUFFIXES:
- metric = f"{source}{suffix}"
- change_column = f"{metric}相对对照组变化率"
- result[change_column] = ""
- for row_type in ["分组聚合", "每桶均值"]:
- exp_idx = result.index[(result["行类型"] == row_type) & (result["分组"] == "实验组(0、1)")]
- ctrl_idx = result.index[(result["行类型"] == row_type) & (result["分组"] == "对照组(其余14桶)")]
- if len(exp_idx) == 1 and len(ctrl_idx) == 1:
- control = result.loc[ctrl_idx[0], metric]
- if pd.notna(control) and control != 0:
- result.loc[exp_idx[0], change_column] = format_percent(result.loc[exp_idx[0], metric] / control - 1)
- return result
- def format_output(data):
- result = data.copy()
- for source in SOURCES:
- for suffix in ["曝光PV/首层UV", "播放PV/首层UV", "分享PV/首层UV"]:
- result[f"{source}{suffix}"] = result[f"{source}{suffix}"].round(4)
- for suffix in ["STR(分享PV/曝光PV)", "T0裂变率(裂变层UV/首层UV)", "裂变层UV/曝光PV"]:
- result[f"{source}{suffix}"] = result[f"{source}{suffix}"].map(format_percent)
- result[FACT_COLUMNS] = result[FACT_COLUMNS].round(0).astype("Int64")
- ordered = ["日期", "产品类型", "版本号", "行类型", "分组", "尾号", "首层UV", "首层UV相对对照组变化率"]
- for source in SOURCES:
- ordered.extend([
- f"{source}曝光PV", f"{source}曝光UV",
- f"{source}曝光PV/首层UV", f"{source}曝光PV/首层UV相对对照组变化率",
- f"{source}播放PV", f"{source}播放UV",
- f"{source}播放PV/首层UV", f"{source}播放PV/首层UV相对对照组变化率",
- f"{source}分享PV", f"{source}分享UV",
- f"{source}分享PV/首层UV", f"{source}分享PV/首层UV相对对照组变化率",
- f"{source}STR(分享PV/曝光PV)", f"{source}STR(分享PV/曝光PV)相对对照组变化率",
- f"{source}裂变层UV",
- f"{source}T0裂变率(裂变层UV/首层UV)", f"{source}T0裂变率(裂变层UV/首层UV)相对对照组变化率",
- f"{source}裂变层UV/曝光PV", f"{source}裂变层UV/曝光PV相对对照组变化率",
- ])
- return result[ordered]
- def build_daily(data, stat_date):
- daily = data[data["日期"] == stat_date].copy()
- daily["尾号"] = daily["尾号"].astype(str)
- daily["行类型"] = "尾号明细"
- daily["分组"] = daily["尾号"].map(group_name)
- detail = daily[["日期", "产品类型", "版本号", "行类型", "分组", "尾号", *FACT_COLUMNS]]
- aggregates, means = [], []
- for group, buckets in [("实验组(0、1)", {"0", "1"}), ("对照组(其余14桶)", set("23456789abcdef"))]:
- selected = detail[detail["尾号"].isin(buckets)]
- common = {"日期": stat_date, "产品类型": detail["产品类型"].iloc[0], "版本号": detail["版本号"].iloc[0], "分组": group}
- aggregates.append({
- **common, "行类型": "分组聚合", "尾号": "0、1" if len(buckets) == 2 else "其余14桶",
- **selected[FACT_COLUMNS].sum().to_dict(),
- })
- means.append({
- **common, "行类型": "每桶均值", "尾号": "2桶均值" if len(buckets) == 2 else "14桶均值",
- **selected[FACT_COLUMNS].mean().to_dict(),
- })
- result = pd.concat([detail, pd.DataFrame(aggregates), pd.DataFrame(means)], ignore_index=True)
- return format_output(add_relative_changes(add_rates(result)))
- def main():
- mode = sys.argv[1] if len(sys.argv) > 1 else "realtime"
- if mode not in CONFIG:
- raise SystemExit("usage: python3 run_first_layer_source_metrics.py [realtime|offline]")
- sql_file, output_file = CONFIG[mode]
- data = ODPSClient().execute_sql(sql_file.read_text(encoding="utf-8")).rename(columns=column_mapping())
- data["日期"] = data["日期"].astype(str)
- reports = [build_daily(data, stat_date) for stat_date in sorted(data["日期"].unique(), reverse=True)]
- result = pd.concat(reports, ignore_index=True)
- result.to_csv(output_file, index=False, encoding="utf-8-sig")
- print(f"[CSV] {output_file}", flush=True)
- summary = result[result["行类型"] == "分组聚合"][[
- "日期", "版本号", "分组", "首层UV", "首层UV相对对照组变化率",
- "全部曝光PV/首层UV", "全部播放PV/首层UV", "全部分享PV/首层UV",
- "全部T0裂变率(裂变层UV/首层UV)", "全部裂变层UV/曝光PV",
- ]]
- print(summary.to_string(index=False), flush=True)
- if __name__ == "__main__":
- main()
|