Skip to content

Commit 473db52

Browse files
committed
More improvements
1 parent a52e50d commit 473db52

1 file changed

Lines changed: 119 additions & 52 deletions

File tree

docs/src/user_manual/explanation/netcdf_io.rst

Lines changed: 119 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -135,9 +135,45 @@ Iris' optimisation all together, and will take its chunksizes from Dask's behavi
135135

136136
Character and String datatypes
137137
------------------------------
138+
Summary
139+
^^^^^^^
140+
141+
* Iris currently *only* fully supports fixed-width 'char' type data in netCDF variables
142+
143+
* the 'string' type (variable-width unicode strings) will be added in a future release
144+
145+
* 'char' variable data is represented as numpy string arrays in Iris objects, such as
146+
cubes and coordinates.
147+
148+
* the numpy dtype is of the type "U<xx>", where <xx> is a character width.
149+
* the character width relates to a string *dimension* of the netCDF variable,
150+
which is not in the dimensions of the Iris object or its data array.
151+
* the dtype 'width' controls the length of string dimensions created when saving
152+
153+
* Iris also uses a variable ``_Encoding`` attribute to enable storage of non-ascii
154+
characters in 'char' type arrays.
155+
156+
* it appears as a regular attribute of the Iris object
157+
* it is not needed for ascii-only data
158+
* it is not needed to *read* 'utf-8' encoded data correctly
159+
* it **is** required to *save* any non-ascii characters
160+
161+
The following describes the nature of character and string data handling in :
162+
netCDF itself; the CF conventions; the netCDF4 Python module and the Iris implementation.
163+
In practice all these are connected.
164+
165+
The details are generally much simpler when strings may contain only ASCII characters.
166+
When strings may include non-ascii characters, this requires a specific encoding to be
167+
adopted when translating to and from bytes, and rules for determining what the encoding
168+
is or was.
169+
170+
Another significant factor is the way in which all the relevant projects have changed
171+
their features and methods over time, so that historic datasets may often use
172+
non-standard approaches to record string data.
173+
138174

139-
In NetCDF
140-
^^^^^^^^^
175+
In the NetCDF file format
176+
^^^^^^^^^^^^^^^^^^^^^^^^^
141177
In the NetCDF v4 implementation, there are three specific areas where the datatype and
142178
storage characteristics of character data are relevant:
143179

@@ -154,44 +190,53 @@ storage characteristics of character data are relevant:
154190
* 'char' type variables contain one-byte characters, and generally have a fixed-length
155191
"string dimension". If they contain *only* ascii character values, this is
156192
uncomplicated, but they may also be used to contain non-ascii data (i.e.
157-
including unicode characters). There is no universally defined agreement on how
158-
to indicate a variable containing non-ascii data, but many datasets have used a
159-
suitable ``_Encoding`` attribute.
193+
including unicode characters). There is no universally defined agreement for
194+
how to indicate that bytes are encoded non-ascii data, but many older datasets
195+
have used a variable attribute ``_Encoding`` indicating the encoding name.
160196

161197
The NetCDF documentation also mentions that an ``_Encoding`` attribute may be used to
162-
represent non-ascii strings in a 'char' type array. However this is described as
163-
"reserved for future use", and its valid values and effects are not explicitly defined.
198+
represent non-ascii strings. However this is described as "reserved for future use",
199+
and its valid values and effects are not explicitly defined.
164200

165201
However, it is also notable that the standard ``ncgen`` and ``ncdump`` tools *do*
166202
correctly interpret an ``_Encoding`` attribute in most cases, despite this not being an
167203
"official" solution.
168204

169205

170-
In CF
171-
^^^^^
172-
The CF Conventions define a subset of "allowed" datatypes, and describe various data
173-
elements stored as variables, such as data variables, auxiliary coordinates, cell
174-
methods, etc.
206+
In the netCDF CF Conventions
207+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
208+
The `CF Conventions <https://https://cfconventions.org/>`_ define a subset of
209+
"allowed" datatypes, and various types of data elements represented by variables
210+
-- such as data variables, auxiliary coordinates, cell methods, etc.
175211

176212
CF currently supports the use of either netcdf 'string' or 'char' arrays for any
177213
variables.
214+
However, *historically*, CF has had more limited support, and also "unofficial
215+
conventions" have been used for string data encoded as bytes, which may be encountered
216+
in older datasets.
217+
218+
**Prior to v1.8**, CF required to use 'char' type only, and provided
219+
**no official means** of representing non-ascii data.
178220

179-
However, historically CF has had more limited support, and also 'unofficial'
180-
conventions for the use of 'char' arrays have been used which may be encountered in
181-
older datasets.
221+
**Since v1.8**, CF has allowed the use of 'string' data in all variables.
222+
However, up to v1.12 there was still no official way of encoding non-ascii data in
223+
'char' arrays.
182224

183-
Since v1.8, CF has allowed the use of 'string' data in all variables, but prior to that
184-
it was required to use 'char' type only, without providing any official means of storing
185-
non-ascii data.
225+
**Since v1.12**, CF now mandates a *default* assumption of utf-8 encoding to store
226+
non-ascii data in 'char' form. It does also note that some data in the past has used an
227+
``_Encoding`` attribute -- though this was never an official CF usage.
186228

187-
Since v1.12, CF also mandates a *default* assumption of utf-8 encoding to store non-ascii
188-
data in 'char' form, but it also notes that some data in the past has used an
189-
``_Encoding`` attribute -- though was never an official CF usage.
229+
Characteristics
230+
~~~~~~~~~~~~~~~
231+
Where strings are stored as 'char' type, which is the more common traditional approach,
232+
the array must have a "string dimension", which is a normal file dimension. Thus, these
233+
strings always have a *fixed byte width*. (However, that is not the same as a fixed
234+
*character* width, since in most encodings non-ascii characters require more bytes to
235+
store).
190236

191-
Where strings are stored as 'char' type, the array must have a "string dimension",
192-
which is a normal file dimension. Thus, these strings always have a *fixed byte width*.
193-
(However, that is not the same as a fixed *character* width, since in most encodings
194-
non-ascii characters require more bytes to store.)
237+
Although the variable-length 'string' data is now supported in CF, the use of
238+
fixed-width 'char' arrays is obviously more efficient for storage and access, and it is
239+
still the most common approach in practice.
195240

196241

197242
In the netCDF4 Python module
@@ -206,60 +251,82 @@ In the netCDF4 Python module
206251

207252
* variable data of type 'char' is presented (read and written) as numpy arrays of
208253
dtype "S1" -- that is, an array of length-1 Python "bytes" objects.
209-
The netCDF4 package can also automatically translate this to string arrays of dtype
210-
"U<xx>", if the variable has an ``_Encoding`` attribute, but Iris turns this feature
211-
*off* in order to implement its own wider-ranging encoding support (see below).
254+
255+
.. note::
256+
257+
The netCDF4 package can also automatically translate this to string arrays of
258+
dtype "U<xx>", if the variable has an ``_Encoding`` attribute. **However,**
259+
Iris turns this feature *off*, in order to implement its own wider-ranging
260+
encoding support (described below).
212261

213262

214263
In Iris
215264
^^^^^^^
216-
In Iris, the 'string' data type is supported at present, though this is planned for
217-
future releases. See the following section `Variable-length datatypes`_ for
218-
an interim solution enabling you at least to *load* variable-length string data.
265+
.. note::
266+
267+
In Iris, **the 'string' data type is not supported at present**, though this is
268+
planned for future releases. See the following section `Variable-length datatypes`_
269+
for an interim solution enabling you at least to *load* variable-length string data.
219270

220271
Iris stores string data in arrays of dtype "U<xx>", where <xx> is a maximum character
221272
width. However, this data is currently **only** read and written in netCDF files as
222-
'char' type variables.
273+
'char' type variables (i.e. byte arrays).
223274

224-
Iris provides a set of valid encodings for non-ascii data :
225-
"ascii", "utf8", "utf16" and "utf32". These (or valid aliases) will appear in the
275+
Iris supports a specific set of valid encodings for non-ascii data :
276+
"ascii", "utf8", "utf16" and "utf32". These (or aliases) will appear in the
226277
``_Encoding`` attribute of a file variable, and likewise as an attribute of the
227278
corresponding Iris component object (e.g. cube or coordinate).
228279

229-
**On loading**, if there is a valid ``_Encoding`` attribute this is used to decode the data,
230-
otherwise a default encoding of "utf8" is applied: This works transparently if only
231-
ascii bytes are present, and also allows the ``_Encoding`` attribute to be omitted as
232-
long as utf8 was used.
233-
234-
**On saving**, any string data with only ascii characters does not require an
235-
``_Encoding`` attribute. However if there are non-ascii bytes, and no ``_Encoding``
236-
attribute, then an error will be raised.
237-
238-
So effectively, the **"default" encodings are 'utf8' for load and 'ascii' for save**.
239-
280+
When loading
281+
~~~~~~~~~~~~
282+
If there is a valid ``_Encoding`` attribute this is used to decode the
283+
data, otherwise a default encoding of "utf8" is applied: This works transparently when
284+
only ascii characters are present, and also allows the ``_Encoding`` attribute to be
285+
omitted as long as utf8 was used. An invalid or unsupported encoding name will be
286+
ignored, with a warning, but the attribute will still be added to the Iris component
287+
object.
288+
289+
When saving
290+
~~~~~~~~~~~
291+
Any string data with only ascii characters does not require an ``_Encoding`` attribute.
292+
However if there are any non-ascii characters, and no ``_Encoding``
293+
attribute, then an error will be raised. An invalid or unsupported encoding name will
294+
be ignored, with a warning, but the attribute will still be stored to the file.
295+
296+
So effectively, the **default encoding is 'utf8' for load and 'ascii' for save**.
297+
298+
String width dimensions
299+
~~~~~~~~~~~~~~~~~~~~~~~
240300
For each valid encoding there is a definite relation between the string dimension length
241-
in the file (actually, the number of *bytes*), and the maximum character length in the
242-
array dtype, i.e. the "<xx>" in the "U<xx>" dtype.
301+
in the file (actually, the number of *bytes*), and the maximum character length, aka
302+
string width, in the array dtype : i.e. the "<xx>" in the "U<xx>" dtype.
243303

244-
The lengths of string dimensions created on write are calculated as follows:
304+
The **lengths of string dimensions created on write** are calculated as follows:
245305

246306
* ascii : n-bytes = n-characters
247307
* utf8 : n-bytes = n-characters
248308
* utf16 : n-bytes = 2 * (n-characters + 1)
249309
* utf32 : n-bytes = 4 * (n-characters + 1)
250310

251311
For reading, the inverse relations are applied to determine the '"U<xx>"' dtype in which
252-
the data is presented. This will round-trip correctly, i.e. is unchanged if written and
253-
then read back.
312+
the data is presented. This will always round-trip correctly, i.e. the dimension length
313+
is unchanged if data is read and then written back.
254314

255-
For 'ascii' and 'utf32' this relationship is simple + fixed, but for 'utf8' and
256-
'utf16', the number of encoded bytes depends on the actual characters present
315+
For 'ascii' and 'utf32' this character-to-byte relationship is simple + fixed, but for
316+
'utf8' and 'utf16', the number of encoded bytes depends on the actual characters present
257317
**and can exceed the numbers given above**. If any string in the *actual* data encodes
258318
to more bytes than the above-calculated string dimension, then Iris will raise an
259319
:class:`iris.exceptions.TranslationError`. In this case, the user must **explicitly
260320
specify** a longer string dimension, by converting the data to a longer "U<xx>" dtype :
261321
for example, ``cube.data = cube.core_data().astype("U20")``.
262322

323+
.. warning::
324+
325+
When processing string arrays, Numpy does not routinely preserve the "<xx>" width part
326+
of "U<xx>" type data : instead, some operations will reduce it to the maximum width
327+
occurring. So in these cases also, it may be necessary to explicitly re-assert the
328+
desired "string width" before saving -- use ``.astype()``, as above.
329+
263330

264331
Variable-length datatypes
265332
-------------------------

0 commit comments

Comments
 (0)