Spaces:
Sleeping
Sleeping
File size: 6,133 Bytes
f54e1d4 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 | from modules.datafact_generator.util import DataFact, DataFactGenerator
import numpy as np
from sklearn.linear_model import LinearRegression
from scipy.special import expit
class TrendFact(DataFact):
def __init__(self):
super().__init__()
self.type = "trend"
self.types = [
"increase", "decrease", "stable", "increase_then_decrease", "decrease_then_increase"
]
class TrendFactGenerator(DataFactGenerator):
def __init__(self, data):
super().__init__(data)
def extract_trend_facts(self) -> list[TrendFact]:
trend_facts: list[TrendFact] = []
if not self.is_temporal:
return []
for group_value in self.grouped_data.keys():
indices = self.grouped_data[group_value]["indices"]
y_list = self.grouped_data[group_value]["y_list"]
if len(y_list) <= 1:
continue
trend_fact = self._extract_single_trend(group_value, indices, y_list)
trend_facts.append(trend_fact)
return trend_facts
def _extract_single_trend(self, group_value: str, indices: list[int], y_list: list) -> TrendFact:
""" 处理单个 group """
# 分别计算单调上升,单调下降的分数,然后分别以每个点为转折点,计算先升后降,先降后升分数,取最高
# 分数计算方式使用线性回归后的斜率
trend_fact = TrendFact()
def generate_score(y, slope_threshold=0.05, slope_scale=1.5):
"""
生成某一段的 trend 分数
划分为 decrease, stable, increase 三类
"""
y = np.array(y)
x = np.arange(len(y)).reshape(-1, 1)
model = LinearRegression().fit(x, y)
slope = model.coef_[0]
abs_slope = abs(slope)
y_mean = np.mean(y)
abs_slope = abs_slope / y_mean * (len(y)-1)
if abs_slope < slope_threshold:
subtype = "stable"
score = 1 - expit(slope_scale * (abs_slope / slope_threshold)) # 越靠近 0 越高
else:
subtype = "increase" if slope > 0 else "decrease"
score = expit(slope_scale * (abs_slope - slope_threshold)) # 越远离阈值越高
return score, subtype
# 计算单调上升,单调下降的分数
mono_score, mono_subtype = generate_score(y_list)
# 分别遍历每个点作为临界点,分别计算两部分分数,汇总为先升后降和先降后升的分数
# 我们希望如果两段比较均分,那么分数应该相对较高;如果两段很不均匀,分数应该很低
# 熵很好
max_poly_score = 0
max_poly_subtype = ""
best_split_idx = -1
for idx in range(2, len(y_list)-2):
first_y_list, second_y_list = [y_list[i] for i in range(0, idx)], [y_list[i] for i in range(idx, len(y_list))]
first_score, first_subtype = generate_score(first_y_list)
second_score, second_subtype = generate_score(second_y_list)
# 趋势一样不考虑
if first_subtype == second_subtype:
continue
# 有 stable 不考虑
if first_subtype == "stable" or second_subtype == "stable":
continue
if first_score == "increase":
poly_subtype = "increase_then_decrease"
else:
poly_subtype = "decrease_then_increase"
first_ratio = len(first_y_list) / len(y_list)
second_ratio = len(second_y_list) / len(y_list)
# 熵
poly_score = - first_ratio * np.log2(first_ratio) * first_score - second_ratio * np.log2(second_ratio) * second_score
if poly_score > max_poly_score:
max_poly_score = poly_score
max_poly_subtype = poly_subtype
best_split_idx = idx
score = 0
subtype = ""
if mono_score >= max_poly_score:
score = mono_score
subtype = mono_subtype
else:
score = max_poly_score
subtype = max_poly_subtype
def generate_annotation_and_reason():
annotation, reason = "", ""
if subtype == "increase":
annotation = f"The {self.y_column} of {group_value} shows an increasing trend."
reason = "The overall data exhibits a consistent upward movement."
elif subtype == "decrease":
annotation = f"The {self.y_column} of {group_value} shows a decreasing trend."
reason = "The overall data exhibits a consistent downward movement."
elif subtype == "stable":
annotation = f"The {self.y_column} of {group_value} remains stable over time."
reason = "The slope of the data is close to zero, indicating minimal variation."
elif subtype == "increase_then_decrease" and best_split_idx is not None:
annotation = f"The {self.y_column} of {group_value} increases first and then decreases."
reason = (
f"From index 0 to {best_split_idx}, the data increases; "
f"from index {best_split_idx} to {len(y_list)-1}, it decreases."
)
elif subtype == "decrease_then_increase" and best_split_idx is not None:
annotation = f"The {self.y_column} of {group_value} decreases first and then increases."
reason = (
f"From index 0 to {best_split_idx}, the data decreases; "
f"from index {best_split_idx} to {len(y_list)-1}, it increases."
)
return annotation, reason
annotation, reason = generate_annotation_and_reason()
data_points = [self.tabular_data[indices[-1]]] # 把最后一个元素作为 data point
trend_fact.set_value(
subtype, data_points, score, annotation, reason
)
return trend_fact |