8
8
using Base: @pure
9
9
end
10
10
11
+ typealias Symbols Tuple{Symbol,Vararg{Symbol}}
12
+
11
13
@doc """
12
14
Type-stable axis-specific indexing and identification with a
13
15
parametric type.
@@ -51,7 +53,7 @@ immutable Axis{name,T}
51
53
end
52
54
# Constructed exclusively through Axis{:symbol}(...) or Axis{1}(...)
53
55
@compat (:: Type{Axis{name}} ){name,T}(I:: T = ()) = Axis {name,T} (I)
54
- @compat Base.:(== ){name,T }(A:: Axis{name,T } , B:: Axis{name,T } ) = A. val == B. val
56
+ @compat Base.:(== ){name}(A:: Axis{name} , B:: Axis{name} ) = A. val == B. val
55
57
Base. hash {name} (A:: Axis{name} , hx:: UInt ) = hash (A. val, hash (name, hx))
56
58
axistype {name,T} (:: Axis{name,T} ) = T
57
59
axistype {name,T} (:: Type{Axis{name,T}} ) = T
@@ -61,8 +63,12 @@ Base.getindex(A::Axis, i...) = A.val[i...]
61
63
Base. unsafe_getindex (A:: Axis , i... ) = Base. unsafe_getindex (A, i... )
62
64
Base. eltype {_,T} (:: Type{Axis{_,T}} ) = eltype (T)
63
65
Base. size (A:: Axis ) = size (A. val)
66
+ Base. indices (A:: Axis ) = indices (A. val)
67
+ Base. indices (A:: Axis , d) = indices (A. val, d)
64
68
Base. length (A:: Axis ) = length (A. val)
65
69
@compat (A:: Axis{name} ){name}(i) = Axis {name} (i)
70
+ Base. convert {name,T} (:: Type{Axis{name,T}} , ax:: Axis{name,T} ) = ax
71
+ Base. convert {name,T} (:: Type{Axis{name,T}} , ax:: Axis{name} ) = Axis {name} (convert (T, ax. val))
66
72
67
73
@doc """
68
74
An AxisArray is an AbstractArray that wraps another AbstractArray and
@@ -95,11 +101,12 @@ AxisArray(A::AbstractArray, vectors::AbstractVector...)
95
101
* `A::AbstractArray` : the wrapped array data
96
102
* `axes` or `names` or `vectors` : dimensional information for the wrapped array
97
103
98
- The dimensional information may be passed in one of three ways and is entirely
99
- optional. When the axis name or value is missing for a dimension, a default is
100
- substituted. The default axis names for dimensions `(1, 2, 3, 4, 5, ...)` are
101
- `(:row, :col, :page, :dim_4, :dim_5, ...)`. The default axis values are the
102
- integer unit ranges: `1:size(A, d)` for each missing dimension `d`.
104
+ The dimensional information may be passed in one of three ways and is
105
+ entirely optional. When the axis name or value is missing for a
106
+ dimension, a default is substituted. The default axis names for
107
+ dimensions `(1, 2, 3, 4, 5, ...)` are `(:row, :col, :page, :dim_4,
108
+ :dim_5, ...)`. The default axis values are `indices(A, d)` for each
109
+ missing dimension `d`.
103
110
104
111
### Indexing
105
112
@@ -166,12 +173,12 @@ AxisArray(A::AbstractArray, axs::Axis...) = AxisArray(A, axs)
166
173
push! (ax. args, :(axs[$ i]))
167
174
end
168
175
for i= L+ 1 : N
169
- push! (ax. args, :(Axis {_defaultdimname($i)} (1 : size (A, $ i))))
176
+ push! (ax. args, :(Axis {_defaultdimname($i)} (indices (A, $ i))))
170
177
end
171
178
quote
172
179
for i = 1 : length (axs)
173
180
checkaxis (axs[i]. val)
174
- if length (axs[i]. val) != size (A, i)
181
+ if _length (axs[i]. val) != _size (A, i)
175
182
throw (ArgumentError (" the length of each axis must match the corresponding size of data" ))
176
183
end
177
184
end
@@ -183,7 +190,7 @@ AxisArray(A::AbstractArray, axs::Axis...) = AxisArray(A, axs)
183
190
end
184
191
# Simple non-type-stable constructors to specify just the name or axis values
185
192
AxisArray (A:: AbstractArray ) = AxisArray (A, ()) # Disambiguation
186
- AxisArray (A:: AbstractArray , names:: Symbol... ) = AxisArray (A, ntuple (i -> Axis {names[i]} ( 1 : size (A, i)), length (names )))
193
+ AxisArray (A:: AbstractArray , names:: Symbol... ) = AxisArray (A, map ((name,ind) -> Axis {name} (ind), names, indices (A )))
187
194
AxisArray (A:: AbstractArray , vects:: AbstractVector... ) = AxisArray (A, ntuple (i-> Axis {_defaultdimname(i)} (vects[i]), length (vects)))
188
195
189
196
# Axis definitions
@@ -214,47 +221,103 @@ end
214
221
Base. size (A:: AxisArray ) = size (A. data)
215
222
Base. size (A:: AxisArray , Ax:: Axis ) = size (A. data, axisdim (A, Ax))
216
223
Base. size {Ax<:Axis} (A:: AxisArray , :: Type{Ax} ) = size (A. data, axisdim (A, Ax))
224
+ Base. indices (A:: AxisArray ) = indices (A. data)
225
+ Base. indices (A:: AxisArray , Ax:: Axis ) = indices (A. data, axisdim (A, Ax))
226
+ Base. indices {Ax<:Axis} (A:: AxisArray , :: Type{Ax} ) = indices (A. data, axisdim (A, Ax))
217
227
Base. linearindexing (A:: AxisArray ) = Base. linearindexing (A. data)
218
228
Base. convert {T,N} (:: Type{Array{T,N}} , A:: AxisArray{T,N} ) = convert (Array{T,N}, A. data)
219
229
# Similar is tricky. If we're just changing the element type, it can stay as an
220
230
# AxisArray. But if we're changing dimensions, there's no way it can know how
221
231
# to keep track of the axes, so just punt and return a regular old Array.
222
232
# TODO : would it feel more consistent to return an AxisArray without any axes?
223
- Base. similar {T} (A:: AxisArray{T} ) = (d = similar (A. data, T); AxisArray (d, A. axes))
224
- Base. similar {T} (A:: AxisArray{T} , S:: Type ) = (d = similar (A. data, S); AxisArray (d, A. axes))
225
- Base. similar {T} (A:: AxisArray{T} , S:: Type , :: Tuple{} ) = (d = similar (A. data, S); AxisArray (d, A. axes))
226
- Base. similar {T} (A:: AxisArray{T} , dims:: Int ) = similar (A, T, (dims,))
227
- Base. similar {T} (A:: AxisArray{T} , dims:: Int... ) = similar (A, T, dims)
228
- Base. similar {T} (A:: AxisArray{T} , dims:: Tuple{Vararg{Int}} ) = similar (A, T, dims)
229
- Base. similar {T} (A:: AxisArray{T} , S:: Type , dims:: Int... ) = similar (A. data, S, dims)
230
- Base. similar {T} (A:: AxisArray{T} , S:: Type , dims:: Tuple{Vararg{Int}} ) = similar (A. data, S, dims)
233
+ Base. similar {S} (A:: AxisArray , :: Type{S} ) = (d = similar (A. data, S); AxisArray (d, A. axes))
234
+ Base. similar {S,N} (A:: AxisArray , :: Type{S} , dims:: Dims{N} ) = similar (A. data, S, dims)
231
235
# If, however, we pass Axis objects containing the new axis for that dimension,
232
236
# we can return a similar AxisArray with an appropriately modified size
233
- Base. similar {T} (A:: AxisArray{T} , axs:: Axis... ) = similar (A, T, axs)
234
- Base. similar {T } (A:: AxisArray{T } , S :: Type , axs:: Axis... ) = similar (A, S, axs)
235
- @generated function Base. similar {T,N} (A:: AxisArray{T,N} , S :: Type , axs:: Tuple{Vararg{Axis}} )
236
- sz = Expr (:tuple )
237
+ Base. similar {T} (A:: AxisArray{T} , ax1 :: Axis , axs:: Axis... ) = similar (A, T, (ax1, axs... ) )
238
+ Base. similar {S } (A:: AxisArray , :: Type{S } , ax1 :: Axis , axs:: Axis... ) = similar (A, S, (ax1, axs... ) )
239
+ @generated function Base. similar {T,S, N} (A:: AxisArray{T,N} , :: Type{S} , axs:: Tuple{Axis, Vararg{Axis}} )
240
+ inds = Expr (:tuple )
237
241
ax = Expr (:tuple )
238
242
for d= 1 : N
239
- push! (sz . args, :(size (A, Axis{$ d})))
243
+ push! (inds . args, :(indices (A, Axis{$ d})))
240
244
push! (ax. args, :(axes (A, Axis{$ d})))
241
245
end
242
246
to_delete = Int[]
243
247
for i= 1 : length (axs. parameters)
244
248
a = axs. parameters[i]
245
249
d = axisdim (A, a)
246
250
axistype (a) <: Tuple{} && push! (to_delete, d)
247
- sz . args[d] = :(length (axs[$ i]. val))
251
+ inds . args[d] = :(indices (axs[$ i]. val, 1 ))
248
252
ax. args[d] = :(axs[$ i])
249
253
end
250
254
sort! (to_delete)
251
- deleteat! (sz . args, to_delete)
255
+ deleteat! (inds . args, to_delete)
252
256
deleteat! (ax. args, to_delete)
253
257
quote
254
- d = similar (A. data, S, $ sz )
258
+ d = similar (A. data, S, $ inds )
255
259
AxisArray (d, $ ax)
256
260
end
257
261
end
262
+
263
+ function Base. permutedims (A:: AxisArray , perm)
264
+ p = permutation (perm, axisnames (A))
265
+ AxisArray (permutedims (A. data, p), axes (A)[[p... ]])
266
+ end
267
+ permutation (to:: Union{AbstractVector{Int},Tuple{Int,Vararg{Int}}} , from:: Symbols ) = to
268
+
269
+ """
270
+ permutation(to, from) -> p
271
+
272
+ Calculate the permutation of labels in `from` to produce the order in
273
+ `to`. Any entries in `to` that are missing in `from` will receive an
274
+ index of 0. Any entries in `from` that are missing in `to` will have
275
+ their indices appended to the end of the permutation. Consequently,
276
+ the length of `p` is equal to the longer of `to` and `from`.
277
+ """
278
+ function permutation (to:: Symbols , from:: Symbols )
279
+ n = length (to)
280
+ nf = length (from)
281
+ li = linearindices (from)
282
+ d = Dict (from[i]=> i for i in li)
283
+ covered = similar (dims-> falses (length (li)), li)
284
+ ind = Array (Int, max (n, nf))
285
+ for (i,toi) in enumerate (to)
286
+ j = get (d, toi, 0 )
287
+ ind[i] = j
288
+ if j != 0
289
+ covered[j] = true
290
+ end
291
+ end
292
+ k = n
293
+ for i in li
294
+ if ! covered[i]
295
+ d[from[i]] != i && throw (ArgumentError (" $(from[i]) is a duplicated argument" ))
296
+ k += 1
297
+ k > nf && throw (ArgumentError (" no incomplete containment allowed in $to and $from " ))
298
+ ind[k] = i
299
+ end
300
+ end
301
+ ind
302
+ end
303
+
304
+ function Base. squeeze (A:: AxisArray , dims:: Dims )
305
+ keepdims = setdiff (1 : ndims (A), dims)
306
+ AxisArray (squeeze (A. data, dims), axes (A)[keepdims])
307
+ end
308
+ # This version is type-stable
309
+ function Base. squeeze {Ax<:Axis} (A:: AxisArray , :: Type{Ax} )
310
+ dim = axisdim (A, Ax)
311
+ AxisArray (squeeze (A. data, dim), dropax (Ax, axes (A)... ))
312
+ end
313
+
314
+ @inline dropax (ax, ax1, axs... ) = (ax1, dropax (ax, axs... )... )
315
+ @inline dropax {name} (ax:: Axis{name} , ax1:: Axis{name} , axs... ) = dropax (ax, axs... )
316
+ @inline dropax {name} (ax:: Type{Axis{name}} , ax1:: Axis{name} , axs... ) = dropax (ax, axs... )
317
+ @inline dropax {name,T} (ax:: Type{Axis{name,T}} , ax1:: Axis{name} , axs... ) = dropax (ax, axs... )
318
+ dropax (ax) = ()
319
+
320
+
258
321
# A simple display method to include axis information. It might be nice to
259
322
# eventually display the axis labels alongside the data array, but that is
260
323
# much more difficult.
@@ -356,3 +419,10 @@ function checkaxis(::Type{Categorical}, ax)
356
419
push! (seen, elt)
357
420
end
358
421
end
422
+
423
+ _length (A:: AbstractArray ) = length (linearindices (A))
424
+ _length (A) = length (A)
425
+ _size (A:: AbstractArray ) = map (length, indices (A))
426
+ _size (A) = size (A)
427
+ _size (A:: AbstractArray , d) = length (indices (A, d))
428
+ _size (A, d) = size (A, d)
0 commit comments