From 6d1bc045aa76dd3e1a0994de791bc59de60fd6e7 Mon Sep 17 00:00:00 2001
From: Nicole Epp <nicole.epp@invenia.ca>
Date: Wed, 10 Feb 2021 16:03:11 -0600
Subject: [PATCH 1/4] Implement general case non-mutating apply

---
 src/periodic.jl     | 31 ++++-------------
 src/transformers.jl | 74 +++++++++++++++++++++++++++++++++-------
 test/periodic.jl    | 43 ++++++++++++------------
 test/power.jl       | 82 ++++++++++++++++++++++++---------------------
 4 files changed, 134 insertions(+), 96 deletions(-)

diff --git a/src/periodic.jl b/src/periodic.jl
index 0c25ba2..4c953a3 100644
--- a/src/periodic.jl
+++ b/src/periodic.jl
@@ -35,34 +35,17 @@ Returns a `Periodic` transform with zero phase shift.
 """
 Periodic(f, period::T) where T = Periodic(f, period, zero(T))
 
-function _apply!(x::AbstractArray{T}, P::Periodic; kwargs...) where T <: Real
-    x[:] = P.f.(2π .* (x .- P.phase_shift) / P.period)
-    return x
+function _apply(x, P::Periodic; kwargs...)
+    return P.f.(2π .* (x .- P.phase_shift) / P.period)
 end
 
-# `U <: Period` needed to avoid method ambiguity with
-# `apply(table, P::Periodic{T}; cols=nothing) where T <: Period`
-function apply(
-    x::AbstractArray{T},
-    P::Periodic{U};
-    kwargs...
-) where {T <: TimeType, U <: Period}
-    return map(xi -> _periodic(P.f, xi, P.period, P.phase_shift), x)
+function _apply(x, P::Periodic{T}; kwargs...) where T <: Period
+    map(xi -> _periodic(P.f, xi, P.period, P.phase_shift), x)
 end
 
-"""
-    Transforms.apply(table, ::Periodic{T}; cols=nothing) where T <: Period -> Array
-
-Applies [`Periodic`](@ref) to each of the specified columns in `table`.
-If no `cols` are specified, then [`Periodic`](@ref) is applied to all columns.
-Returns an array containing each transformed column, in the same order as `cols`.
-"""
-function apply(table, P::Periodic{T}; cols=nothing) where T <: Period
-    Tables.istable(table) || throw(MethodError(apply, (table, P)))
-
-    columntable = Tables.columns(table)
-    cnames = cols === nothing ? propertynames(columntable) : cols
-    return [apply(getproperty(columntable, cname), P) for cname in cnames]
+function _apply!(x::AbstractArray{T}, P::Periodic; kwargs...) where T <: Real
+    x[:] = apply(x, P; kwargs...)
+    return x
 end
 
 """
diff --git a/src/transformers.jl b/src/transformers.jl
index 238b8ef..e7a1c1e 100644
--- a/src/transformers.jl
+++ b/src/transformers.jl
@@ -28,30 +28,80 @@ Non-mutating version of [`transform!`](@ref).
 function transform end
 
 """
-    Transforms.apply!(data::T, ::Transform; kwargs...) -> T
+    apply(data::T, ::Transform; kwargs...)
+
+Applies the [`Transform`](@ref) to the data. New transforms should usually only extend
+`_apply` which this method delegates to.
+
+Where possible, this should be extended for new data types `T`.
+"""
+function apply end
+
+"""
+    apply!(data::T, ::Transform; kwargs...) -> T
+
+Applies the [`Transform`](@ref) mutating the input `data`. New transforms should usually
+only extend `_apply!` which this method delegates to.
 
-Applies the [`Transform`](@ref) mutating the input `data`.
 Where possible, this should be extended for new data types `T`.
 """
 function apply! end
 
+
 """
-    Transforms.apply(data::T, ::Transform; kwargs...) -> T
+    apply(A::AbstractArray, ::Transform; dims=:, inds=:, kwargs...)
 
-Non-mutating version of [`apply!`](@ref), which it delegates to by default.
-Does not need to be extended unless a mutating [`Transform`](@ref) is not possible.
+Applies the [`Transform`](@ref) to each element of `A`.
+Optionally specify the `dims` to apply the [`Transform`](@ref) along certain dimensions
+and `inds` will be the indices to apply the Transform to along the `dims` specified.
+If `dims` === : (all dimensions), then `inds` will be the global indices of the array,
+instead of being relative to a certain dimension.
+
+Will return the same datatype except for AxisArrays as operations on those do not preserve
+type.
 """
-function apply end
+function apply(A::AbstractArray, t::Transform; dims=:, inds=:, kwargs...)
+    if dims === Colon()
+        if inds === Colon()
+            return _apply(A, t; kwargs...)
+        else
+            return _apply(A[:][inds], t; kwargs...)
+        end
+    end
+
+    return mapslices(x -> _apply(x[inds], t; kwargs...), A, dims=dims)
+end
 
 """
-    apply!(A::AbstractArray{T}, ::Transform; dims=:, kwargs...) where T <: Real
+    apply(table, ::Transform; cols=nothing, kwargs...)
+
+Applies the [`Transform`](@ref) to each of the specified columns in the `table`.
+If no `cols` are specified, then the [`Transform`](@ref) is applied to all columns.
+
+Returns an array containing each transformed column, in the same order as `cols`.
+"""
+function apply(table, t::Transform; cols=nothing, kwargs...)
+    Tables.istable(table) || throw(MethodError(apply!, (table, t)))
+
+    # Extract a columns iterator that we should be able to use to mutate the data.
+    # NOTE: Mutation is not guaranteed for all table types, but it avoid copying the data
+    columntable = Tables.columns(table)
+
+    cnames = cols === nothing ? propertynames(columntable) : cols
+    return [_apply(getproperty(columntable, cname), t; kwargs...)  for cname in cnames]
+end
+
+#TODO: should this be apply! ?
+_apply(x, t::Transform; kwargs...) = apply!(_try_copy(x), t; kwargs...)
+
+
+"""
+    apply!(A::AbstractArray, ::Transform; dims=:, kwargs...)
 
 Applies the [`Transform`](@ref) to each element of `A`.
 Optionally specify the `dims` to apply the [`Transform`](@ref) along certain dimensions.
 """
-function apply!(
-    A::AbstractArray{T}, t::Transform; dims=:, kwargs...
-) where T <: Real
+function apply!(A::AbstractArray, t::Transform; dims=:, kwargs...)
     dims == Colon() && return _apply!(A, t; kwargs...)
 
     for x in eachslice(A; dims=dims)
@@ -61,10 +111,8 @@ function apply!(
     return A
 end
 
-apply(x, t::Transform; kwargs...) = apply!(_try_copy(x), t; kwargs...)
-
 """
-    Transforms.apply!(table::T, ::Transform; cols=nothing)::T where T
+    apply!(table::T, ::Transform; cols=nothing)::T where T
 
 Applies the [`Transform`](@ref) to each of the specified columns in the `table`.
 If no `cols` are specified, then the [`Transform`](@ref) is applied to all columns.
diff --git a/test/periodic.jl b/test/periodic.jl
index 2ad8ee1..85147fd 100644
--- a/test/periodic.jl
+++ b/test/periodic.jl
@@ -127,12 +127,14 @@
 
                 @testset "dims = $d" for d in (Colon(), 1, 2)
                     transformed = Transforms.apply(A, p; dims=d)
-                    @test transformed isa AxisArray
+                    # AxisArray doesn't preserve type when operations are performed on it
+                    @test transformed isa AbstractArray
                     @test transformed ≈ A_expected atol=1e-14
                 end
 
                 _A = copy(A)
                 Transforms.apply!(_A, p)
+                @test _A isa AxisArray
                 @test _A ≈ A_expected atol=1e-14
             end
 
@@ -163,12 +165,12 @@
 
                 @testset "all cols" begin
                     transformed = Transforms.apply(nt, p)
-                    @test transformed isa NamedTuple{(:a, :b)}
-                    @test collect(transformed) ≈ collect(nt_expected) atol=1e-14
-                    @test collect(p(nt)) ≈ collect(nt_expected) atol=1e-14
+                    @test transformed ≈ collect(nt_expected) atol=1e-14
+                    @test p(nt) ≈ collect(nt_expected) atol=1e-14
 
                     _nt = deepcopy(nt)
                     Transforms.apply!(_nt, p)
+                    @test _nt isa NamedTuple{(:a, :b)}
                     @test collect(_nt) ≈ collect(nt_expected) atol=1e-14
                 end
 
@@ -177,12 +179,12 @@
                     nt_expected_ = merge(nt, nt_mutated)
 
                     transformed = Transforms.apply(nt, p; cols=[c])
-                    @test transformed isa NamedTuple{(:a, :b)}  # before applying `collect`
-                    @test collect(transformed) ≈ collect(nt_expected_) atol=1e-14
-                    @test collect(p(nt; cols=[c])) ≈ collect(nt_expected_) atol=1e-14
+                    @test transformed ≈ [collect(nt_expected_[c])] atol=1e-14
+                    @test p(nt; cols=[c]) ≈ [collect(nt_expected_[c])] atol=1e-14
 
                     _nt = deepcopy(nt)
                     Transforms.apply!(_nt, p; cols=[c])
+                    @test _nt isa NamedTuple{(:a, :b)}  # before applying `collect`
                     @test collect(_nt) ≈ collect(nt_expected_) atol=1e-14
                 end
             end
@@ -191,23 +193,19 @@
                 df = DataFrame(:a => collect(0.:2.), :b => collect(3.:5.))
                 df_expected = DataFrame(:a => expected[1:3], :b => expected[4:6])
 
-                transformed = Transforms.apply(df, p)
-                @test transformed isa DataFrame
-                @test transformed ≈ df_expected atol=1e-14
+                @test Transforms.apply(df, p) ≈ [df_expected.a, df_expected.b] atol=1e-14
 
-                @test ≈(
-                    Transforms.apply(df, p; cols=[:a]),
-                    DataFrame(:a => expected[1:3], :b => collect(3.:5.)),
-                    atol=1e-14
-                )
-                @test ≈(
-                    Transforms.apply(df, p; cols=[:b]),
-                    DataFrame(:a => collect(0.:2.), :b => expected[4:6]),
-                    atol=1e-14
-                )
+                @testset "cols = $c" for c in (:a, :b)
+                    @test ≈(
+                        Transforms.apply(df, p; cols=[c]),
+                        [df_expected[!, c]],
+                        atol=1e-14
+                    )
+                end
 
                 _df = deepcopy(df)
                 Transforms.apply!(_df, p)
+                @test _df isa DataFrame
                 @test _df ≈ df_expected atol=1e-14
             end
         end
@@ -271,9 +269,12 @@
 
                 @testset "dims = $d" for d in (Colon(), 1, 2)
                     transformed = Transforms.apply(A, p; dims=d)
-                    @test transformed isa AxisArray
+                    # AxisArray doesn't preserve type when operations are performed on it
+                    @test transformed isa AbstractArray
                     @test transformed ≈ expected atol=1e-14
                 end
+
+                # TODO: confirm we don't support mutating periodic for Time Types
             end
 
             @testset "AxisKey" begin
diff --git a/test/power.jl b/test/power.jl
index a1ccb38..54ec237 100644
--- a/test/power.jl
+++ b/test/power.jl
@@ -30,44 +30,21 @@
         end
     end
 
-    @testset "NamedTuple" begin
-        nt = (a = [1, 2, 3], b = [4, 5, 6])
-        expected = (a = [1, 8, 27], b = [64, 125, 216])
-
-        @testset "all cols" begin
-            transformed = Transforms.apply(nt, p)
-            @test transformed isa NamedTuple{(:a, :b)}
-            @test transformed == expected
-            @test p(nt) == expected
-
-            _nt = deepcopy(nt)
-            Transforms.apply!(_nt, p)
-            @test _nt == expected
-        end
-
-        @testset "cols = $c" for c in (:a, :b)
-            nt_mutated = NamedTuple{(Symbol("$c"), )}((expected[c], ))
-            nt_expected = merge(nt, nt_mutated)
-
-            @test Transforms.apply(nt, p; cols=[c]) == nt_expected
-            @test p(nt; cols=[c]) == nt_expected
-
-            _nt = deepcopy(nt)
-            Transforms.apply!(_nt, p; cols=[c])
-            @test _nt == nt_expected
-        end
-    end
-
     @testset "AxisArray" begin
         A = AxisArray([1 2 3; 4 5 6], foo=["a", "b"], bar=["x", "y", "z"])
-        expected = AxisArray([1 8 27; 64 125 216], foo=["a", "b"], bar=["x", "y", "z"])
+        expected = [1 8 27; 64 125 216]
 
         @testset "dims = $d" for d in (Colon(), 1, 2)
             transformed = Transforms.apply(A, p; dims=d)
-            @test transformed isa AxisArray
+            # AxisArray doesn't preserve the type it operates on
+            @test transformed isa AbstractArray
             @test transformed == expected
         end
 
+        _A = copy(A)
+        Transforms.apply!(_A, p)
+        @test _A isa AxisArray
+        @test _A == expected
     end
 
     @testset "AxisKey" begin
@@ -82,23 +59,52 @@
 
         _A = copy(A)
         Transforms.apply!(_A, p)
+        @test _A isa KeyedArray
         @test _A == expected
     end
 
+    @testset "NamedTuple" begin
+        nt = (a = [1, 2, 3], b = [4, 5, 6])
+        expected = [[1, 8, 27], [64, 125, 216]]
+        expected_nt = (a = [1, 8, 27], b = [64, 125, 216])
+
+        @testset "all cols" begin
+            @test Transforms.apply(nt, p) == expected
+            @test p(nt) == expected
+
+            _nt = deepcopy(nt)
+            Transforms.apply!(_nt, p)
+            @test _nt isa NamedTuple{(:a, :b)}
+            @test _nt == expected_nt
+        end
+
+        @testset "cols = $c" for c in (:a, :b)
+            nt_mutated = NamedTuple{(Symbol("$c"), )}((expected_nt[c], ))
+            expected_nt_mutated = merge(nt, nt_mutated)
+
+            @test Transforms.apply(nt, p; cols=[c]) == [expected_nt[c]]
+            @test p(nt; cols=[c]) == [expected_nt[c]]
+
+            _nt = deepcopy(nt)
+            Transforms.apply!(_nt, p; cols=[c])
+            @test _nt == expected_nt_mutated
+            @test _nt isa NamedTuple
+        end
+    end
+
     @testset "DataFrame" begin
         df = DataFrame(:a => [1, 2, 3], :b => [4, 5, 6])
-        expected = DataFrame(:a => [1, 8, 27], :b => [64, 125, 216])
+        expected_df = DataFrame(:a => [1, 8, 27], :b => [64, 125, 216])
+        expected = [expected_df.a, expected_df.b]
 
-        transformed = Transforms.apply(df, p)
-        @test transformed isa DataFrame
-        @test transformed == expected
+        @test Transforms.apply(df, p) == expected
 
-        @test Transforms.apply(df, p; cols=[:a]) == DataFrame(:a => [1, 8, 27], :b => [4, 5, 6])
-        @test Transforms.apply(df, p; cols=[:b]) == DataFrame(:a => [1, 2, 3], :b => [64, 125, 216])
+        @test Transforms.apply(df, p; cols=[:a]) == [expected_df.a]
+        @test Transforms.apply(df, p; cols=[:b]) ==[expected_df.b]
 
         _df = deepcopy(df)
         Transforms.apply!(_df, p)
-        @test _df == expected
+        @test _df isa DataFrame
+        @test _df == expected_df
     end
-
 end

From e15d02fa26a8a72e8015b80c601568a69f553d81 Mon Sep 17 00:00:00 2001
From: Nicole Epp <nicole.epp@invenia.ca>
Date: Thu, 11 Feb 2021 09:32:45 -0600
Subject: [PATCH 2/4] Resolve small PR comments

---
 src/periodic.jl     | 2 +-
 src/transformers.jl | 6 +++---
 test/periodic.jl    | 2 --
 3 files changed, 4 insertions(+), 6 deletions(-)

diff --git a/src/periodic.jl b/src/periodic.jl
index 4c953a3..beaab77 100644
--- a/src/periodic.jl
+++ b/src/periodic.jl
@@ -44,7 +44,7 @@ function _apply(x, P::Periodic{T}; kwargs...) where T <: Period
 end
 
 function _apply!(x::AbstractArray{T}, P::Periodic; kwargs...) where T <: Real
-    x[:] = apply(x, P; kwargs...)
+    x[:] = _apply(x, P; kwargs...)
     return x
 end
 
diff --git a/src/transformers.jl b/src/transformers.jl
index e7a1c1e..3f131ba 100644
--- a/src/transformers.jl
+++ b/src/transformers.jl
@@ -57,8 +57,8 @@ and `inds` will be the indices to apply the Transform to along the `dims` specif
 If `dims` === : (all dimensions), then `inds` will be the global indices of the array,
 instead of being relative to a certain dimension.
 
-Will return the same datatype except for AxisArrays as operations on those do not preserve
-type.
+May not return the same data type depending on what the data type is, and what `dims` and
+`inds` were specified.
 """
 function apply(A::AbstractArray, t::Transform; dims=:, inds=:, kwargs...)
     if dims === Colon()
@@ -81,7 +81,7 @@ If no `cols` are specified, then the [`Transform`](@ref) is applied to all colum
 Returns an array containing each transformed column, in the same order as `cols`.
 """
 function apply(table, t::Transform; cols=nothing, kwargs...)
-    Tables.istable(table) || throw(MethodError(apply!, (table, t)))
+    Tables.istable(table) || throw(MethodError(apply, (table, t)))
 
     # Extract a columns iterator that we should be able to use to mutate the data.
     # NOTE: Mutation is not guaranteed for all table types, but it avoid copying the data
diff --git a/test/periodic.jl b/test/periodic.jl
index 85147fd..1afb64f 100644
--- a/test/periodic.jl
+++ b/test/periodic.jl
@@ -273,8 +273,6 @@
                     @test transformed isa AbstractArray
                     @test transformed ≈ expected atol=1e-14
                 end
-
-                # TODO: confirm we don't support mutating periodic for Time Types
             end
 
             @testset "AxisKey" begin

From 652bb72ae9897084899a81d700601677a0722fe5 Mon Sep 17 00:00:00 2001
From: Nicole Epp <nicole.epp@invenia.ca>
Date: Thu, 11 Feb 2021 14:56:01 -0600
Subject: [PATCH 3/4] Add inds tests

---
 src/power.jl     |  6 +++++-
 test/periodic.jl | 30 ++++++++++++++++++++++++++++++
 test/power.jl    | 30 ++++++++++++++++++++++++++++++
 3 files changed, 65 insertions(+), 1 deletion(-)

diff --git a/src/power.jl b/src/power.jl
index 95010a5..d628e85 100644
--- a/src/power.jl
+++ b/src/power.jl
@@ -7,7 +7,11 @@ struct Power <: Transform
     exponent::Real
 end
 
+function _apply(x::AbstractArray{T}, P::Power; kwargs...) where T <: Real
+    return x .^ P.exponent
+end
+
 function _apply!(x::AbstractArray{T}, P::Power; kwargs...) where T <: Real
-    x[:] = x .^ P.exponent
+    x[:] = _apply(x, P; kwargs...)
     return x
 end
diff --git a/test/periodic.jl b/test/periodic.jl
index 1afb64f..0aa81e1 100644
--- a/test/periodic.jl
+++ b/test/periodic.jl
@@ -98,6 +98,15 @@
                 _x = copy(x)
                 Transforms.apply!(_x, p)
                 @test _x ≈ expected atol=1e-14
+
+                @testset "inds" begin
+                    @test Transforms.apply(x, p; inds=2:5) ≈ expected[2:5] atol=1e-14
+                    @test Transforms.apply(x, p; dims=:) ≈ expected atol=1e-14
+                    @test Transforms.apply(x, p; dims=1) ≈ expected atol=1e-14
+                    @test Transforms.apply(x, p; dims=1, inds=[2, 3, 4, 5]) ≈ expected[2:5] atol=1e-14
+
+                    @test_throws BoundsError Transforms.apply(x, p; dims=2)
+                end
             end
 
             @testset "Matrix" begin
@@ -113,6 +122,13 @@
                     Transforms.apply!(_M, p; dims=d)
                     @test _M ≈ M_expected atol=1e-14
                 end
+
+                @testset "inds" begin
+                    @test Transforms.apply(M, p; inds=[2, 3]) ≈ M_expected[[2, 3]] atol=1e-14
+                    @test Transforms.apply(M, p; dims=:, inds=[2, 3]) ≈ M_expected[[2, 3]] atol=1e-14
+                    @test Transforms.apply(M, p; dims=1, inds=[2]) ≈ reshape(M_expected[[2, 5]], 1, 2) atol=1e-14
+                    @test Transforms.apply(M, p; dims=2, inds=[2]) ≈ reshape(M_expected[[4, 5, 6]], 3, 1) atol=1e-14
+                end
             end
 
             @testset "AxisArray" begin
@@ -136,6 +152,13 @@
                 Transforms.apply!(_A, p)
                 @test _A isa AxisArray
                 @test _A ≈ A_expected atol=1e-14
+
+                @testset "inds" begin
+                    @test Transforms.apply(A, p; inds=[2, 3]) ≈ A_expected[[2, 3]] atol=1e-14
+                    @test Transforms.apply(A, p; dims=:, inds=[2, 3]) ≈ A_expected[[2, 3]] atol=1e-14
+                    @test Transforms.apply(A, p; dims=1, inds=[2]) ≈ reshape(A_expected[[2, 5]], 1, 2) atol=1e-14
+                    @test Transforms.apply(A, p; dims=2, inds=[2]) ≈ reshape(A_expected[[4, 5, 6]], 3, 1) atol=1e-14
+                end
             end
 
             @testset "AxisKey" begin
@@ -157,6 +180,13 @@
                 _A = copy(A)
                 Transforms.apply!(_A, p)
                 @test _A ≈ A_expected atol=1e-14
+
+                @testset "inds" begin
+                    @test Transforms.apply(A, p; inds=[2, 3]) ≈ [A_expected[2], A_expected[3]] atol=1e-14
+                    @test Transforms.apply(A, p; dims=:, inds=[2, 3]) ≈ [A_expected[2], A_expected[3]] atol=1e-14
+                    @test Transforms.apply(A, p; dims=1, inds=[2]) ≈ reshape([A_expected[2], A_expected[5]], 1, 2) atol=1e-14
+                    @test Transforms.apply(A, p; dims=2, inds=[2]) ≈ reshape([A_expected[4], A_expected[5], A_expected[6]], 3, 1) atol=1e-14
+                end
             end
 
             @testset "NamedTuple" begin
diff --git a/test/power.jl b/test/power.jl
index 54ec237..8ec2066 100644
--- a/test/power.jl
+++ b/test/power.jl
@@ -14,6 +14,15 @@
         _x = copy(x)
         Transforms.apply!(_x, p)
         @test _x == expected
+
+        @testset "inds" begin
+            @test Transforms.apply(x, p; inds=2:5) ==  expected[2:5]
+            @test Transforms.apply(x, p; dims=:) == expected
+            @test Transforms.apply(x, p; dims=1) == expected
+            @test Transforms.apply(x, p; dims=1, inds=[2, 3, 4, 5]) == expected[2:5]
+
+            @test_throws BoundsError Transforms.apply(x, p; dims=2)
+        end
     end
 
     @testset "Matrix" begin
@@ -28,6 +37,13 @@
             Transforms.apply!(_M, p; dims=d)
             @test _M == expected
         end
+
+        @testset "inds" begin
+            @test Transforms.apply(M, p; inds=[2, 3]) == expected[[2, 3]]
+            @test Transforms.apply(M, p; dims=:, inds=[2, 3]) == expected[[2, 3]]
+            @test Transforms.apply(M, p; dims=1, inds=[2]) == [64 125 216]
+            @test Transforms.apply(M, p; dims=2, inds=[2]) == reshape([8, 125], 2, 1)
+        end
     end
 
     @testset "AxisArray" begin
@@ -45,6 +61,13 @@
         Transforms.apply!(_A, p)
         @test _A isa AxisArray
         @test _A == expected
+
+        @testset "inds" begin
+            @test Transforms.apply(A, p; inds=[2, 3]) == expected[[2, 3]]
+            @test Transforms.apply(A, p; dims=:, inds=[2, 3]) == expected[[2, 3]]
+            @test Transforms.apply(A, p; dims=1, inds=[2]) == [64 125 216]
+            @test Transforms.apply(A, p; dims=2, inds=[2]) == reshape([8, 125], 2, 1)
+        end
     end
 
     @testset "AxisKey" begin
@@ -61,6 +84,13 @@
         Transforms.apply!(_A, p)
         @test _A isa KeyedArray
         @test _A == expected
+
+        @testset "inds" begin
+            @test Transforms.apply(A, p; inds=[2, 3]) == [64, 8]
+            @test Transforms.apply(A, p; dims=:, inds=[2, 3]) == [64, 8]
+            @test Transforms.apply(A, p; dims=1, inds=[2]) == [64 125 216]
+            @test Transforms.apply(A, p; dims=2, inds=[2]) == reshape([8, 125], 2, 1)
+        end
     end
 
     @testset "NamedTuple" begin

From 2becaea1d48b102dea334c1090ae0d0e54d1178e Mon Sep 17 00:00:00 2001
From: Nicole Epp <nicole.epp@invenia.ca>
Date: Thu, 11 Feb 2021 15:05:43 -0600
Subject: [PATCH 4/4] Clarify docstrings

---
 src/transformers.jl | 24 +++++++++++++-----------
 1 file changed, 13 insertions(+), 11 deletions(-)

diff --git a/src/transformers.jl b/src/transformers.jl
index 3f131ba..a356315 100644
--- a/src/transformers.jl
+++ b/src/transformers.jl
@@ -33,7 +33,7 @@ function transform end
 Applies the [`Transform`](@ref) to the data. New transforms should usually only extend
 `_apply` which this method delegates to.
 
-Where possible, this should be extended for new data types `T`.
+Where necessary, this should be extended for new data types `T`.
 """
 function apply end
 
@@ -43,7 +43,7 @@ function apply end
 Applies the [`Transform`](@ref) mutating the input `data`. New transforms should usually
 only extend `_apply!` which this method delegates to.
 
-Where possible, this should be extended for new data types `T`.
+Where necessary, this should be extended for new data types `T`.
 """
 function apply! end
 
@@ -51,14 +51,17 @@ function apply! end
 """
     apply(A::AbstractArray, ::Transform; dims=:, inds=:, kwargs...)
 
-Applies the [`Transform`](@ref) to each element of `A`.
-Optionally specify the `dims` to apply the [`Transform`](@ref) along certain dimensions
-and `inds` will be the indices to apply the Transform to along the `dims` specified.
-If `dims` === : (all dimensions), then `inds` will be the global indices of the array,
+Applies the [`Transform`](@ref) to the elements of `A`.
+Provide the `dims` keyword to apply the [`Transform`](@ref) along a certain dimension.
+Provide the `inds` keyword to apply the [`Transform`](@ref) to certain indices along the
+`dims` specified.
+
+Note: if `dims === :` (all dimensions), then `inds` will be the global indices of the array,
 instead of being relative to a certain dimension.
 
-May not return the same data type depending on what the data type is, and what `dims` and
-`inds` were specified.
+This method does not guarantee the data type of what is returned. It will try to conserve
+type but the returned type depends on what the original `A` was, and the `dims` and `inds`
+specified.
 """
 function apply(A::AbstractArray, t::Transform; dims=:, inds=:, kwargs...)
     if dims === Colon()
@@ -73,7 +76,7 @@ function apply(A::AbstractArray, t::Transform; dims=:, inds=:, kwargs...)
 end
 
 """
-    apply(table, ::Transform; cols=nothing, kwargs...)
+    apply(table, ::Transform; cols=nothing, kwargs...) -> Vector
 
 Applies the [`Transform`](@ref) to each of the specified columns in the `table`.
 If no `cols` are specified, then the [`Transform`](@ref) is applied to all columns.
@@ -91,8 +94,7 @@ function apply(table, t::Transform; cols=nothing, kwargs...)
     return [_apply(getproperty(columntable, cname), t; kwargs...)  for cname in cnames]
 end
 
-#TODO: should this be apply! ?
-_apply(x, t::Transform; kwargs...) = apply!(_try_copy(x), t; kwargs...)
+_apply(x, t::Transform; kwargs...) = _apply!(_try_copy(x), t; kwargs...)
 
 
 """