def test_conflicting_roles(self): with raises(ValueError): CustomUnitPolicy([ ScaleUnitConversion(Unit('m'), Unit('km'), 0.001), ScaleUnitConversion(Unit('km'), Unit('mm'), 1000000) ], duplicates='overwrite')
def test_duplicate_raise(self): with raises(ValueError): CustomUnitPolicy([ ScaleUnitConversion(Unit('km'), Unit('m'), 1000), ScaleUnitConversion(Unit('km'), Unit('mm'), 1000000) ], duplicates='raise')
def test_alias(self, custom_unit_conversion): alias = custom_unit_conversion.alias(Unit('jiggyjag')) assert alias == CustomUnitConversion(Unit('jiggyjag'), Unit('bork'), lambda x: x**2, lambda x: math.sqrt(x)) assert alias.to_ref(12) == approx(144) assert alias.from_ref(9) == approx(3)
def test_convert_to_ref(self): cup = CustomUnitPolicy([ ScaleUnitConversion(Unit('km'), Unit('m'), 1000), ScaleUnitConversion(Unit('mm'), Unit('m'), 0.001), AffineUnitConversion(Unit('C'), Unit('K'), 1, 273.15) ]) assert cup.convert_to_ref(42, Unit('km')) == 42000 # source to ref assert cup.convert_to_ref(42, Unit('m')) == 42 # ref to ref with raises(ValueError): cup.convert_to_ref(42, Unit('bogus')) # unit doesn't exist
def test_eq(self): assert IdentityUnitConversion(Unit('metre'), Unit('m')) == IdentityUnitConversion( Unit('metre'), Unit('m')) assert IdentityUnitConversion(Unit('metre'), Unit('m')) != IdentityUnitConversion( Unit('mtr'), Unit('m'))
def test_eq(self): assert ScaleUnitConversion(Unit('mm'), Unit('m'), 0.001) == ScaleUnitConversion( Unit('mm'), Unit('m'), 0.001) assert ScaleUnitConversion(Unit('bs'), Unit('m'), 0.123) != ScaleUnitConversion( Unit('mm'), Unit('m'), 0.001)
def test_evaluate_expression_syntax_error(self): df = pd.DataFrame(data=[[nan, 'gnu', 3], [4, 'gnat', '{{((((*+-/ x y heres_a_syntax_error!!!!!!!!!!!!!!!!}}']], columns=['a', 'b', 'c']) col_specs = {n: ColumnMetadata(Unit(u)) for n, u in zip(['a', 'b', 'c'], ['-', 'text', 'm'])} t = Table(df=df, name='some_table', col_specs=col_specs, destinations=['success', 'glory']) with raises(SyntaxError, match=r"Syntax error in expression in table 'some_table', column 2, row 1"): t.evaluate_expressions({'x': 7, 'y': 9}, inplace=False)
def __init__(self, df: pd.DataFrame, name: str, col_specs: Dict[str, ColumnMetadata] = None, destinations: Optional[Iterable[str]] = None, origin: Optional[TableOrigin] = None, remark: Optional[str] = None): """ :param df: DataFrame of Table contents. :param name: Name of this Table. :param col_specs: If supplied, specifies column metadata. If not, these are created with default values. :param destinations: Iterable of destination strings. :param origin: :param remark: Free-text remark about this table block. """ if col_specs: self._validate_col_specs(col_specs, df.columns) df: pd.DataFrame = df.copy() self._col_specs = col_specs else: self._col_specs = { col_name: ColumnMetadata(Unit(DEFAULT_UNIT_STR)) for col_name in df.columns } self.name = name if not destinations: destinations = [DEFAULT_DESTINATION] self._destinations = self._sanitize_destinations(destinations) self.origin = origin self._df = df self.remark = remark
def test_iter(self): cup = CustomUnitPolicy([ ScaleUnitConversion(Unit('km'), Unit('m'), 1000), ScaleUnitConversion(Unit('mm'), Unit('m'), 0.001), AffineUnitConversion(Unit('C'), Unit('K'), 1, 273.15) ]) assert Unit('mm') in cup assert Unit('m') in cup assert set(cup) == {'km', 'mm', 'm', 'C', 'K'}
def test_ref_unit(self): cup = CustomUnitPolicy( [ScaleUnitConversion(Unit('km'), Unit('m'), 1000)]) assert cup.ref_unit(Unit('km')) == Unit('m') assert cup.ref_unit(Unit('m')) == Unit('m') with raises(ValueError): cup.ref_unit(Unit('furlong')) # unit doesn't exist
def test_convert_to_home_units(self): df = pd.DataFrame([ [11, 12, 13], [21, 22, 23]], columns=['a', 'b', 'c']) cs = {n: ColumnMetadata(Unit(u), Unit(hu)) for n, u, hu in zip( ['a', 'b', 'c'], ['m', 'm', 'm'], ['m', 'mm', 'km'])} t = Table(df, name='Fool', col_specs=cs) cup = CustomUnitPolicy([ ScaleUnitConversion(Unit('mm'), Unit('m'), 0.001), ScaleUnitConversion(Unit('km'), Unit('m'), 1000)]) t_home = t.convert_to_home_units(cup) assert t_home.col_units == [Unit('m'), Unit('mm'), Unit('km')] assert (np.array(t_home.df) == np.array([[11, 12000, 0.013], [21, 22000, 0.023]])).all()
def test_duplicate_overwrite(self): cup = CustomUnitPolicy([ ScaleUnitConversion(Unit('km'), Unit('m'), 1000), ScaleUnitConversion(Unit('km'), Unit('mm'), 1000000) ], duplicates='overwrite') assert cup.ref_unit(Unit('km')) == Unit('mm')
def convert_units(self, unit_policy: UnitPolicy, new_units: Dict[str, Unit] = None, inplace: bool = False, new_unit_missing: str = 'raise') -> Optional['Table']: """ Changes values and units in accordance with unit policy. :param unit_policy: Unit policy that will govern unit conversion. :param new_units: dict of column name --> new unit. If none, unit_policy decides. :param inplace: If True, convert values and unit fields in-place. If False, return a new converted Table. :param new_unit_missing: {'ignore', 'raise'}, default 'raise'. Defines behaviour if a column's new unit is not supported by the unit policy. If 'raise', raise an exception. If 'ignore', leave the unit in place, no conversion done. # TODO what about if old unit is not in policy... """ new_unit_missing_options = {'ignore', 'raise'} if new_unit_missing not in new_unit_missing_options: raise ValueError( f"Expected one of {new_unit_missing_options}, got '{new_unit_missing}'." ) table = self if inplace else self.copy() for col in table.df: old_unit = self._col_specs[col].unit if old_unit != Unit(TEXT_COL_UNIT_STR): try: new_unit = new_units[col] except KeyError: if new_unit_missing == 'raise': raise ValueError( f"New unit missing for column '{col}'.") continue # Move on to next column. try: # Convert values in this col table.df[col] = table.df[col].apply(unit_policy.convert, from_unit=old_unit, to_unit=new_unit) # Change this col's unit to ref_unit table._col_specs[col].unit = new_unit except ValueError as ve: raise ValueError( f"Can't convert unit of column '{col}' from '{old_unit}' to '{new_unit}'." ) from ve if not inplace: return table
def test_convert_to_ref_units(self): df = pd.DataFrame([ [11, 12, 13], [21, 22, 23]], columns=['a', 'b', 'c']) cs = {n: ColumnMetadata(Unit(u)) for n, u in zip(['a', 'b', 'c'], ['m', 'mm', 'km'])} t = Table(df, name='Fool', col_specs=cs) cup = CustomUnitPolicy([ ScaleUnitConversion(Unit('mm'), Unit('m'), 0.001), ScaleUnitConversion(Unit('km'), Unit('m'), 1000)]) t_ref = t.convert_to_ref_units(cup, inplace=False) assert t_ref.col_units == [Unit('m')] * 3 assert (np.array(t_ref.df) == np.array([[11, 0.012, 13000], [21, 0.022, 23000]])).all()
def test_to_csv_nonstring_colnames_and_destinations(self): # PS-53 Bundle.to_csv() fails when column names are not strings nonstring_colnames = [1.234, 666.0, 42.0] nonstring_destinations = [1984, 2001.2001] df = pd.DataFrame(data=[[nan, 'gnu', 3], [4, 'gnat', '{{(+ x y)}}']], columns=nonstring_colnames) col_specs = {n: ColumnMetadata(Unit(u)) for n, u in zip(nonstring_colnames, ['-', 'text', 'm'])} t = Table(df=df, name='some_table', col_specs=col_specs, destinations=nonstring_destinations) out = io.StringIO() t.to_csv(out) assert out.getvalue() == dedent("""\ **some_table;; 1984 2001.2001 1.234;666.0;42.0 -;text;m -;gnu;3 4.0;gnat;{{(+ x y)}} """)
def test_convert_to_ref_units_unknown_unit(self): df = pd.DataFrame([ [11, 12, 13], [21, 22, 23]], columns=['a', 'b', 'c']) cs = {n: ColumnMetadata(Unit(u)) for n, u in zip(['a', 'b', 'c'], ['m', 'mm', 'km'])} t = Table(df, name='Fool', col_specs=cs) cup_no_km = CustomUnitPolicy([ScaleUnitConversion(Unit('mm'), Unit('m'), 0.001)]) t_ref_no_km = t.convert_to_ref_units(cup_no_km, inplace=False, units_not_in_policy='ignore') assert t_ref_no_km.col_units == [Unit('m'), Unit('m'), Unit('km')] assert (np.array(t_ref_no_km.df) == np.array([[11, 0.012, 13], [21, 0.022, 23]])).all() with raises(ValueError): t.convert_to_ref_units(cup_no_km, inplace=False, units_not_in_policy='raise')
def convert_to_ref_units( self, unit_policy: UnitPolicy, inplace: bool = False, units_not_in_policy: str = 'raise') -> Optional['Table']: """ Converts values and units to unit policy's reference units. :param unit_policy: :param inplace: :param units_not_in_policy: {'ignore', 'raise'}, default 'raise'. Defines behaviour if a column's unit is not supported by the unit policy. If 'raise', raise an exception. If 'ignore', leave the unit in place, no conversion done. """ # TODO unit conversion will fail on expressions units_not_in_policy_options = {'ignore', 'raise'} if units_not_in_policy not in units_not_in_policy_options: raise ValueError( f"Expected one of {units_not_in_policy_options}, got '{units_not_in_policy}'." ) table = self if inplace else self.copy() for col in table.df: old_unit = self._col_specs[col].unit if not old_unit == Unit(TEXT_COL_UNIT_STR): try: # Convert values in this col table.df[col] = table.df[col].apply( unit_policy.convert_to_ref, src_unit=old_unit) # Change this col's unit to ref_unit table._col_specs[col].unit = unit_policy.ref_unit(old_unit) except ValueError: if units_not_in_policy == 'raise': raise ValueError( f"Unit '{old_unit}' of column '{col}' not found in unit policy." ) pass # Ignore the unknown unit, do no conversion if not inplace: return table
def test_reverse(self, custom_unit_conversion): assert custom_unit_conversion.reverse() == \ CustomUnitConversion(Unit('bork'), Unit('zonk'), lambda x: math.sqrt(x), lambda x: x ** 2)
def test_alias(self): alias = ScaleUnitConversion(Unit('mm'), Unit('m'), 0.001).alias(Unit('millimetre')) assert alias == ScaleUnitConversion(Unit('millimetre'), Unit('m'), 0.001) assert alias.to_ref(1000) == 1
def test_eq(self, custom_unit_conversion): assert custom_unit_conversion == \ CustomUnitConversion(Unit('zonk'), Unit('bork'), lambda x: x ** 2, lambda x: math.sqrt(x)) assert custom_unit_conversion != \ CustomUnitConversion(Unit('zonk'), Unit('bork'), lambda x: x ** 2, lambda x: x)
def custom_unit_conversion(self) -> CustomUnitConversion: return CustomUnitConversion(Unit('zonk'), Unit('bork'), lambda x: x**2, lambda x: math.sqrt(x))
def test_reverse(self): assert AffineUnitConversion( Unit('degC'), Unit('K'), 1, 273.15).reverse().to_ref(293.15) == approx(20)
def test_from_ref(self): assert ScaleUnitConversion(Unit('mm'), Unit('m'), 0.001).from_ref(42) == 42000
def col_specs(self): return {n: ColumnMetadata(Unit(u)) for n, u in zip(['a', 'b', 'c'], ['-', 'text', 'm'])}
def test_eq(self): assert AffineUnitConversion(Unit('degC'), Unit('K'), 1, 273.15) == \ AffineUnitConversion(Unit('degC'), Unit('K'), 1, 273.15) assert AffineUnitConversion(Unit('degC'), Unit('K'), 1, 273.15) != \ AffineUnitConversion(Unit('degC'), Unit('K'), 1, 666)
def test_to_ref(self): assert ScaleUnitConversion(Unit('mm'), Unit('m'), 0.001).to_ref(42) == 0.042
def test_to_ref(self): assert AffineUnitConversion(Unit('degC'), Unit('K'), 1, 273.15).to_ref(-273.15) == 0
def test_reverse(self): assert ScaleUnitConversion(Unit('mm'), Unit('m'), 0.001).reverse().to_ref(42) == approx(42000)
def col_specs_with_format(self): return {'a': ColumnMetadata(Unit('-'), format_str='${:,.2f}'), 'b': ColumnMetadata(Unit('text')), 'c': ColumnMetadata(Unit('m'), format_str='.4e')}
def test_from_ref(self): assert AffineUnitConversion(Unit('degC'), Unit('K'), 1, 273.15).from_ref(0) == -273.15