models.py 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. # encoding: utf-8
  2. from __future__ import annotations
  3. import numpy as np
  4. import statsmodels.api as sm
  5. from pandas import DataFrame
  6. from applications.config import CATEGORY_FEATURES, CATEGORY_MAP
  7. class CategoryRegression:
  8. """品类回归模型"""
  9. def __init__(self, features=None, category_map=None):
  10. self.features = features or CATEGORY_FEATURES
  11. self.category_map = category_map or CATEGORY_MAP
  12. @staticmethod
  13. def clip_func(x):
  14. """
  15. 阅读率均值倍数调整
  16. """
  17. return x if x < 1.4 else 0.7 * np.log(x) + 1.165
  18. def preprocess_data(self, raw_dataframe: DataFrame) -> DataFrame:
  19. """预处理数据"""
  20. for category in self.category_map:
  21. colname = self.category_map[category]
  22. raw_dataframe[colname] = raw_dataframe["category"] == category
  23. raw_dataframe[colname] = raw_dataframe[colname].astype(int)
  24. # 次条阅读量校正
  25. df_idx1 = raw_dataframe[raw_dataframe["index"] == 1][
  26. ["dt", "gh_id", "read_avg_rate"]
  27. ]
  28. merged_dataframe = raw_dataframe.merge(
  29. df_idx1, how="left", on=["dt", "gh_id"], suffixes=("", "1")
  30. )
  31. debias_selection = merged_dataframe.query(
  32. "index != 1 and read_avg_rate1 < 0.7 and read_avg_rate < 0.7"
  33. )
  34. output_dataframe = merged_dataframe.drop(debias_selection.index)
  35. output_dataframe["read_avg_rate"] = output_dataframe["read_avg_rate"].apply(
  36. self.clip_func
  37. )
  38. output_dataframe["view_count_rate"] = output_dataframe["view_count_rate"].apply(
  39. self.clip_func
  40. )
  41. output_dataframe["days_decrease"] = output_dataframe["first_pub_interval"] * (
  42. -0.2 / 120
  43. )
  44. output_dataframe["RegressionY"] = output_dataframe["read_avg_rate"]
  45. return output_dataframe
  46. def _build_and_print_by_account(
  47. self, raw_dataframe: DataFrame, account_name: str | None
  48. ) -> None:
  49. if account_name:
  50. sub_df = raw_dataframe[raw_dataframe["account_name"] == account_name]
  51. else:
  52. sub_df = raw_dataframe
  53. if len(sub_df) < 5:
  54. return
  55. sample_count = len(sub_df)
  56. params, t_stats, p_values = self.run_ols_linear_regression(sub_df)
  57. row = f"{account_name}\t{sample_count}"
  58. for param, p_value in zip(params, p_values):
  59. row += f"\t{param:.3f}\t{p_value:.3f}"
  60. print(row)
  61. def build_and_print_matrix(self, raw_dataframe: DataFrame) -> None:
  62. p_value_column_names = "\t".join(
  63. [name + "\tp-" + name for name in ["bias"] + self.features]
  64. )
  65. print("account\tsamples\t{}".format(p_value_column_names))
  66. # self._build_and_print_by_account(raw_dataframe, None)
  67. for account_name in raw_dataframe["account_name"].unique():
  68. self._build_and_print_by_account(raw_dataframe, account_name)
  69. def get_param_names(self):
  70. return ["bias"] + self.features
  71. def run_ols_linear_regression(
  72. self,
  73. raw_dataframe: DataFrame.series,
  74. print_residual: bool = False,
  75. print_p_value_threshold: float = 0.1,
  76. ):
  77. X = raw_dataframe[self.features] # 特征列
  78. y = raw_dataframe["RegressionY"] # 目标变量
  79. X = sm.add_constant(X, has_constant="add")
  80. model = sm.OLS(y, X).fit()
  81. params = model.params
  82. t_stats = model.tvalues
  83. p_values = model.pvalues
  84. conf_int = model.conf_int()
  85. if print_residual:
  86. predict_y = model.predict(X)
  87. residuals = y - predict_y
  88. new_x = raw_dataframe[["title", "category"]].copy()
  89. new_x["residual"] = residuals
  90. new_x["y"] = y
  91. select_idx = []
  92. for index, row in new_x.iterrows():
  93. param_name = self.category_map.get(row["category"], None)
  94. if not param_name:
  95. continue
  96. param_index = self.features.index(param_name) + 1
  97. param = params.iloc[param_index]
  98. p_value = p_values.iloc[param_index]
  99. if p_value < print_p_value_threshold:
  100. print(
  101. f"{row['y']:.3f}\t{row['residual']:.3f}\t{row['category']}\t{param:.2f}\t{row['title'][0:30]}"
  102. )
  103. select_idx.append(index)
  104. has_category_residuals = residuals.loc[select_idx]
  105. r_min = has_category_residuals.min()
  106. r_max = has_category_residuals.max()
  107. r_avg = has_category_residuals.mean()
  108. print(f"residuals min: {r_min:.3f}, max: {r_max:.3f}, mean: {r_avg:.3f}")
  109. return params, t_stats, p_values