-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathDataset.cs
More file actions
210 lines (171 loc) · 7.49 KB
/
Copy pathDataset.cs
File metadata and controls
210 lines (171 loc) · 7.49 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Serialization;
using JSONstat.IO;
namespace JSONstat.Models;
/// <summary>
/// A JSON-stat dataset: a multi-dimensional cube of values with dimensions,
/// optional roles, status, and metadata. Carries the Toolkit-parity read and
/// navigation surface (<c>Dimension</c>, <c>Data</c>, ...) and the non-deprecated
/// transforms (<c>Dice</c>, <c>Unflatten</c>, <c>Transform</c>).
/// Source: <c>../wiki/dataset-structure.md</c>.
/// </summary>
public sealed partial class Dataset : IJsonOnDeserialized
{
/// <summary>JSON-stat version.</summary>
public string? Version { get; set; } = "2.0";
/// <summary>The response class (always <see cref="ResponseClass.Dataset"/>).</summary>
public ResponseClass Class { get; set; } = ResponseClass.Dataset;
/// <summary>Short descriptive text.</summary>
public string? Label { get; set; }
/// <summary>Source description.</summary>
public string? Source { get; set; }
/// <summary>Update time (ISO 8601).</summary>
public string? Updated { get; set; }
/// <summary>Ordered dimension ids.</summary>
public string[]? Id { get; set; }
/// <summary>Category counts per dimension (same order as <see cref="Id"/>).</summary>
public int[]? Size { get; set; }
/// <summary>Optional dimension roles.</summary>
public Role? Role { get; set; }
/// <summary>The dimension map (wire property <c>dimension</c>).</summary>
[JsonPropertyName("dimension")]
public Dictionary<string, Dimension> DimensionMap { get; set; } = new Dictionary<string, Dimension>();
/// <summary>The data values (dense array or sparse object).</summary>
public ValueCollection? Value { get; set; }
/// <summary>Observation-level status (array, string, or object).</summary>
public StatusCollection? Status { get; set; }
/// <summary>Annotations.</summary>
public string[]? Note { get; set; }
/// <summary>Related resources.</summary>
public Link? Link { get; set; }
/// <summary>Provider-specific extension.</summary>
public JsonElement? Extension { get; set; }
/// <summary>Provider-specific extension properties not modelled explicitly.</summary>
[JsonExtensionData]
public IDictionary<string, JsonElement>? ExtensionData { get; set; }
private long _n;
private Dimension[] _orderedDimensions = System.Array.Empty<Dimension>();
private bool _linked;
private List<Datum>? _dataCache;
/// <summary>Wires dimension ids/roles/sizes and computes <see cref="N"/>.</summary>
internal Dataset Wire()
{
if (_linked) return this;
var ids = Id ?? System.Array.Empty<string>();
var sizes = Size ?? System.Array.Empty<int>();
var ordered = new List<Dimension>(ids.Length);
for (int i = 0; i < ids.Length; i++)
{
var id = ids[i];
if (!DimensionMap.TryGetValue(id, out var dim))
{
dim = new Dimension();
DimensionMap[id] = dim;
}
dim.Id = id;
dim.Role = Role != null && Role.RoleOf(id) is { } r ? r : DimensionRole.Classification;
dim.Wire(i < sizes.Length ? sizes[i] : 0);
ordered.Add(dim);
}
_orderedDimensions = ordered.ToArray();
long n = 1;
foreach (var s in sizes) n *= s;
_n = n;
_linked = true;
return this;
}
/// <inheritdoc />
void IJsonOnDeserialized.OnDeserialized() => Wire();
private void EnsureLinked() => Wire();
private int[] Sizes => Size ?? System.Array.Empty<int>();
/// <summary>Total number of cells in the cube (including missing).</summary>
[JsonIgnore]
public long N
{
get { EnsureLinked(); return _n; }
}
/// <summary>The dimensions in id order.</summary>
[JsonIgnore]
public IReadOnlyList<Dimension> Dimensions
{
get { EnsureLinked(); return _orderedDimensions; }
}
/// <summary>Gets the dimension at <paramref name="index"/>, or <c>null</c>.</summary>
public Dimension? Dimension(int index)
{
EnsureLinked();
return index >= 0 && index < _orderedDimensions.Length ? _orderedDimensions[index] : null;
}
/// <summary>Gets the dimension with id <paramref name="id"/>, or <c>null</c>.</summary>
public Dimension? Dimension(string id)
{
EnsureLinked();
return DimensionMap.TryGetValue(id, out var dim) ? dim : null;
}
/// <summary>Gets the first dimension with the given role, or <c>null</c>.</summary>
public Dimension? Dimension(DimensionRole role)
{
EnsureLinked();
return _orderedDimensions.FirstOrDefault(d => d.Role == role);
}
/// <summary>Encodes per-dimension category indices into a linear index.</summary>
public long LinearOf(int[] indices)
{
EnsureLinked();
return Cube.CubeIndex.ToLinear(Sizes, indices);
}
/// <summary>Resolves dimension-id/category-id pairs into category indices.</summary>
public int[]? IndicesOf(IReadOnlyDictionary<string, string> byId)
{
EnsureLinked();
var ids = Id ?? System.Array.Empty<string>();
var indices = new int[ids.Length];
foreach (var pair in byId)
{
int d = System.Array.IndexOf(ids, pair.Key);
if (d < 0) return null;
if (!DimensionMap.TryGetValue(pair.Key, out var dim)) return null;
var pos = dim.CategoryInfo.PositionOf(pair.Value);
if (!pos.HasValue) return null;
indices[d] = pos.Value;
}
return indices;
}
/// <summary>The value at a linear index (<c>null</c> when missing). Equivalent to the
/// Toolkit's <c>Data(id, false)</c>.</summary>
public double? ValueAt(int linear) => (Value ?? ValueCollection.Empty()).GetValue(linear);
/// <summary>The value at the given category indices.</summary>
public double? ValueAt(int[] indices) => ValueAt((int)LinearOf(indices));
/// <summary>The value resolved by dimension-id/category-id pairs.</summary>
public double? ValueAt(IReadOnlyDictionary<string, string> byId)
{
var indices = IndicesOf(byId);
return indices is null ? (double?)null : ValueAt(indices);
}
/// <summary>The status at a linear index, or <c>null</c>.</summary>
public string? StatusAt(int linear) => Status?.GetStatus(linear);
/// <summary>All observations, in linear order.</summary>
public IReadOnlyList<Datum> Data() => _dataCache ??= EnumerateData().ToList();
/// <summary>The observation at a linear index.</summary>
public Datum Data(int linear) => new Datum(ValueAt(linear), StatusAt(linear));
/// <summary>The observation at the given category indices, or <c>null</c> if invalid.</summary>
public Datum? Data(int[] indices)
{
if (indices == null || indices.Length != (Id?.Length ?? 0)) return null;
return Data((int)LinearOf(indices));
}
/// <summary>The observation resolved by dimension-id/category-id pairs, or <c>null</c> if invalid.</summary>
public Datum? Data(IReadOnlyDictionary<string, string> byId)
{
var indices = IndicesOf(byId);
return indices is null ? null : Data(indices);
}
/// <summary>Enumerates observations in linear order without materializing the whole cube.</summary>
public IEnumerable<Datum> EnumerateData()
{
EnsureLinked();
for (int i = 0; i < _n; i++) yield return new Datum(ValueAt(i), StatusAt(i));
}
}