diff --git a/distribution/lib/Standard/Table/0.0.0-dev/src/Internal/Type_Refinements/Column_Refinements.enso b/distribution/lib/Standard/Table/0.0.0-dev/src/Internal/Type_Refinements/Column_Refinements.enso index 0a4d04752549..95e4fbac30b2 100644 --- a/distribution/lib/Standard/Table/0.0.0-dev/src/Internal/Type_Refinements/Column_Refinements.enso +++ b/distribution/lib/Standard/Table/0.0.0-dev/src/Internal/Type_Refinements/Column_Refinements.enso @@ -4,35 +4,46 @@ from Standard.Base import all import project.Column.Column import project.Internal.Value_Type_Helpers +import project.Refined_Types.Numeric_Column.Numeric_Column import project.Value_Type.Value_Type from project.Internal.Type_Refinements.Single_Value_Column_Conversions import all from project.Internal.Type_Refinements.Typed_Column_Conversions import all refine_column (column : Column) = ## We treat a column as single value if it contains a single not-nothing value. - if is_single_value column . not then column else - inferred_value_type = column.inferred_precise_value_type - case inferred_value_type of - Value_Type.Integer _ -> - # `inferred_precise_value_type` will return Integer if the column was Float (or Mixed) but contained integral values - e.g. [2.0] - # We inspect the actual value to correctly deal with both Float and Mixed base type. - value = column.at 0 - case value of - # If the value was really a float, we preserve that. - _ : Float -> (column : Column & Numeric_Column & Float) - # Otherwise we treat it as an integer. - _ -> (column : Column & Numeric_Column & Integer) - Value_Type.Float _ -> (column : Column & Numeric_Column & Float) - Value_Type.Char _ _ -> (column : Column & Text) - Value_Type.Boolean -> (column : Column & Boolean) - Value_Type.Date -> (column : Column & Date) - Value_Type.Time -> (column : Column & Time_Of_Day) - Value_Type.Date_Time True -> (column : Column & Date_Time) - Value_Type.Decimal _ _ -> - is_integer = Value_Type_Helpers.is_decimal_integer inferred_value_type - if is_integer then (column : Column & Numeric_Column & Integer) else (column : Column & Numeric_Column & Decimal) - # Other types (e.g. Mixed) are not supported. - _ -> column + if is_single_value column then _refine_single_value_column column else _refine_column column + +private _refine_column column = + inferred_value_type = column.inferred_precise_value_type + case inferred_value_type of + Value_Type.Integer _ -> (column : Column & Numeric_Column) + Value_Type.Float _ -> (column : Column & Numeric_Column) + Value_Type.Decimal _ _ -> (column : Column & Numeric_Column) + _ -> column + +private _refine_single_value_column column = + inferred_value_type = column.inferred_precise_value_type + case inferred_value_type of + Value_Type.Integer _ -> + # `inferred_precise_value_type` will return Integer if the column was Float (or Mixed) but contained integral values - e.g. [2.0] + # We inspect the actual value to correctly deal with both Float and Mixed base type. + value = column.at 0 + case value of + # If the value was really a float, we preserve that. + _ : Float -> (column : Column & Numeric_Column & Float) + # Otherwise we treat it as an integer. + _ -> (column : Column & Numeric_Column & Integer) + Value_Type.Float _ -> (column : Column & Numeric_Column & Float) + Value_Type.Char _ _ -> (column : Column & Text) + Value_Type.Boolean -> (column : Column & Boolean) + Value_Type.Date -> (column : Column & Date) + Value_Type.Time -> (column : Column & Time_Of_Day) + Value_Type.Date_Time True -> (column : Column & Date_Time) + Value_Type.Decimal _ _ -> + is_integer = Value_Type_Helpers.is_decimal_integer inferred_value_type + if is_integer then (column : Column & Numeric_Column & Integer) else (column : Column & Numeric_Column & Decimal) + # Other types (e.g. Mixed) are not supported. + _ -> column is_single_value column:Column -> Boolean = (column.length == 1) && (column.at 0 . is_nothing . not) diff --git a/distribution/lib/Standard/Table/0.0.0-dev/src/Internal/Type_Refinements/Table_Refinements.enso b/distribution/lib/Standard/Table/0.0.0-dev/src/Internal/Type_Refinements/Table_Refinements.enso index d635a0fe617a..c9cf2b06324c 100644 --- a/distribution/lib/Standard/Table/0.0.0-dev/src/Internal/Type_Refinements/Table_Refinements.enso +++ b/distribution/lib/Standard/Table/0.0.0-dev/src/Internal/Type_Refinements/Table_Refinements.enso @@ -4,6 +4,7 @@ from Standard.Base import all import project.Column.Column import project.Internal.Value_Type_Helpers +import project.Refined_Types.Numeric_Column.Numeric_Column import project.Table.Table import project.Value_Type.Value_Type from project.Internal.Type_Refinements.Single_Column_Table_Conversions import all diff --git a/distribution/lib/Standard/Table/0.0.0-dev/src/Internal/Type_Refinements/Typed_Column_Conversions.enso b/distribution/lib/Standard/Table/0.0.0-dev/src/Internal/Type_Refinements/Typed_Column_Conversions.enso index ee98df624090..89b0470f2cf1 100644 --- a/distribution/lib/Standard/Table/0.0.0-dev/src/Internal/Type_Refinements/Typed_Column_Conversions.enso +++ b/distribution/lib/Standard/Table/0.0.0-dev/src/Internal/Type_Refinements/Typed_Column_Conversions.enso @@ -1,5 +1,30 @@ private +from Standard.Base import all +import Standard.Base.Errors.Illegal_Argument.Illegal_Argument + +import project.Column.Column +import project.Internal.Type_Refinements.Typed_Column_Helpers +import project.Refined_Types.Numeric_Column.Numeric_Column +import project.Value_Type.Value_Type +from project.Column import apply_unary_operation, naming_helper + +polyglot java import org.enso.table.data.column.operation.unary.AbsOperation +polyglot java import org.enso.table.data.column.operation.unary.SignumOperation + ## This conversion is internal and should never be exported. Numeric_Column.from (that : Column) = - ... + if that.value_type.is_numeric.not then + Error.throw (Illegal_Argument.Error "Cannot convert a "+that.value_type.to_display_text+" column to a numeric column.") + Typed_Column_Helpers.make_numeric_column that In_Memory_Numeric_Column_Implementation + +type In_Memory_Numeric_Column_Implementation + abs column = + Value_Type.expect_numeric column <| + new_name = naming_helper.concat ["abs", naming_helper.to_expression_text column] + apply_unary_operation column AbsOperation.INSTANCE new_name + + signum column = + Value_Type.expect_numeric column <| + new_name = naming_helper.concat ["signum", naming_helper.to_expression_text column] + apply_unary_operation column SignumOperation.INSTANCE new_name diff --git a/distribution/lib/Standard/Table/0.0.0-dev/src/Internal/Type_Refinements/Typed_Column_Helpers.enso b/distribution/lib/Standard/Table/0.0.0-dev/src/Internal/Type_Refinements/Typed_Column_Helpers.enso new file mode 100644 index 000000000000..0e5ec39d0535 --- /dev/null +++ b/distribution/lib/Standard/Table/0.0.0-dev/src/Internal/Type_Refinements/Typed_Column_Helpers.enso @@ -0,0 +1,7 @@ +import project.Refined_Types.Numeric_Column.Numeric_Column + +## PRIVATE + For internal use only, this function should be used by implementations to + provide a hidden conversion to `Numeric_Column` that allows the type refinement. +make_numeric_column base_column numeric_operations_implementation = + Numeric_Column.Value base_column numeric_operations_implementation diff --git a/distribution/lib/Standard/Table/0.0.0-dev/src/Main.enso b/distribution/lib/Standard/Table/0.0.0-dev/src/Main.enso index 74e5ca8d432f..a615163af529 100644 --- a/distribution/lib/Standard/Table/0.0.0-dev/src/Main.enso +++ b/distribution/lib/Standard/Table/0.0.0-dev/src/Main.enso @@ -47,6 +47,8 @@ export project.Position.Position export project.Prefix_Name.Prefix_Name +export project.Refined_Types.Numeric_Column.Numeric_Column + export project.Set_Mode.Set_Mode export project.Simple_Expression.Simple_Calculation diff --git a/distribution/lib/Standard/Table/0.0.0-dev/src/Refined_Types/Numeric_Column.enso b/distribution/lib/Standard/Table/0.0.0-dev/src/Refined_Types/Numeric_Column.enso index 0ca443a0ddab..53e2c888554c 100644 --- a/distribution/lib/Standard/Table/0.0.0-dev/src/Refined_Types/Numeric_Column.enso +++ b/distribution/lib/Standard/Table/0.0.0-dev/src/Refined_Types/Numeric_Column.enso @@ -1,8 +1,31 @@ +## A column that contains numeric values. type Numeric_Column private Value column operations_implementation + ## GROUP Standard.Base.Operators + ICON operators + Computes the absolute value of each element in the column. + + > Example + Compute the absolute value of values in a column. + + import Standard.Examples + + example_abs = Examples.decimal_column.abs abs self -> Numeric_Column = - "TODO" + self.operations_implementation.abs self.column + + ## GROUP Standard.Base.Operators + ICON operators + Computes the sign of each element in the column. + + It will return -1, 0 or 1 depending on the sign of the value. + + > Example + Compute the signum of values in a column. + + import Standard.Examples + example_signum = Examples.decimal_column.signum signum self -> Numeric_Column = - "TODO" + self.operations_implementation.signum self.column diff --git a/test/Table_Tests/src/Common_Table_Operations/Main.enso b/test/Table_Tests/src/Common_Table_Operations/Main.enso index 4dc890751917..520da01e0ec0 100644 --- a/test/Table_Tests/src/Common_Table_Operations/Main.enso +++ b/test/Table_Tests/src/Common_Table_Operations/Main.enso @@ -29,6 +29,7 @@ import project.Common_Table_Operations.Map_Spec import project.Common_Table_Operations.Map_To_Table_Spec import project.Common_Table_Operations.Missing_Values_Spec import project.Common_Table_Operations.Nothing_Spec +import project.Common_Table_Operations.Numeric_Column_Spec import project.Common_Table_Operations.Offset_Spec import project.Common_Table_Operations.Order_By_Spec import project.Common_Table_Operations.Select_Columns_Spec @@ -141,6 +142,7 @@ add_specs suite_builder setup = Map_To_Table_Spec.add_specs suite_builder setup Missing_Values_Spec.add_specs suite_builder setup Nothing_Spec.add_specs suite_builder setup + Numeric_Column_Spec.add_specs suite_builder setup Offset_Spec.add_specs suite_builder setup Order_By_Spec.add_specs suite_builder setup Replace_Spec.add_specs suite_builder setup diff --git a/test/Table_Tests/src/Common_Table_Operations/Numeric_Column_Spec.enso b/test/Table_Tests/src/Common_Table_Operations/Numeric_Column_Spec.enso new file mode 100644 index 000000000000..7f2912785b06 --- /dev/null +++ b/test/Table_Tests/src/Common_Table_Operations/Numeric_Column_Spec.enso @@ -0,0 +1,57 @@ +from Standard.Base import all +import Standard.Base.Errors.Common.Type_Error + +from Standard.Table import all +from Standard.Table.Errors import all + +import Standard.Database.Feature.Feature + +from Standard.Test import all + +import project.Common_Table_Operations.Util +from project.Common_Table_Operations.Util import run_default_backend + +type Lazy_Ref + Value ~get + +main filter=Nothing = run_default_backend add_specs filter + +add_specs suite_builder setup = + if setup.is_feature_supported Feature.Column_Operations then (add_numeric_columns_specs suite_builder setup) + +add_numeric_columns_specs suite_builder setup = + prefix = setup.prefix + table_builder = setup.light_table_builder + build_sorted_table = Util.build_sorted_table setup + suite_builder.group prefix+"Numeric_Column" group_builder-> + numeric_column = Lazy_Ref.Value <| build_sorted_table [["X", [-10, 0, 1, 12]]] . at "X" + group_builder.specify "a numeric column can be cast to Numeric_Column" <| + column = numeric_column.get + column.value_type.is_numeric . should_be_true + column:Numeric_Column + + group_builder.specify "a text column cannot be cast to Numeric_Column" <| + column = table_builder [["X", ["txt"]]] . at "X" + column.value_type.is_numeric . should_be_false + Test.expect_panic Type_Error (column:Numeric_Column) + + group_builder.specify "a numeric column supports abs operation" <| + column = numeric_column.get + abs_column = (column:Numeric_Column).abs + abs_column.to_vector . should_equal [10, 0, 1, 12] + + group_builder.specify "a numeric column supports signum operation" <| + column = numeric_column.get + signum_column = (column:Numeric_Column).signum + signum_column.to_vector . should_equal [-1, 0, 1, 1] + + group_builder.specify "floating-point columns support abs and signum" <| + column = build_sorted_table [["X", [-1.5, 0.0, -0.0, 1.0, 200.25]]] . at "X" + abs_column = (column:Numeric_Column).abs + abs_column.to_vector . should_equal [1.5, 0.0, 0.0, 1.0, 200.25] + signum_column = (column:Numeric_Column).signum + signum_column.to_vector . should_equal [-1, 0, 0, 1, 1] + + group_builder.specify "a single value column can also be Numeric_Column" <| + column = table_builder [["X", [1]]] . at "X" + column:Numeric_Column