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