Spaces:
Sleeping
Sleeping
File size: 12,764 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 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 | from modules.datafact_generator.util import DataFact, DataFactGenerator
from modules.datafact_generator.value_fact import ValueFact
from typing import Any
class ProportionFact(DataFact):
""" 单个 proportion fact """
def __init__(self):
super().__init__()
self.type = "proportion"
self.types = [
"value_majority",
"value_minority",
"total_majority",
"total_minority"
]
class ProportionFactGenerator(DataFactGenerator):
""" 处理从数据提取 proportion facts 的问题 """
def __init__(self, data: dict, value_facts: list[ValueFact]):
super().__init__(data)
# 使用计算好的 value facts 进行组合
self.value_facts = value_facts
self.total_facts: list[ValueFact] = []
for fact in self.value_facts:
if fact.subtype == "total":
self.total_facts.append(fact)
def extract_proportion_facts(self) -> list[ProportionFact]:
proportion_facts: list[ProportionFact] = []
# 如果数据的 y 中包含负数,那么 majoroty / minority 就失去意义了
for single_data in self.tabular_data:
if single_data[self.y_column] < 0:
return []
# 如果就一个 group 也不需要算了
if self.group_column is None:
return []
# 对 value based 的操作
# 随便找一个 x_list, 用来遍历
any_group = next(iter(self.grouped_data.values()))
x_list = any_group["x_list"]
num_items = len(x_list)
try:
for i in range(num_items):
# 对于某一个 x 值,把对应的 index, group_value, y 聚合起来
info = {}
for group_value, group_info in self.grouped_data.items():
y_list = group_info["y_list"]
indices = group_info["indices"]
info[group_value] = { # 构建一个 y 值和 index 的 dict 作传参
"y": y_list[i],
"index": indices[i]
}
x_value = x_list[i]
value_max_fact, value_min_fact = self._extract_value_based_facts(x_value, info)
proportion_facts.append(value_max_fact)
proportion_facts.append(value_min_fact)
except Exception as e:
print(e)
# 直接依赖 value facts
total_majority_fact, total_minority_fact = self._extract_total_based_facts(self.total_facts)
proportion_facts.append(total_majority_fact)
proportion_facts.append(total_minority_fact)
return proportion_facts
def _extract_value_based_facts(self, x_value: str, info: dict[str, dict[str, Any]]):
""" 处理 value majority fact 和 value minority fact """
max_proportion_fact, min_proportion_fact = ProportionFact(), ProportionFact()
max_subtype, min_subtype = "value_majority", "value_minority"
# 先把 y 值和 index 取出来,用 y 作索引取最大、最小值
y_and_index = [(group_value, value["y"], value["index"]) for group_value, value in info.items()]
y_values = [y for _, y, _ in y_and_index]
sum_y = sum(y_values) or 1e-8
max_y = max(y_values)
min_y = min(y_values)
max_proportion, min_proportion = max_y / sum_y, min_y / sum_y
# 多个最大值/最小值情况
max_group_values, max_indices = [], []
min_group_values, min_indices = [], []
for group_value, y, index in y_and_index:
if y == max_y:
max_group_values.append(group_value)
max_indices.append(index)
if y == min_y:
min_group_values.append(group_value)
min_indices.append(index)
max_data_points = [self.tabular_data[index] for index in max_indices]
min_data_points = [self.tabular_data[index] for index in min_indices]
def generate_score():
# 对于majority, 类别越多,分值肯定应该相对较高
if 1 - max_proportion <= 1e-8:
return 0.0, 0.0
k_max = 1.0
max_score = 1 / (1 + k_max * max_proportion / len(info))
if min_proportion <= 1e-8:
return max_score, 0.0
k_min = 5.0
min_score = 1 / (1 + k_min * min_proportion)
# 最小值用比例评分
if min_proportion <= 1e-8:
return max_score, 0.0 # 感觉一般来说有问题
k_min = 5.0
min_score = 1 / (1 + k_min * min_proportion)
return max_score, min_score
def generate_annotation_and_reason():
max_annotation, max_reason = "", ""
min_annotation, min_reason = "", ""
max_group_value_str = ", ".join(max_group_values)
min_group_value_str = ", ".join(min_group_values)
if len(max_group_values) == 1:
if max_proportion > 0.5:
max_annotation = f"The {max_group_value_str} accounts for the majority in {x_value}."
max_reason = (
f"The {self.y_column} of {max_group_value_str} in {x_value} accounts for {max_proportion} (more than 50%) "
f"of the total of {self.y_column} of all {self.group_column}."
)
else:
max_annotation = f"The {max_group_value_str} has the largest proportion in {x_value}."
max_reason = (
f"The {self.y_column} of {max_group_value_str} in {x_value} accounts for {max_proportion}, larger than all "
f"other {self.group_column}, of the total of {self.y_column} of all {self.group_column}."
)
else:
max_annotation = f"The {max_group_value_str} all have the largest proportion in {x_value}."
max_reason = (
f"The {self.y_column} of {max_group_value_str} in {x_value} all account for {max_proportion}, larger than all "
f"other {self.group_column}, of the total of {self.y_column} of all {self.group_column}."
)
# 正常来说除非就两类,不然占"少数"应该是正常情况
if len(min_group_values) == 1:
min_annotation = f"The {min_group_value_str} has the smallest proportion in {x_value}."
min_reason = (
f"The {self.y_column} of {min_group_value_str} in {x_value} accounts for {min_proportion}, smaller than all "
f"other {self.group_column}, of the total of {self.y_column} of all {self.group_column}."
)
else:
min_annotation = f"The {min_group_value_str} all have the smallest proportion in {x_value}."
min_reason = (
f"The {self.y_column} of {min_group_value_str} in {x_value} all account for {min_proportion}, smaller than all "
f"other {self.group_column}, of the total of {self.y_column} of all {self.group_column}."
)
return max_annotation, max_reason, min_annotation, min_reason
max_score, min_score = generate_score()
max_annotation, max_reason, min_annotation, min_reason = generate_annotation_and_reason()
max_proportion_fact.set_value(
max_subtype, max_data_points, max_score, max_annotation, max_reason
)
min_proportion_fact.set_value(
min_subtype, min_data_points, min_score, min_annotation, min_reason
)
return max_proportion_fact, min_proportion_fact
def _extract_total_based_facts(self, total_facts: list[ValueFact]):
""" 处理 total majority fact 和 total minority fact """
max_proportion_fact, min_proportion_fact = ProportionFact(), ProportionFact()
max_subtype, min_subtype = "value_majority", "value_minority"
max_total = max(total_facts, key=lambda x: x.data_points[0][self.y_column]).data_points[0][self.y_column]
min_total = min(total_facts, key=lambda x: x.data_points[0][self.y_column]).data_points[0][self.y_column]
sum_total = sum([total_fact.data_points[0][self.y_column] for total_fact in total_facts]) or 1e-8
max_proportion = max_total / sum_total
min_proportion = min_total / sum_total
max_total_facts: list[ValueFact] = []
min_total_facts: list[ValueFact] = []
for total_fact in total_facts:
if total_fact.data_points[0][self.y_column] == max_total:
max_total_facts.append(total_fact)
if total_fact.data_points[0][self.y_column] == min_total:
min_total_facts.append(total_fact)
# total fact 的 data pooints 必定只有一项
max_data_points = [total_fact.data_points[0] for total_fact in max_total_facts]
min_data_points = [total_fact.data_points[0] for total_fact in max_total_facts]
def generate_score():
if 1 - max_proportion <= 1e-8:
return 0.0, 0.0
k_max = 1.0
max_score = 1 / (1 + k_max * max_proportion / len(total_facts))
if min_proportion <= 1e-8:
return max_score, 0.0
k_min = 5.0
min_score = 1 / (1 + k_min * min_proportion)
return max_score, min_score
def generate_annotation_and_reason():
max_annotation, max_reason = "", ""
min_annotation, min_reason = "", ""
max_group_value_str = ", ".join([max_data_point[self.group_column] for max_data_point in max_data_points])
min_group_value_str = ", ".join([min_data_point[self.group_column] for min_data_point in min_data_points])
if len(max_data_points) == 1:
if max_proportion > 0.5:
max_annotation = f"The total value of {max_group_value_str} accounts for the majority in all {self.group_column}."
max_reason = (
f"The total value of {self.y_column} of {max_group_value_str} accounts for {max_proportion} (more than 50%) "
f"of the total of {self.y_column} of all {self.group_column}."
)
else:
max_annotation = f"The total value of {max_group_value_str} has the largest proportion."
max_reason = (
f"The total value of {self.y_column} of {max_group_value_str} accounts for {max_proportion}, larger than all "
f"other {self.group_column}, of the total of {self.y_column} of all {self.group_column}."
)
else:
max_annotation = f"The total value of {max_group_value_str} all have the largest proportion."
max_reason = (
f"The total value of {self.y_column} of {max_group_value_str} all account for {max_proportion}, larger than all "
f"other {self.group_column}, of the total of {self.y_column} of all {self.group_column}."
)
if len(min_data_points) == 1:
min_annotation = f"The total value of {min_group_value_str} has the smallest proportion."
min_reason = (
f"The total value of {self.y_column} of {min_group_value_str} accounts for {min_proportion}, smaller than all "
f"other {self.group_column}, of the total of {self.y_column} of all {self.group_column}."
)
else:
min_annotation = f"The total value of {min_group_value_str} all have the smallest proportion."
min_reason = (
f"The total value of {self.y_column} of {min_group_value_str} all account for {min_proportion}, smaller than all "
f"other {self.group_column}, of the total of {self.y_column} of all {self.group_column}."
)
return max_annotation, max_reason, min_annotation, min_reason
max_score, min_score = generate_score()
max_annotation, max_reason, min_annotation, min_reason = generate_annotation_and_reason()
max_proportion_fact.set_value(
max_subtype, max_data_points, max_score, max_annotation, max_reason
)
min_proportion_fact.set_value(
min_subtype, min_data_points, min_score, min_annotation, min_reason
)
return max_proportion_fact, min_proportion_fact |