feature_utils.py 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. # -*- coding:utf-8 -*-
  2. """
  3. @author: yq
  4. @time: 2023/12/28
  5. @desc: 特征工具类
  6. """
  7. import numpy as np
  8. import pandas as pd
  9. from statsmodels.stats.outliers_influence import variance_inflation_factor as vif
  10. FORMAT_DICT = {
  11. # 比例类 -1 - 1
  12. "bin_rate1": np.arange(-1, 1 + 0.1, 0.1),
  13. # 次数类1 0 -10
  14. "bin_cnt1": np.arange(0, 11, 1),
  15. # 次数类2 0 - 20
  16. "bin_cnt2": [0, 1, 2, 3, 4, 5, 8, 10, 15, 20],
  17. # 次数类3 0 - 50
  18. "bin_cnt3": [0, 2, 4, 6, 8, 10, 15, 20, 25, 30, 35, 40, 45, 50],
  19. # 次数类4 0 - 100
  20. "bin_cnt4": [0, 3, 6, 10, 15, 20, 30, 40, 50, 100],
  21. # 金额类1 0 - 1w
  22. "bin_amt1": np.arange(0, 1.1e4, 1e3),
  23. # 金额类2 0 - 5w
  24. "bin_amt2": np.arange(0, 5.5e4, 5e3),
  25. # 金额类3 0 - 10w
  26. "bin_amt3": np.arange(0, 11e4, 1e4),
  27. # 金额类4 0 - 20w
  28. "bin_amt4": [0, 1e4, 2e4, 3e4, 4e4, 5e4, 8e4, 10e4, 15e4, 20e4],
  29. # 金额类5 0 - 100w
  30. "bin_amt5": [0, 5e4, 10e4, 15e4, 20e4, 25e4, 30e4, 40e4, 50e4, 100e4],
  31. # 年龄类
  32. "bin_age": [20, 25, 30, 35, 40, 45, 50, 55, 60, 65],
  33. }
  34. # 粗分箱
  35. def f_format_bin(data_describe: pd.Series, raw_v):
  36. percent10 = data_describe["10%"]
  37. percent90 = data_describe["90%"]
  38. format_v = raw_v
  39. # 筛选最合适的标准化分箱节点
  40. bin = None
  41. for k, v_list in FORMAT_DICT.items():
  42. bin_min = min(v_list)
  43. bin_max = max(v_list)
  44. if percent10 >= bin_min and percent90 <= bin_max:
  45. if bin is None:
  46. bin = (k, bin_max)
  47. elif bin[1] > bin_max:
  48. bin = (k, bin_max)
  49. if bin is None:
  50. return format_v
  51. # 选择分箱内适合的切分点
  52. v_list = FORMAT_DICT[bin[0]]
  53. for idx in range(1, len(v_list)):
  54. v_left = v_list[idx - 1]
  55. v_right = v_list[idx]
  56. # 就近原则
  57. if v_left <= raw_v <= v_right:
  58. format_v = v_right if (raw_v - v_left) - (v_right - raw_v) > 0 else v_left
  59. if format_v not in v_list:
  60. if format_v > v_list[-1]:
  61. format_v = v_list[-1]
  62. if format_v < v_list[0]:
  63. format_v = v_list[0]
  64. return format_v
  65. # 此函数判断list的单调性,允许至多N次符号变化
  66. def f_judge_monto(bd_list: list, pos_neg_cnt: int = 1) -> bool:
  67. if len(bd_list) < 2:
  68. return True
  69. start_tr = bd_list[1] - bd_list[0]
  70. tmp_len = len(bd_list)
  71. pos_neg_flag = 0
  72. for i in range(2, tmp_len):
  73. tmp_tr = bd_list[i] - bd_list[i - 1]
  74. # 后一位bad_rate减前一位bad_rate,保证bad_rate的单调性
  75. # 记录符号变化, 允许 最多一次符号变化,即U型分布
  76. if (tmp_tr >= 0 and start_tr >= 0) or (tmp_tr <= 0 and start_tr <= 0):
  77. # 满足趋势保持,查看下一位
  78. continue
  79. else:
  80. # 记录一次符号变化
  81. start_tr = tmp_tr
  82. pos_neg_flag += 1
  83. if pos_neg_flag > pos_neg_cnt:
  84. return False
  85. # 记录满足趋势要求的变量
  86. if pos_neg_flag <= pos_neg_cnt:
  87. return True
  88. return False
  89. # 变量趋势一致性判断
  90. def f_monto_contrast(train_bd_list: list, test_bd_list: list, monto_contrast_change_cnt: int = 0):
  91. if len(train_bd_list) != len(test_bd_list) or len(train_bd_list) < 2 or len(test_bd_list) < 2:
  92. return False
  93. train_monto = np.array(train_bd_list[1:]) - np.array(train_bd_list[0:-1])
  94. train_monto = np.where(train_monto >= 0, 1, -1)
  95. test_monto = np.array(test_bd_list[1:]) - np.array(test_bd_list[0:-1])
  96. test_monto = np.where(test_monto >= 0, 1, -1)
  97. contrast = train_monto - test_monto
  98. if len(contrast[contrast != 0]) > monto_contrast_change_cnt:
  99. return False
  100. return True
  101. def f_get_corr(data: pd.DataFrame, meth: str = 'spearman') -> pd.DataFrame:
  102. return data.corr(method=meth)
  103. def f_get_ivf(data: pd.DataFrame) -> pd.DataFrame:
  104. if len(data.columns.to_list()) <= 1:
  105. return None
  106. vif_v = [vif(data.values, data.columns.get_loc(i)) for i in data.columns]
  107. vif_df = pd.DataFrame()
  108. vif_df["变量"] = data.columns
  109. vif_df['vif'] = vif_v
  110. return vif_df