@@ -139,3 +139,197 @@ function Base.join{T,N,D,Ax}(As::AxisArray{T,N,D,Ax}...; fillvalue::T=zero(T),
139
139
return result
140
140
141
141
end # join
142
+
143
+ function _collapse_array_axes (array_name, array_axes... )
144
+ ((array_name, (idx isa Tuple ? idx : (idx,)). .. ) for idx in product ((Ax. val for Ax in array_axes). .. ))
145
+ end
146
+
147
+ function _collapse_axes (array_names, array_axes)
148
+ collect (Iterators. flatten (map (array_names, array_axes) do tup_name, tup_array_axes
149
+ _collapse_array_axes (tup_name, tup_array_axes... )
150
+ end ))
151
+ end
152
+
153
+ function _splitall {N} (:: Type{Val{N}} , As... )
154
+ tuple ((Base. IteratorsMD. split (A, Val{N}) for A in As). .. )
155
+ end
156
+
157
+ function _reshapeall {N} (:: Type{Val{N}} , As... )
158
+ tuple ((reshape (A, Val{N}) for A in As). .. )
159
+ end
160
+
161
+ function _check_common_axes (common_axis_tuple)
162
+ if ! all (axisname (first (common_axis_tuple)) .=== axisname .(common_axis_tuple[2 : end ]))
163
+ throw (ArgumentError (" Leading common axes must have the same name in each array" ))
164
+ end
165
+
166
+ return nothing
167
+ end
168
+
169
+ function _collapsed_axis_eltype (LType, trailing_axes)
170
+ eltypes = map (trailing_axes) do array_trailing_axes
171
+ Tuple{LType, eltype .(array_trailing_axes)... }
172
+ end
173
+
174
+ return typejoin (eltypes... )
175
+ end
176
+
177
+ function collapse {N, AN} (:: Type{Val{N}} , As:: Vararg{AxisArray, AN} )
178
+ collapse (Val{N}, ntuple (identity, Val{AN}), As... )
179
+ end
180
+
181
+ function collapse {N, AN, NewArrayType<:AbstractArray} (:: Type{Val{N}} , :: Type{NewArrayType} , As:: Vararg{AxisArray, AN} )
182
+ collapse (Val{N}, NewArrayType, ntuple (identity, Val{AN}), As... )
183
+ end
184
+
185
+ @generated function collapse {N, AN, LType} (:: Type{Val{N}} , labels:: NTuple{AN, LType} , As:: Vararg{AxisArray, AN} )
186
+ collapsed_dim_int = Int (N) + 1
187
+ new_eltype = Base. promote_eltype (As... )
188
+
189
+ quote
190
+ collapse (Val{N}, Array{$ new_eltype, $ collapsed_dim_int}, labels, As... )
191
+ end
192
+ end
193
+
194
+ """
195
+ collapse(::Type{Val{N}}, As::AxisArray...) -> AxisArray
196
+ collapse(::Type{Val{N}}, labels::Tuple, As::AxisArray...) -> AxisArray
197
+ collapse(::Type{Val{N}}, ::Type{NewArrayType}, As::AxisArray...) -> AxisArray
198
+ collapse(::Type{Val{N}}, ::Type{NewArrayType}, labels::Tuple, As::AxisArray...) -> AxisArray
199
+
200
+ Collapses `AxisArray`s with `N` equal leading axes into a single `AxisArray`.
201
+ All additional axes in any of the arrays are collapsed into a single additional
202
+ axis of type `Axis{:collapsed, CategoricalVector{Tuple}}`.
203
+
204
+ ### Arguments
205
+
206
+ * `::Type{Val{N}}`: the greatest common dimension to share between all input
207
+ arrays. The remaining axes are collapsed. All `N` axes must be common
208
+ to each input array, at the same dimension. Values from `0` up to the
209
+ minimum number of dimensions across all input arrays are allowed.
210
+ * `labels::Tuple`: (optional) an index for each array in `As` used as the leading element in
211
+ the index tuples in the `:collapsed` axis. Defaults to `1:length(As)`.
212
+ * `::Type{NewArrayType<:AbstractArray{_, N+1}}`: (optional) the desired underlying array
213
+ type for the returned `AxisArray`.
214
+ * `As::AxisArray...`: `AxisArray`s to be collapsed together.
215
+
216
+ ### Examples
217
+
218
+ ```
219
+ julia> price_data = AxisArray(rand(10), Axis{:time}(Date(2016,01,01):Day(1):Date(2016,01,10)))
220
+ 1-dimensional AxisArray{Float64,1,...} with axes:
221
+ :time, 2016-01-01:1 day:2016-01-10
222
+ And data, a 10-element Array{Float64,1}:
223
+ 0.885014
224
+ 0.418562
225
+ 0.609344
226
+ 0.72221
227
+ 0.43656
228
+ 0.840304
229
+ 0.455337
230
+ 0.65954
231
+ 0.393801
232
+ 0.260207
233
+
234
+ julia> size_data = AxisArray(rand(10,2), Axis{:time}(Date(2016,01,01):Day(1):Date(2016,01,10)), Axis{:measure}([:area, :volume]))
235
+ 2-dimensional AxisArray{Float64,2,...} with axes:
236
+ :time, 2016-01-01:1 day:2016-01-10
237
+ :measure, Symbol[:area, :volume]
238
+ And data, a 10×2 Array{Float64,2}:
239
+ 0.159434 0.456992
240
+ 0.344521 0.374623
241
+ 0.522077 0.313256
242
+ 0.994697 0.320953
243
+ 0.95104 0.900526
244
+ 0.921854 0.729311
245
+ 0.000922581 0.148822
246
+ 0.449128 0.761714
247
+ 0.650277 0.135061
248
+ 0.688773 0.513845
249
+
250
+ julia> collapsed = collapse(Val{1}, (:price, :size), price_data, size_data)
251
+ 2-dimensional AxisArray{Float64,2,...} with axes:
252
+ :time, 2016-01-01:1 day:2016-01-10
253
+ :collapsed, Tuple{Symbol,Vararg{Symbol,N} where N}[(:price,), (:size, :area), (:size, :volume)]
254
+ And data, a 10×3 Array{Float64,2}:
255
+ 0.885014 0.159434 0.456992
256
+ 0.418562 0.344521 0.374623
257
+ 0.609344 0.522077 0.313256
258
+ 0.72221 0.994697 0.320953
259
+ 0.43656 0.95104 0.900526
260
+ 0.840304 0.921854 0.729311
261
+ 0.455337 0.000922581 0.148822
262
+ 0.65954 0.449128 0.761714
263
+ 0.393801 0.650277 0.135061
264
+ 0.260207 0.688773 0.513845
265
+
266
+ julia> collapsed[Axis{:collapsed}(:size)] == size_data
267
+ true
268
+ ```
269
+
270
+ """
271
+ @generated function collapse (:: Type{Val{N}} ,
272
+ :: Type{NewArrayType} ,
273
+ labels:: NTuple{AN, LType} ,
274
+ As:: Vararg{AxisArray, AN} ) where {N, AN, LType, NewArrayType<: AbstractArray }
275
+ if N < 0
276
+ throw (ArgumentError (" collapse dimension N must be at least 0" ))
277
+ end
278
+
279
+ if N > minimum (ndims .(As))
280
+ throw (ArgumentError (
281
+ """
282
+ collapse dimension N must not be greater than the maximum number of dimensions
283
+ across all input arrays
284
+ """
285
+ ))
286
+ end
287
+
288
+ collapsed_dim = Val{N + 1 }
289
+ collapsed_dim_int = Int (N) + 1
290
+
291
+ common_axes, trailing_axes = zip (_splitall (Val{N}, axisparams .(As)... )... )
292
+
293
+ foreach (_check_common_axes, zip (common_axes... ))
294
+
295
+ new_common_axes = first (common_axes)
296
+ collapsed_axis_eltype = _collapsed_axis_eltype (LType, trailing_axes)
297
+ collapsed_axis_type = CategoricalVector{collapsed_axis_eltype, Vector{collapsed_axis_eltype}}
298
+
299
+ new_axes_type = Tuple{new_common_axes... , Axis{:collapsed , collapsed_axis_type}}
300
+ new_eltype = Base. promote_eltype (As... )
301
+
302
+ quote
303
+ common_axes, trailing_axes = zip (_splitall (Val{N}, axes .(As)... )... )
304
+
305
+ for common_axis_tuple in zip (common_axes... )
306
+ if ! isempty (common_axis_tuple)
307
+ for common_axis in common_axis_tuple[2 : end ]
308
+ if ! all (axisvalues (common_axis) .== axisvalues (common_axis_tuple[1 ]))
309
+ throw (ArgumentError (
310
+ """
311
+ Leading common axes must be identical across
312
+ all input arrays"""
313
+ ))
314
+ end
315
+ end
316
+ end
317
+ end
318
+
319
+ array_data = cat ($ collapsed_dim, _reshapeall ($ collapsed_dim, As... )... )
320
+
321
+ axis_array_type = AxisArray{
322
+ $ new_eltype,
323
+ $ collapsed_dim_int,
324
+ $ NewArrayType,
325
+ $ new_axes_type
326
+ }
327
+
328
+ new_axes = (
329
+ first (common_axes)... ,
330
+ Axis {:collapsed, $collapsed_axis_type} ($ collapsed_axis_type (_collapse_axes (labels, trailing_axes))),
331
+ )
332
+
333
+ return axis_array_type (array_data, new_axes)
334
+ end
335
+ end
0 commit comments