def execute(aggregation_type, cell_coos, table): """Executes predicted structure against a table to produce the denotation.""" values = _collect_cells_from_table(cell_coos, table) values_parsed = [_parse_value(value) for value in values] values_parsed = tuple(values_parsed) if aggregation_type == _Answer.NONE: # In this case there is no aggregation return values_parsed, values else: # Should perform aggregation. if not values and (aggregation_type == _Answer.AVERAGE or aggregation_type == _Answer.SUM): # Summing or averaging an empty set results in an empty set. # NB: SQL returns null for sum over an empty set. return tuple(), values if aggregation_type == _Answer.COUNT: denotation = len(values) else: # In this case all values must be numbers (to be summed or averaged). try: values_num = [ text_utils.convert_to_float(value) for value in values ] except ValueError: return values_parsed, values if aggregation_type == _Answer.SUM: denotation = sum(values_num) elif aggregation_type == _Answer.AVERAGE: denotation = sum(values_num) / len(values_num) else: raise ValueError('Unknwon aggregation type: %s' % aggregation_type) return tuple([float(denotation)]), values
def _get_float_answer(table, answer_coordinates, aggregation_op): """Applies operation to produce reference float answer.""" if not answer_coordinates: if aggregation_op == _Aggregation.COUNT: return 0.0 else: return _NAN # Count can support non numeric answers. if aggregation_op == _Aggregation.COUNT: return float(len(answer_coordinates)) # If we have just one answer, if float returns it or try a conversion. values = [table['rows'][i][j] for (i, j) in answer_coordinates] if len(answer_coordinates) == 1: try: return text_utils.convert_to_float(values[0]) except ValueError as e: if aggregation_op != _Aggregation.NONE: raise e if aggregation_op == _Aggregation.NONE: return None # Other aggregation only support numeric values. Bail out if we have strings. if not all((isinstance(v, (int, float)) for v in values)): return None if aggregation_op == _Aggregation.SUM: return float(sum(values)) elif aggregation_op == _Aggregation.AVERAGE: return sum(values) / len(answer_coordinates) else: raise ValueError(f'Unknown aggregation: {aggregation_op}')
def _has_single_float_answer_equal_to(question, target): """Returns true if the question has a single answer whose value equals to target.""" if len(question.answer.answer_texts) != 1: return False try: float_value = text_utils.convert_to_float( question.answer.answer_texts[0]) # In general answer_float is derived by applying the same conver_to_float # function at interaction creation time, hence here we use exact match to # avoid any false positive. return text_utils.to_float32(float_value) == text_utils.to_float32( target) except ValueError: return False
def _safe_convert_to_float(value): float_value = text_utils.convert_to_float(value) if math.isnan(float_value): raise ValueError('Value is NaN %s' % value) return float_value
def _to_floats(values): return [text_utils.convert_to_float(v) for v in values]
def test_float_conversion_fails(self): with self.assertRaises(ValueError): text_utils.convert_to_float("hello")
def test_float_conversion(self, value, expected): self.assertEqual(expected, text_utils.convert_to_float(value))
def _parse_answer_float(answer): if len(answer.answer_texts) > 1: raise ValueError("Cannot convert to multiple answers to single float") float_value = text_utils.convert_to_float(answer.answer_texts[0]) answer.float_value = float_value