From 151e10275084ca87d82f38e0cce3db72be1f8658 Mon Sep 17 00:00:00 2001 From: Norman <37969042+junyuan-chen@users.noreply.github.com> Date: Sun, 27 Nov 2022 20:51:00 -0800 Subject: [PATCH] Improve `LabeledArray` and documentation (#11) --- docs/src/man/date-and-time-values.md | 2 +- docs/src/man/getting-started.md | 46 ++-- docs/src/man/metadata.md | 2 +- docs/src/man/table-interface.md | 4 +- docs/src/man/value-labels.md | 56 +++-- src/LabeledArrays.jl | 320 +++++++++++++++++++-------- src/ReadStatTables.jl | 9 +- src/datetime.jl | 2 +- src/parser.jl | 26 +-- src/table.jl | 54 +++-- test/LabeledArrays.jl | 189 ++++++++++++---- test/readstat.jl | 16 +- test/table.jl | 8 +- 13 files changed, 517 insertions(+), 217 deletions(-) diff --git a/docs/src/man/date-and-time-values.md b/docs/src/man/date-and-time-values.md index f20771f..5905b00 100644 --- a/docs/src/man/date-and-time-values.md +++ b/docs/src/man/date-and-time-values.md @@ -18,7 +18,7 @@ The full lists of recognized date/time formats for the statistical software are stored as dictionary keys; while the associated values are tuples of reference date/time and period length.[^2] If a variable is in a date/time format that is contained in the dictionary keys, -[`readstat`](@ref) will handle the conversion into a Julia time type +[`readstat`](@ref) will handle the conversion to a Julia time type (unless the `convert_datetime` option prevents it). Otherwise, if a date/time format is not found in the dictionary keys, no type conversion will be attempted. diff --git a/docs/src/man/getting-started.md b/docs/src/man/getting-started.md index a9520dc..d8e1779 100644 --- a/docs/src/man/getting-started.md +++ b/docs/src/man/getting-started.md @@ -95,55 +95,55 @@ Variables with value labels are stored in [`LabeledArray`](@ref)s. To convert a `LabeledArray` to another array type, we may either obtain an array of [`LabeledValue`](@ref)s or collect the values and labels separately. -If only the labels contain the relevant information, -we can make use of the `labels` function which returns an iterator for the labels. -For example, to convert a `LabeledArray` to a `CategoricalArray` from -[CategoricalArrays.jl](https://github.com/JuliaData/CategoricalArrays.jl): +The data values can be directly retrieved by calling [`refarray`](@ref): ```@repl getting-started -using CategoricalArrays -CategoricalArray(labels(tb.mylabl)) +refarray(tb.mylabl) ``` -Sometimes, the values have special meanings while the labels are not so important. -To access the array of values underlying a `LabeledArray` directly: +!!! note -```@repl getting-started -refarray(tb.mylabl) -``` + The array returned by `refarray` + is exactly the same array underlying the `LabeledArray`. + Therefore, modifying the elements of the array + will also mutate the values in the associated `LabeledArray`. -Alternatively, convert a `LabeledArray` to an array with appropriate element type: +If only the value labels are needed, +we can obtain an iterator of the value labels via [`valuelabels`](@ref). +For example, to convert a `LabeledArray` to a `CategoricalArray` from +[CategoricalArrays.jl](https://github.com/JuliaData/CategoricalArrays.jl): ```@repl getting-started -convert(Vector{Int}, tb.mylabl) +using CategoricalArrays +CategoricalArray(valuelabels(tb.mylabl)) ``` -In the last example, the element type of the output array has become `Int` -while the labels are ignored. +It is also possible to only convert the type of the underlying data values: -!!! note +```@repl getting-started +convertvalue(Int32, tb.mylabl) +``` - The array returned by `refarray` (and by `convert` if element type is not converted) - is exactly the same array underlying the `LabeledArray`. - Therefore, modifying the elements of the array - will also mutate the values in the associated `LabeledArray`. +```@docs +convertvalue +``` ## More Options -The behavior of `readstat` can be adjusted by passing keyword arguments. +The behavior of `readstat` can be adjusted by passing keyword arguments: ```@docs readstat ``` -The accepted values for selecting certain variables (columns) are shown below: +The accepted types of values for selecting certain variables (data columns) are shown below: ```@docs ReadStatTables.ColumnIndex ReadStatTables.ColumnSelector ``` -File-level metadata can be obtained without reading the entire data file. +File-level metadata can be obtained without reading the entire data file: ```@docs readstatmeta diff --git a/docs/src/man/metadata.md b/docs/src/man/metadata.md index 0f7b9fe..d43d819 100644 --- a/docs/src/man/metadata.md +++ b/docs/src/man/metadata.md @@ -100,7 +100,7 @@ m["label"] copy(m) ``` -However, it can not be modified directly via `setindex!`: +However, it cannot be modified directly via `setindex!`: ```@repl meta m["label"] = "A new label" diff --git a/docs/src/man/table-interface.md b/docs/src/man/table-interface.md index ce575f6..b0e6804 100644 --- a/docs/src/man/table-interface.md +++ b/docs/src/man/table-interface.md @@ -1,6 +1,6 @@ # Table Interface -This page provides further details on the interface of `ReadStatTable`. +This page provides further details on the interface of [`ReadStatTable`](@ref). ```@docs ReadStatTable @@ -59,7 +59,7 @@ end ## Data Values In addition to retrieving the data columns, -it is possible to directly retrieving and modifying individual data values +it is possible to directly retrieve and modify individual data values via `getindex` and `setindex!`. ```@repl table diff --git a/docs/src/man/value-labels.md b/docs/src/man/value-labels.md index 946c8eb..4a3a9a8 100644 --- a/docs/src/man/value-labels.md +++ b/docs/src/man/value-labels.md @@ -1,47 +1,67 @@ # Value Labels -Value labels from the data files are incorporated into the data columns -via a customized array type `LabeledArray`. +Value labels collected from the data files are incorporated into the associated data columns +via a customized array type [`LabeledArray`](@ref). ## LabeledValue and LabeledArray `LabeledValue` and `LabeledArray` are designed to imitate how variables associated with value labels are represented in the original data files from the statistical software. +The former wraps a data array with a reference to the value labels; +while the latter wraps a single data value. The element of a `LabeledArray` is always a `LabeledValue`. +However, a `LabeledValue` obtained from a `LabeledArray` +is only constructed when being retrieved via `getindex` for efficient storage. -In general, variables associated with value labels -should not be treated as categorical data. -Here are some noteworthy distinctions of `LabeledArray` from -an array type designed for categorical data (e.g., `CategoricalArray`): +Some noteworthy distinctions of a `LabeledArray` are highlighted below: -- Values are never recoded when a `LabeledArray` is constructed.[^1] -- It is allowed for some values in a `LabeledArray` to not have a label.[^2] +- Values are never re-encoded when a `LabeledArray` is constructed.[^1] +- It is allowed for some values in a `LabeledArray` to not have a value label.[^2] - A label is always a `String` even when it is associated with `missing`. +In essence, a `LabeledArray` is simply an array of data values (typically numbers) +bundled with a dictionary of value labels. +There is no restriction imposed on the correspondence +between the data values and value labels. +Namely, a data value in a `LabeledArray` is not necessarily attached with a value label +from the associated dictionary; +while the key of a value label contained in the dictionary +may not match any array element. +Furthermore, the dictionary of value labels may be switched and shared +across different `LabeledArray`s. +When setting values in a `LabeledArray`, +the array of data values are modified directly +with no additional check on the associated dictionary of value labels. +For this reason, the functionality of a `LabeledArray` +is not equivalent to that of an array type designed for categorical data +(e.g., `CategoricalArray` from +[CategoricalArrays.jl](https://github.com/JuliaData/CategoricalArrays.jl)). +They are not complete substitutes for each other. + More details are below. ```@docs LabeledValue LabeledArray LabeledVector +LabeledMatrix ``` -## Accessing Labels and Values +## Accessing Values and Labels -If only the labels of a `LabeledArray` are needed, -an iterator that maintains the shape of the `LabeledArray` -can be obtained by calling `labels`. -The iterator can be used for either collecting all labels in a different array type -or retrieving labels for specific values. -On the other hand, if only the values are needed, -the labels can be ignored -if one directly accesses the underlying array that holds the values. +For `LabeledValue`, the underlying data value can be retrieved via [`unwrap`](@ref). +The value label can be obtained via [`valuelabel`](@ref) or conversion to `String`. +For `LabeledArray`, the underlying data values can be retrieved via [`refarray`](@ref). +An iterator of value labels that maintains the shape of the `LabeledArray` +can be obtained by calling [`valuelabels`](@ref). ```@docs -labels unwrap +valuelabel +getvaluelabels refarray +valuelabels ``` [^1]: diff --git a/src/LabeledArrays.jl b/src/LabeledArrays.jl index be4cbae..2a5f35c 100644 --- a/src/LabeledArrays.jl +++ b/src/LabeledArrays.jl @@ -1,23 +1,24 @@ """ - LabeledValue{T} + LabeledValue{T, K} -Value of type `T` that is associated with a dictionary of value labels. -If a value `v` is not a key in the dictionary, -then `string(v)` is taken as the label. +Value of type `T` associated with a dictionary of value labels with keys of type `K`. +If a value `v` is not euqal (`==`) to a key in the dictionary, +then `string(v)` is taken as the value label. See also [`LabeledArray`](@ref). The value underlying a `LabeledValue` can be accessed via [`unwrap`](@ref). -The label can be obtained by converting `LabeledValue` to `String` -or calling [`labels`](@ref) (notice the `s` at the end). +The value label can be obtained by calling [`valuelabel`](@ref) +or converting a `LabeledValue` to `String` via `convert`. +The dictionary of value labels (typically assoicated with a data column) +can be accessed via [`getvaluelabels`](@ref). Comparison operators `==`, `isequal`, `<`, `isless` and `isapprox` -compare the underlying value of type `T`. -An exception is that when a `LabeledValue` and a string are compared with `==`, -the comparison is based on the label. +compare the underlying value of type `T` and disregard any value label. +To compare the value label, use [`valuelabel`](@ref) to retrieve the label first. # Examples ```jldoctest -julia> lbls = Dict{Union{Int,Missing},String}(0=>"a", 1=>"a"); +julia> lbls = Dict{Int,String}(0=>"a", 1=>"a"); julia> v0 = LabeledValue(0, lbls) 0 => a @@ -34,26 +35,31 @@ false julia> v1 == 1 true -julia> v1 == "a" +julia> isequal(vm, missing) true -julia> isequal(vm, missing) +julia> unwrap(v0) +0 + +julia> valuelabel(v1) == "a" +true + +julia> getvaluelabels(v1) === lbls true ``` """ -struct LabeledValue{T} +struct LabeledValue{T, K} value::T - labels::Dict{Any, String} + labels::Dict{K, String} end # A value is not guaranteed to have a label defined in labels function _getlabel(x::LabeledValue) lbl = get(x.labels, x.value, nothing) - lbl === nothing && return string(x.value) - return lbl + return lbl === nothing ? string(x.value) : lbl end -# Comparison between LabeledValues ignores labels +# Comparisons involving LabeledValues always disregard labels Base.:(==)(x::LabeledValue, y::LabeledValue) = x.value == y.value Base.isequal(x::LabeledValue, y::LabeledValue) = isequal(x.value, y.value) Base.:(<)(x::LabeledValue, y::LabeledValue) = x.value < y.value @@ -61,12 +67,8 @@ Base.isless(x::LabeledValue, y::LabeledValue) = isless(x.value, y.value) Base.isapprox(x::LabeledValue, y::LabeledValue; kwargs...) = isapprox(x.value, y.value; kwargs...) -# Comparison with String is interpreted as comparing labels -# Comparison with any other type is interpreted as comparing values Base.:(==)(x::LabeledValue, y) = x.value == y Base.:(==)(x, y::LabeledValue) = x == y.value -Base.:(==)(x::LabeledValue, y::AbstractString) = _getlabel(x) == y -Base.:(==)(x::AbstractString, y::LabeledValue) = x == _getlabel(y) Base.:(==)(::LabeledValue, ::Missing) = missing Base.:(==)(::Missing, ::LabeledValue) = missing @@ -90,48 +92,65 @@ Base.hash(x::LabeledValue, h::UInt=zero(UInt)) = hash(x.value, h) """ unwrap(x::LabeledValue) -Get the value wrapped by `x`. +Return the value underlying the value label of `x`. """ unwrap(x::LabeledValue) = x.value """ - labels(x::LabeledValue) + valuelabel(x::LabeledValue) + +Return the value label associated with `x`. +""" +valuelabel(x::LabeledValue) = _getlabel(x) + +""" + getvaluelabels(x::LabeledValue) -Return the label associated with `x`. +Return the dictionary of value labels (typically assoicated with a data column) +attached to `x`. """ -labels(x::LabeledValue) = _getlabel(x) +getvaluelabels(x::LabeledValue) = x.labels Base.show(io::IO, x::LabeledValue) = print(io, _getlabel(x)) Base.show(io::IO, ::MIME"text/plain", x::LabeledValue) = print(io, x.value, " => ", _getlabel(x)) -Base.convert(::Type{String}, x::LabeledValue) = _getlabel(x) +Base.convert(::Type{<:LabeledValue{T1}}, x::LabeledValue{T2}) where {T1,T2} = + LabeledValue(convert(T1, x.value), x.labels) +Base.convert(::Type{T}, x::LabeledValue) where T<:AbstractString = convert(T, _getlabel(x)) """ - LabeledArray{V, N, T<:LabeledValue} <: AbstractArray{T, N} + LabeledArray{V, N, A<:AbstractArray{V, N}, K} <: AbstractArray{LabeledValue{V, K}, N} -`N`-dimensional dense array with elements associated with labels. +`N`-dimensional dense array with elements associated with value labels. `LabeledArray` provides functionality that is similar to what value labels achieve in statistical software such as Stata. -When printed to REPL, a `LabeledArray` just looks like an array of labels. -Yet, only the underlying values of type `V` are stored in an `Array`. -The associated labels are looked up -from a dictionary of type `Dict{V, String}`. -If a value `v` is not a key in the dictionary, -then `string(v)` is taken as the label. -The elements of type [`LabeledValue`](@ref) -are only constructed lazily when retrieved. +When printed to REPL, a `LabeledArray` just looks like an array of value labels. +Yet, only the underlying values of type `V` are stored in an array of type `A`. +The associated value labels are looked up +from a dictionary of type `Dict{K, String}`. +If a value `v` is not equal (`==`) to a key in the dictionary, +then `string(v)` is taken as the value label. +The elements of type [`LabeledValue{V, K}`](@ref) +are only constructed lazily when they are retrieved. The array of values underlying a `LabeledArray` -can be accessed with [`refarray`](@ref); -while an iterator over the labels for each element -is returned by [`labels`](@ref). - -Equality comparison as defined by `==` involving a `LabeledArray` -only compares the underlying values -unless the element type of the other array is a subtype of `AbstractString`. -The labels are used for comparison in the latter case. +can be accessed via [`refarray`](@ref). +The dictionary of value labels assoicated with a `LabeledArray` +can be accessed via [`getvaluelabels`](@ref). +An iterator over the value labels for each element, +which has the same array shape as the `LabeledArray`, +can be obtained via [`valuelabels`](@ref). + +Equality comparison (`==`) involving a `LabeledArray` +only compares the underlying values and disregard any value label. +To compare the value labels, use [`valuelabels`](@ref) to obtain the labels first. + +Additional array methods such as `push!`, `insert!`, `deleteat!`, `append!` +are supported for [`LabeledVector`](@ref). +They are applied on the underlying array of values retrieved via [`refarray`](@ref) +and do not modify the dictionary of value labels. # Examples ```jldoctest @@ -140,13 +159,13 @@ julia> lbls1 = Dict(1=>"a", 2=>"b"); julia> lbls2 = Dict(1.0=>"p", 2.0=>"q"); julia> x = LabeledArray([0, 1, 2], lbls1) -3-element LabeledVector{Int64, LabeledValue{Int64}}: +3-element LabeledVector{Int64, Vector{Int64}, Int64}: 0 => 0 1 => a 2 => b julia> y = LabeledArray([0.0, 1.0, 2.0], lbls2) -3-element LabeledVector{Float64, LabeledValue{Float64}}: +3-element LabeledVector{Float64, Vector{Float64}, Float64}: 0.0 => 0.0 1.0 => p 2.0 => q @@ -157,40 +176,86 @@ true julia> x == 0:2 true -julia> x == ["0", "a", "b"] +julia> refarray(x) +3-element Vector{Int64}: + 0 + 1 + 2 + +julia> getvaluelabels(x) +Dict{Int64, String} with 2 entries: + 2 => "b" + 1 => "a" + +julia> valuelabels(x) == ["0", "a", "b"] true + +julia> push!(x, 2) +4-element LabeledVector{Int64, Vector{Int64}, Int64}: + 0 => 0 + 1 => a + 2 => b + 2 => b + +julia> deleteat!(x, 4) +3-element LabeledVector{Int64, Vector{Int64}, Int64}: + 0 => 0 + 1 => a + 2 => b + +julia> append!(x, [0, 1, 2]) +6-element LabeledVector{Int64, Vector{Int64}, Int64}: + 0 => 0 + 1 => a + 2 => b + 0 => 0 + 1 => a + 2 => b ``` """ -struct LabeledArray{V, N, T<:LabeledValue, A} <: AbstractArray{T, N} +struct LabeledArray{V, N, A<:AbstractArray{V, N}, K} <: AbstractArray{LabeledValue{V, K}, N} values::A - labels::Dict{Any, String} - function LabeledArray(values::AbstractArray{V,N}, - labels::Dict{Any,String}) where {V,N} - V <: AbstractString && throw(ArgumentError("values of type $V are not accepted")) - return new{V,N,LabeledValue{V},typeof(values)}(values, labels) - end + labels::Dict{K, String} + LabeledArray(values::AbstractArray{V, N}, labels::Dict{K, String}) where {V, N, K} = + new{V, N, typeof(values), K}(values, labels) end """ - LabeledVector{V, T, A} + LabeledVector{V, A, K} <: AbstractVector{LabeledValue{V, K}} -Alias for [`LabeledArray{V, 1, T, A}`](@ref). +Alias for [`LabeledArray{V, 1, A, K}`](@ref). """ -const LabeledVector{V, T, A} = LabeledArray{V, 1, T, A} +const LabeledVector{V, A, K} = LabeledArray{V, 1, A, K} -Base.size(x::LabeledArray) = size(x.values) -Base.IndexStyle(::Type{<:LabeledArray}) = IndexLinear() +""" + LabeledMatrix{V, A, K} <: AbstractMatrix{LabeledValue{V, K}} + +Alias for [`LabeledArray{V, 2, A, K}`](@ref). +""" +const LabeledMatrix{V, A, K} = LabeledArray{V, 2, A, K} + +const LabeledArrOrSubOrReshape{V, N} = Union{LabeledArray{V, N}, + SubArray{<:Any, N, <:LabeledArray{V}}, Base.ReshapedArray{<:Any, N, <:LabeledArray{V}}, + SubArray{<:Any, N, <:Base.ReshapedArray{<:Any, <:Any, <:LabeledArray{V}}}} + +Base.size(x::LabeledArray) = size(refarray(x)) +Base.IndexStyle(::Type{<:LabeledArray{V,N,A}}) where {V,N,A} = IndexStyle(A) Base.@propagate_inbounds function Base.getindex(x::LabeledArray, i::Int) - val = x.values[i] - return LabeledValue(val, x.labels) + val = refarray(x)[i] + return LabeledValue(val, getvaluelabels(x)) +end + +Base.@propagate_inbounds function Base.setindex!(x::LabeledArray, v, i::Int) + refarray(x)[i] = unwrap(v) + return x end """ refarry(x::LabeledArray) refarray(x::SubArray{<:Any, <:Any, <:LabeledArray}) refarray(x::Base.ReshapedArray{<:Any, <:Any, <:LabeledArray}) - refarray(x::AbstractArray{<:LabeledValue}) + refarray(x::SubArray{<:Any, <:Any, <:Base.ReshapedArray{<:Any, <:Any, <:LabeledArray}}) Return the array of values underlying a [`LabeledArray`](@ref). """ @@ -199,7 +264,103 @@ refarray(x::SubArray{<:Any, <:Any, <:LabeledArray}) = view(parent(x).values, x.indices...) refarray(x::Base.ReshapedArray{<:Any, <:Any, <:LabeledArray}) = reshape(parent(x).values, size(x)) -refarray(x::AbstractArray{<:LabeledValue}) = collect(v.value for v in x) +refarray(x::SubArray{<:Any, <:Any, <:Base.ReshapedArray{<:Any, <:Any, <:LabeledArray}}) = + view(reshape(parent(parent(x)).values, size(parent(x))), x.indices...) + +""" + getvaluelabels(x::LabeledArray) + getvaluelabels(x::SubArray{<:Any, <:Any, <:LabeledArray}) + getvaluelabels(x::Base.ReshapedArray{<:Any, <:Any, <:LabeledArray}) + getvaluelabels(x::SubArray{<:Any, <:Any, <:Base.ReshapedArray{<:Any, <:Any, <:LabeledArray}}) + +Return the dictionary of value labels attached to `x`. +""" +getvaluelabels(x::LabeledArray) = x.labels +getvaluelabels(x::SubArray{<:Any, <:Any, <:LabeledArray}) = parent(x).labels +getvaluelabels(x::Base.ReshapedArray{<:Any, <:Any, <:LabeledArray}) = parent(x).labels +getvaluelabels(x::SubArray{<:Any, <:Any, + <:Base.ReshapedArray{<:Any, <:Any, <:LabeledArray}}) = parent(parent(x)).labels + +Base.@propagate_inbounds function Base.getindex(x::LabeledArrOrSubOrReshape, i::Integer) + val = refarray(x)[i] + return LabeledValue(val, getvaluelabels(x)) +end + +Base.@propagate_inbounds function Base.getindex(x::LabeledArrOrSubOrReshape, i) + val = refarray(x)[i] + return LabeledArray(val, getvaluelabels(x)) +end + +Base.@propagate_inbounds function Base.getindex(x::LabeledArrOrSubOrReshape{V,N}, + I::Vararg{Int,N}) where {V,N} + val = refarray(x)[I...] + return LabeledValue(val, getvaluelabels(x)) +end + +Base.@propagate_inbounds function Base.getindex(x::LabeledArrOrSubOrReshape{V,N}, + I::Vararg{<:Integer,N}) where {V,N} + val = refarray(x)[I...] + return LabeledValue(val, getvaluelabels(x)) +end + +Base.@propagate_inbounds function Base.getindex(x::LabeledArrOrSubOrReshape{V,N}, + I::Vararg{Any,N}) where {V,N} + val = refarray(x)[I...] + return LabeledArray(val, getvaluelabels(x)) +end + +Base.fill!(x::LabeledArrOrSubOrReshape, v) = (fill!(refarray(x), unwrap(v)); x) + +Base.resize!(x::LabeledVector, n::Integer) = (resize!(refarray(x), n); x) +Base.push!(x::LabeledVector, v) = (push!(refarray(x), unwrap(v)); x) +Base.pushfirst!(x::LabeledVector, v) = (pushfirst!(refarray(x), unwrap(v)); x) +Base.insert!(x::LabeledVector, i, v) = (insert!(refarray(x), i, unwrap(v)); x) +Base.deleteat!(x::LabeledVector, i) = (deleteat!(refarray(x), i); x) +Base.append!(x::LabeledVector, v) = (append!(refarray(x), refarray(v)); x) +Base.prepend!(x::LabeledVector, v) = (prepend!(refarray(x), refarray(v)); x) +Base.empty!(x::LabeledVector) = (empty!(refarray(x)); x) +Base.sizehint!(x::LabeledVector, n) = (sizehint!(refarray(x), n); x) + +Base.:(==)(x::LabeledArray, y::LabeledArray) = refarray(x) == refarray(y) +Base.:(==)(x::LabeledArray, y::AbstractArray) = refarray(x) == y +Base.:(==)(x::AbstractArray, y::LabeledArray) = x == refarray(y) + +# Value labels are not copied and may still be shared with other LabeledArrays +Base.copy(x::LabeledArray) = LabeledArray(copy(refarray(x)), getvaluelabels(x)) + +# Only convert the value type +Base.convert(::Type{<:AbstractArray{<:LabeledValue{T},N}}, + x::LabeledArray{V,N}) where {T,V,N} = + LabeledArray(convert(AbstractArray{T,N}, refarray(x)), getvaluelabels(x)) + +""" + convertvalue(T, x::LabeledArray) + +Convert the type of data values contained in `x` to `T`. +This method is equivalent to `convert(AbstractArray{LabeledValue{T, K}, N}}, x)`. +""" +convertvalue(::Type{T}, x::LabeledArray{V,N}) where {T,V,N} = + LabeledArray(convert(AbstractArray{T,N}, refarray(x)), getvaluelabels(x)) + +# Keep the same value labels +Base.similar(x::LabeledArrOrSubOrReshape) = + LabeledArray(similar(refarray(x)), getvaluelabels(x)) + +Base.similar(x::LabeledArrOrSubOrReshape, ::Type{<:LabeledValue{V}}) where V = + LabeledArray(similar(refarray(x), V), getvaluelabels(x)) + +Base.similar(x::LabeledArrOrSubOrReshape, dims::Dims) = + LabeledArray(similar(refarray(x), dims), getvaluelabels(x)) + +Base.similar(x::LabeledArrOrSubOrReshape, ::Type{<:LabeledValue{V}}, dims::Dims) where V = + LabeledArray(similar(refarray(x), V, dims), getvaluelabels(x)) + +# Assume VERSION >= v"1.3.0" +# Define abbreviated element type name for printing with PrettyTables.jl +function compact_type_str(::Type{<:LabeledValue{V}}) where V + str = V >: Missing ? string(nonmissingtype(V)) * "?" : string(V) + return replace("Labeled{" * str * "}", "Union" => "U") +end struct LabelIterator{A, N} <: AbstractArray{String, N} a::A @@ -207,23 +368,24 @@ struct LabelIterator{A, N} <: AbstractArray{String, N} end """ - labels(x::AbstractArray{<:LabeledValue}) + valuelabels(x::AbstractArray{<:LabeledValue}) -Return an iterator over the labels for each element in `x`. +Return an iterator over the value labels of all elements in `x`. The returned object is a subtype of `AbstractArray` with the same size of `x`. -The iterator can be used to collect the labels while discarding the underlying values. +The iterator can be used to collect value labels to arrays +while discarding the underlying values. # Examples ```jldoctest julia> x = LabeledArray([1, 2, 3], Dict(1=>"a", 2=>"b")) -3-element LabeledVector{Int64, LabeledValue{Int64}}: +3-element LabeledVector{Int64, Vector{Int64}, Int64}: 1 => a 2 => b 3 => 3 -julia> lbls = labels(x) -3-element ReadStatTables.LabelIterator{LabeledVector{Int64, LabeledValue{Int64}}, 1}: +julia> lbls = valuelabels(x) +3-element ReadStatTables.LabelIterator{LabeledVector{Int64, Vector{Int64}, Int64}, 1}: "a" "b" "3" @@ -241,7 +403,7 @@ julia> CategoricalArray(lbls) "3" ``` """ -labels(x::AbstractArray{<:LabeledValue}) = LabelIterator(x) +valuelabels(x::AbstractArray{<:LabeledValue}) = LabelIterator(x) Base.size(lbls::LabelIterator) = size(lbls.a) Base.IndexStyle(::Type{<:LabelIterator{A}}) where A = IndexStyle(A) @@ -255,21 +417,3 @@ end Base.@propagate_inbounds Base.getindex(lbls::LabelIterator{<:AbstractArray}, i::Int) = _getlabel(lbls.a[i]) - -Base.:(==)(x::LabeledArray, y::LabeledArray) = x.values == y.values -Base.:(==)(x::LabeledArray, y::AbstractArray) = x.values == y -Base.:(==)(x::AbstractArray, y::LabeledArray) = x == y.values -Base.:(==)(x::LabeledArray, y::AbstractArray{<:AbstractString}) = labels(x) == y -Base.:(==)(x::AbstractArray{<:AbstractString}, y::LabeledArray) = x == labels(y) - -Base.copy(x::LabeledArray) = - LabeledArray(copy(x.values), copy(x.labels)) - -Base.convert(::Type{Array{V1,N}}, x::LabeledArray{V2,N}) where {V1,V2,N} = - convert(Array{V1}, x.values) - -# Assume VERSION >= v"1.3.0" -function compact_type_str(::Type{LabeledValue{V}}) where V - str = V >: Missing ? string(nonmissingtype(V)) * "?" : string(V) - return replace("Labeled{" * str * "}", "Union" => "U") -end diff --git a/src/ReadStatTables.jl b/src/ReadStatTables.jl index 5dbe577..757549c 100644 --- a/src/ReadStatTables.jl +++ b/src/ReadStatTables.jl @@ -22,7 +22,11 @@ export columnnames export LabeledValue, LabeledArray, LabeledVector, - labels, + LabeledMatrix, + valuelabel, + getvaluelabels, + convertvalue, + valuelabels, ReadStatColumns, @@ -37,8 +41,7 @@ export LabeledValue, colmetavalues, readstat, - readstatmeta, - valuelabels + readstatmeta include("wrappers.jl") include("LabeledArrays.jl") diff --git a/src/datetime.jl b/src/datetime.jl index eb1ae77..a3caaa4 100644 --- a/src/datetime.jl +++ b/src/datetime.jl @@ -66,7 +66,7 @@ const dt_formats = Dict{String, Dict}( Construct a vector of time values of type `DateTime` or `Date` by interpreting the elements in `col` as the number of periods passed since `epoch` with the length of each period being `delta`. -Returned object is of a type acceptable by [`ReadStatColumns`](@ref). +Returned object is of a type acceptable by `ReadStatColumns`. """ function parse_datetime(col::AbstractVector, epoch::Union{DateTime,Date}, delta::Period, hasmissing::Bool) diff --git a/src/parser.jl b/src/parser.jl index 5763580..9045eae 100644 --- a/src/parser.jl +++ b/src/parser.jl @@ -156,25 +156,23 @@ function handle_value!(obs_index::Cint, variable::Ptr{Cvoid}, value::readstat_va return READSTAT_HANDLER_OK end -# Needed for getting value labels -# String variables do not have value labels -function getvalue(value::readstat_value_t) - type = value_type(value) - if Int(type) <= 3 - return int32_value(value) - else - return double_value(value) - end -end - function handle_value_label!(val_labels::Cstring, value::readstat_value_t, label::Cstring, ctx::ParserContext) tb = ctx.tb lblname = Symbol(_string(val_labels)) - lbls = get!(Dict{Any,String}, _vallabels(tb), lblname) + type = value_type(value) + # String variables do not have value labels + # All integers are Int32 and all floats are Float64 (ReadStat handles type conversion) # Tentatively save tagged missing values as Char - val = value_is_tagged_missing(value) ? value_tag(value) : getvalue(value) - lbls[val] = _string(label) + if Int(type) <= 3 + lbls = get!(Dict{Union{Int32,Char},String}, _vallabels(tb), lblname) + val = value_is_tagged_missing(value) ? value_tag(value) : int32_value(value) + lbls[val] = _string(label) + else + lbls = get!(Dict{Union{Float64,Char},String}, _vallabels(tb), lblname) + val = value_is_tagged_missing(value) ? value_tag(value) : double_value(value) + lbls[val] = _string(label) + end return READSTAT_HANDLER_OK end diff --git a/src/table.jl b/src/table.jl index 6fcf670..f689e71 100644 --- a/src/table.jl +++ b/src/table.jl @@ -21,11 +21,12 @@ Base.get(f::Base.Callable, m::AbstractMetaDict, key) = A collection of file-level metadata associated with a data file processed with `ReadStat`. -Metadata can be retrieved and modified either through -a dictionary-like interface or via methods compatible with `DataAPI.jl`. +Metadata can be retrieved and modified from the associated [`ReadStatTable`](@ref) +via methods compatible with `DataAPI.jl`. +A dictionary-like interface is also available for directly working with `ReadStatMeta`. # Fields -- `row_count::Int`: number of rows returned by `ReadStat` parser; being `-1` if not available in metadata; may reflect the value set with the `row_limit` parser option. +- `row_count::Int`: number of rows returned by `ReadStat` parser; being `-1` if not available in metadata; may reflect the value set with the `row_limit` parser option instead of the actual number of rows in the data file. - `var_count::Int`: number of data columns returned by `ReadStat` parser. - `creation_time::DateTime`: timestamp for file creation. - `modified_time::DateTime`: timestamp for file modification. @@ -86,15 +87,17 @@ end A collection of variable-level metadata associated with a data column processed with `ReadStat`. -Metadata can be retrieved and modified via methods compatible with `DataAPI.jl`. -A dictionary-like interface only allows retrieving the metadata. +Metadata can be retrieved and modified from the associated [`ReadStatTable`](@ref) +via methods compatible with `DataAPI.jl`. +A dictionary-like interface is also available for directly working with `ReadStatColMeta`, +but it does not allow modifying metadata values. An alternative way to retrive and modify the metadata is via [`colmetavalues`](@ref). # Fields - `label::String`: variable label. - `format::String`: variable format. - `type::readstat_type_t`: original variable type recognized by `ReadStat`. -- `vallabel::Symbol`: name of the set of value labels associated with the variable. +- `vallabel::Symbol`: name of the dictionary of value labels associated with the variable; see also [`getvaluelabels`](@ref) for the effect of modifying this field. - `storage_width::Csize_t`: variable storage width in data file. - `display_width::Cint`: width for display. - `measure::readstat_measure_t`: measure type of the variable; only relevant to SPSS. @@ -155,14 +158,18 @@ const ColMetaVec = StructVector{ReadStatColMeta, NamedTuple{(:label, :format, :t """ ReadStatTable <: Tables.AbstractColumns -A `Tables.jl`-compatible column table that collects data (including metadata) -for a Stata, SAS or SPSS file processed with `ReadStat`. +A `Tables.jl`-compatible column table that efficiently collects data +from a Stata, SAS or SPSS file processed with the `ReadStat` C library. +File-level and variable-level metadata can be retrieved and modified +via methods compatible with `DataAPI.jl`. + +See also [`ReadStatMeta`](@ref) and [`ReadStatColMeta`](@ref) for the included metadata. """ struct ReadStatTable <: Tables.AbstractColumns columns::ReadStatColumns names::Vector{Symbol} lookup::Dict{Symbol, Int} - vallabels::Dict{Symbol, Dict{Any,String}} + vallabels::Dict{Symbol, Dict} hasmissing::Vector{Bool} meta::ReadStatMeta colmeta::ColMetaVec @@ -171,7 +178,7 @@ struct ReadStatTable <: Tables.AbstractColumns columns = ReadStatColumns() names = Symbol[] lookup = Dict{Symbol, Int}() - vallabels = Dict{Symbol, Dict{Any,String}}() + vallabels = Dict{Symbol, Dict}() hasmissing = Vector{Bool}() meta = ReadStatMeta() colmeta = StructVector{ReadStatColMeta}((String[], String[], readstat_type_t[], @@ -180,7 +187,7 @@ struct ReadStatTable <: Tables.AbstractColumns return new(columns, names, lookup, vallabels, hasmissing, meta, colmeta, styles) end function ReadStatTable(columns::ReadStatColumns, names::Vector{Symbol}, - vallabels::Dict{Symbol, Dict{Any,String}}, hasmissing::Vector{Bool}, + vallabels::Dict{Symbol, Dict}, hasmissing::Vector{Bool}, meta::ReadStatMeta, colmeta::ColMetaVec, styles::Dict{Symbol, Symbol}=Dict{Symbol, Symbol}()) lookup = Dict{Symbol, Int}(n=>i for (i, n) in enumerate(names)) @@ -269,8 +276,8 @@ function _gettype(tb::ReadStatTable, i) T = eltype(Tables.getcolumn(tb, i)) if T === Union{Int8, Missing} _hasmissing(tb)[i] || return Int8 - elseif T === LabeledValue{Union{Missing, Int8}} - _hasmissing(tb)[i] || return LabeledValue{Int8} + elseif T <: LabeledValue && T.parameters[1] === Union{Int8, Missing} + _hasmissing(tb)[i] || return LabeledValue{Int8, T.parameters[2]} end return T end @@ -436,5 +443,22 @@ Return an array of metadata values associated with `key` for all columns in `tb` """ colmetavalues(tb::ReadStatTable, key) = _colmeta(tb, key) -valuelabels(tb::ReadStatTable) = _vallabels(tb) -valuelabels(tb::ReadStatTable, name::Symbol) = _vallabels(tb)[name] +""" + getvaluelabels(tb::ReadStatTable) + getvaluelabels(tb::ReadStatTable, name::Symbol) + +Return a dictionary of all value label dictionaries contained in `tb` +obtained from the data file. +Return a specific dictionary of value labels if a `name` is specified. + +Each dictionary of value labels is associated with a name +that may appear in the variable-level metadata under the key `vallabel` +for identifying the dictionary of value labels attached to each data column. +The same dictionary may be associated with multiple data columns. +Modifying the metadata value of `vallabel` for a data column +switches the associated value labels for the data column. +If the metadata value is set to `Symbol("")`, +the data column is not associated with any value label. +""" +getvaluelabels(tb::ReadStatTable) = _vallabels(tb) +getvaluelabels(tb::ReadStatTable, name::Symbol) = _vallabels(tb)[name] diff --git a/test/LabeledArrays.jl b/test/LabeledArrays.jl index 600d526..1c67b3c 100644 --- a/test/LabeledArrays.jl +++ b/test/LabeledArrays.jl @@ -1,5 +1,5 @@ @testset "LabeledValue" begin - lbls1 = Dict{Any,String}(1=>"a", 2=>"b") + lbls1 = Dict{Int32,String}(1=>"a", 2=>"b") lbls2 = Dict{Any,String}(1.0=>"b") v1 = LabeledValue(1, lbls1) v2 = LabeledValue(2, lbls1) @@ -16,8 +16,6 @@ @test v1 == 1 @test 1 == v1 - @test v1 == "a" - @test "a" == v1 @test ismissing(v1 == missing) @test ismissing(missing == v1) @test isequal(v1, 1) @@ -40,45 +38,175 @@ @test haskey(d, v3) @test unwrap(v1) === 1 - @test labels(v1) == "a" - @test labels(v4) == "missing" + @test valuelabel(v1) == "a" + @test valuelabel(v4) == "missing" + @test getvaluelabels(v1) === v1.labels @test sprint(show, v1) == "a" @test sprint(show, v4) == "missing" @test sprint(show, MIME("text/plain"), v1) == "1 => a" + v5 = convert(LabeledValue{Int16}, v1) + @test v5.value isa Int16 + @test v5.labels === v1.labels @test convert(String, v1) == "a" end @testset "LabeledArray" begin vals = repeat(1:3, inner=2) - lbls = Dict{Any,String}(i=>string(i) for i in 1:3) + lbls = Dict{Union{Int,Missing},String}(i=>string(i) for i in 1:3) x = LabeledArray(vals, lbls) - @test eltype(x) == LabeledValue{Int} + @test eltype(x) == LabeledValue{Int, Union{Int,Missing}} @test size(x) == (6,) - @test IndexStyle(typeof(x)) == IndexLinear() + @test IndexStyle(typeof(x)) == IndexStyle(typeof(vals)) @test x[1] === LabeledValue(1, lbls) - @test x[2:3] == [1, 2] - @test x[isodd.(1:6)] == [1, 2, 3] - @test_throws ArgumentError LabeledArray(string.(vals), Dict{Any,String}()) + @test x[Int16(1)] === LabeledValue(1, lbls) + s = x[2:3] + @test s == [1, 2] + @test s isa LabeledArray + @test s.labels === lbls + s = x[isodd.(1:6)] + @test s == [1, 2, 3] + @test s isa LabeledArray + @test s.labels === lbls + + x2 = LabeledArray([1:3 1:3], lbls) + ra = refarray(x2) + @test size(ra) == (3, 2) + @test ra isa Matrix{Int64} + + @test x2[1] === LabeledValue(1, lbls) + @test x2[1,1] === LabeledValue(1, lbls) + s = x2[2:3] + @test s == [2, 3] + @test s isa LabeledArray + @test s.labels === lbls + s = x2[isodd.(1:6)] + @test s == [1, 3, 2] + @test s isa LabeledArray + @test s.labels === lbls + s = x2[1,1:2] + @test s == [1, 1] + @test s isa LabeledArray + @test s.labels === lbls + s = x2[2,isodd.(1:2)] + @test s isa LabeledArray + @test s.labels === lbls + + @test view(x, 1:3)[Int16(1)] === LabeledValue(1, lbls) + @test reshape(x, 2, 3)[Int16(1)] === LabeledValue(1, lbls) + v = view(x, 1:3)[1:2] + @test v isa LabeledArray + @test v.labels === lbls + v = reshape(x, 3, 2)[1:2] + @test v isa LabeledArray + @test v.labels === lbls + v = view(x2, 1:3)[1:2] + @test v isa LabeledArray + @test v.labels === lbls + + @test view(x2, 1:2, 1:2)[Int16(1)] === LabeledValue(1, lbls) + @test reshape(x2, 2, 3)[Int16(1)] === LabeledValue(1, lbls) + v = view(x2, 1:2, 1:2)[1,1] + @test v isa LabeledValue + @test v.labels === lbls + v = view(x2, 1:2, 1:2)[Int16(1),Int16(1)] + @test v isa LabeledValue + @test v.labels === lbls + v = view(x2, 1:2, 1:2)[1,1:2] + @test v isa LabeledArray + @test v.labels === lbls + + x[1] = 2 + @test x[1] == 2 + x[1:2] .= 1 + @test all(x[1:2] .== 1) + x2[1,1:2] .= 2 + @test all(x2[1,1:2] .== 2) vals1 = [1, 2, missing] x1 = LabeledArray(vals1, lbls) @test isequal(x1[3], missing) - @test x1[3] == "missing" + @test isequal(x1[3], LabeledValue(missing, Dict{Any,String}())) @test refarray(x) === x.values @test refarray(view(x, [1, 3])) == 1:2 @test refarray(reshape(x, 3, 2)) == reshape(x.values, 3, 2) - @test collect(x) == x + @test refarray(view(x2, 1:3)) == x2.values[1:3] + x3 = collect(x) + @test x3 == x + @test x3 isa Array + @test eltype(x3) == eltype(x) + + x3 = LabeledArray(copy(vals1), lbls) + fill!(x3, 1) + @test all(x3 .== 1) + + v = copy(x) + @test v == x + @test refarray(v) !== refarray(x) + @test v.labels === x.labels + resize!(v, 7) + @test length(v) == 7 + push!(v, 1) + @test v[8] == 1 + pushfirst!(v, 2) + @test v[1] == 2 + insert!(v, 3, 4) + @test v[3] == 4 + deleteat!(v, 3) + @test length(v) == 9 + @test v[3] == 1 + append!(v, 1:3) + @test length(v) == 12 + @test v[10:12] == 1:3 + prepend!(v, 1:3) + @test length(v) == 15 + @test v[1:3] == 1:3 + empty!(v) + @test isempty(v) + sizehint!(v, 10) - x2 = LabeledArray([1:3 1:3], lbls) - ra = refarray(collect(x2)) - @test size(ra) == (3, 2) - @test ra isa Matrix{Int64} + y = LabeledArray(copy(vals), copy(lbls)) + @test x == y + y.labels[4] = "4" + @test x == y + y.values[1] = 2 + @test x != y + + @test x == vals + @test vals == x - lbs = labels(x) + x1 = copy(x) + @test x1 == x + @test refarray(x1) !== refarray(x) + @test typeof(x1) == typeof(x) + @test x1.labels === x.labels + + x2 = convert(AbstractVector{LabeledValue{Int16,Union{Int,Missing}}}, x) + @test typeof(x2) == LabeledVector{Int16, Vector{Int16}, Union{Int,Missing}} + @test x2.values == x.values + @test x2.labels === x.labels + + @test convert(AbstractVector{LabeledValue{Int,Union{Int,Missing}}}, x) === x + + x3 = convertvalue(Int16, x) + @test typeof(x3) == LabeledVector{Int16, Vector{Int16}, Union{Int,Missing}} + @test x3.values == x.values + @test x3.labels === x.labels + + s = similar(x) + @test typeof(s) == typeof(x) + s = similar(x, LabeledValue{Int16}) + @test eltype(s) == LabeledValue{Int16, keytype(lbls)} + s = similar(x, (3, 3)) + @test size(s) == (3, 3) + s = similar(x, LabeledValue{Int16}, 3, 3) + @test eltype(s) == LabeledValue{Int16, keytype(lbls)} + @test size(s) == (3, 3) + + lbs = valuelabels(x) @test size(lbs) == size(x) @test IndexStyle(typeof(lbs)) == IndexLinear() @test lbs[1] == "1" @@ -86,31 +214,14 @@ end @test length(lbs) == length(x) v = view(x, 1:3) - lbs = labels(v) + lbs = valuelabels(v) @test size(lbs) == size(v) @test lbs[3] == "2" - lbs1 = labels(x1) + x1 = LabeledArray(vals1, lbls) + lbs1 = valuelabels(x1) @test lbs1[3] == "missing" - y = LabeledArray(copy(vals), copy(lbls)) - @test x == y - y.labels[4] = "4" - @test x == y - y.values[1] = 2 - @test x != y - - @test x == vals - @test vals == x - @test x == string.(vals) - @test string.(vals) == x - - @test copy(x) == x - @test typeof(copy(x)) == typeof(x) - - @test convert(Vector{Int}, x) === x.values - @test convert(Vector{Int16}, x) == x.values - - c = CategoricalArray(labels(x)) + c = CategoricalArray(valuelabels(x)) @test c == string.(vals) end diff --git a/test/readstat.jl b/test/readstat.jl index 92a1f61..e546774 100644 --- a/test/readstat.jl +++ b/test/readstat.jl @@ -20,7 +20,7 @@ end 5 │ e 1000.3 missing missing Male missing 2000-01-01T00:00:00""" m = metadata(d) - @test valuelabels(d, :mylabl) == d.mylabl.labels + @test getvaluelabels(d, :mylabl) == d.mylabl.labels @test minute(m.modified_time) == 36 @test sprint(show, m) == "ReadStatMeta(A test file, .dta)" # Timestamp displays different values depending on time zone @@ -74,7 +74,7 @@ end d = readstat(dta, usecols=Int[]) @test sprint(show, d) == "0×0 ReadStatTable" @test isempty(colmetadata(d)) - @test length(valuelabels(d)) == 2 + @test length(getvaluelabels(d)) == 2 d = readstat(dta, usecols=1:3, row_offset=10) @test size(d) == (0, 3) @@ -130,15 +130,15 @@ end alltypes = "$(@__DIR__)/../data/alltypes.dta" dtype = readstat(alltypes) - @test eltype(dtype[1]) == LabeledValue{Union{Missing, Int8}} - @test eltype(dtype[2]) == LabeledValue{Union{Missing, Int16}} - @test eltype(dtype[3]) == LabeledValue{Union{Missing, Int32}} - @test eltype(dtype[4]) == LabeledValue{Union{Missing, Float32}} - @test eltype(dtype[5]) == LabeledValue{Union{Missing, Float64}} + @test eltype(dtype[1]) == LabeledValue{Union{Missing, Int8}, Union{Int32,Char}} + @test eltype(dtype[2]) == LabeledValue{Union{Missing, Int16}, Union{Int32,Char}} + @test eltype(dtype[3]) == LabeledValue{Union{Missing, Int32}, Union{Int32,Char}} + @test eltype(dtype[4]) == LabeledValue{Union{Missing, Float32}, Union{Int32,Char}} + @test eltype(dtype[5]) == LabeledValue{Union{Missing, Float64}, Union{Int32,Char}} @test eltype(dtype[6]) == String @test eltype(dtype[7]) == String @test length(dtype[1,7]) == 114 - vallbls = valuelabels(dtype) + vallbls = getvaluelabels(dtype) @test length(vallbls) == 1 lbls = vallbls[:testlbl] @test length(lbls) == 2 diff --git a/test/table.jl b/test/table.jl index d205752..6220b0d 100644 --- a/test/table.jl +++ b/test/table.jl @@ -75,7 +75,7 @@ end m = gettestmeta() ms = StructVector(ReadStatColMeta[]) lbls = Dict{Any,String}() - vls = Dict{Symbol, Dict{Any,String}}(:A=>lbls) + vls = Dict{Symbol, Dict}(:A=>lbls) hms = Bool[false] @test_throws ArgumentError ReadStatTable(ReadStatColumns(), Symbol[:c], vls, hms, m, ms) @test_throws ArgumentError ReadStatTable(ReadStatColumns(), Symbol[], vls, [true], m, ms) @@ -86,8 +86,8 @@ end @test size(tb) == (0, 1) @test length(tb) == 1 @test isempty(tb) - @test valuelabels(tb) === vls - @test valuelabels(tb, :A) === lbls + @test getvaluelabels(tb) === vls + @test getvaluelabels(tb, :A) === lbls delete!(vls, :A) @test sprint(show, MIME("text/plain"), tb) == "0×1 ReadStatTable" @@ -184,7 +184,7 @@ end push!(cols, c1, c2) names = [:c1, :c2] hms = Bool[false, true] - vls = Dict{Symbol, Dict{Any,String}}() + vls = Dict{Symbol, Dict}() m = gettestmeta() ms = StructVector{ReadStatColMeta}((["v1","v2"], ["%tf","%tc"], [READSTAT_TYPE_INT8, READSTAT_TYPE_DOUBLE], [:A,Symbol()], [Csize_t(1),Csize_t(1)],