Getting started ############### The cf package allows a data array and its associated metadata to be contained and manipulated as a single entity called a *field*, which is stored in a :class:`.Field` object. A first example =============== Much of the basic field manipulation syntax can be seen in this simple read-modify-write example which: * Reads a field from a file on disk and find out information about it. * Modifies an attribute and the units of its data. * Modifies the data values. * Modifies a subset of the data values. * Writes it out to another file on disk. The example may be reproduced by downloading the :download:`sample netCDF file (file.nc) <../file.nc>` (taking care not to overwrite an existing file with that name). **1.** Import the cf package. >>> import cf **2.** Read a field from disk and find a summary of its contents. >>> f = cf.read('file.nc')[0] >>> type(f) >>> f >>> print f Data : AIR_TEMP(latitude, longitude) Cell methods : time: mean Dimensions : time(1) = [15] days since 2000-1-1 : latitude(4) = [-2.5, ..., 5] degrees_north : longitude(5) = [0, ..., 15] degrees_east : height(1) = [2] m Auxiliary coords: **3.** Find all of the field's attributes and its data array as a masked numpy array. >>> f.attributes {'Conventions': 'CF-1.0', '_FillValue': 1e+20, 'cell_methods': , 'experiment_id': 'stabilization experiment (SRES A1B)', 'long_name': 'Surface Air Temperature', 'standard_name': 'AIR_TEMP', 'title': 'SRES A1B', 'units': 'K'} >>> f.array masked_array(data = [[ 274.15, 276.15, 275.15, 277.15, 278.15], [ 274.15, 275.15, 276.15, 277.15, 276.15], [ 277.15, 275.15, 278.15, 274.15, 278.15], [ 276.15, 274.15, 275.15, 277.15, 274.15]], mask = False, fill_value = 1e+20) **4.**. Modify the field's standard name attribute and change the field's data from units of Kelvin to Celsius. .. note:: Changing the units automatically changes the data when it is next accessed. >>> f.standard_name 'AIR_TEMP' >>> f.standard_name = 'air_temperature' >>> f.standard_name 'air_temperature' >>> f.Units -= 273.15 >>> f.units 'K @ 273.15' >>> f.array masked_array(data = [[ 1. 3. 2. 4. 5.] [ 1. 2. 3. 4. 3.] [ 4. 2. 5. 1. 5.] [ 3. 1. 2. 4. 1.]], mask = False, fill_value = 1e+20) >>> print f Data : air_temperature(latitude, longitude) Cell methods : time: mean Dimensions : time(1) = [15] days since 2000-1-1 : latitude(4) = [-2.5, ..., 5] degrees_north : longitude(5) = [0, ..., 15] degrees_east : height(1) = [2] m Auxiliary coords: **5.** Check that the field has 'temperature' in its standard name and that at least one of its longitude coordinate values is greater than 0.0. >>> f.match(attr = {'standard_name': '.*temperature.*'}, coord = {'longitude' : cf.gt(0.)}) True **6.** Modify the data values. >>> g = f + 100 >>> g.array masked_array(data = [[ 101. 103. 102. 104. 105.] [ 101. 102. 103. 104. 103.] [ 104. 102. 105. 101. 105.] [ 103. 101. 102. 104. 101.]], mask = False, fill_value = 1e+20) >>> g = f + f >>> g.array masked_array(data = [[ 2. 6. 4. 8. 10.] [ 2. 4. 6. 8. 6.] [ 8. 4. 10. 2. 10.] [ 6. 2. 4. 8. 2.]], mask = False, fill_value = 1e+20) >>> g = f / cf.Data(2, 'seconds') >>> g.array masked_array(data = [[0.5 1.5 1.0 2.0 2.5] [0.5 1.0 1.5 2.0 1.5] [2.0 1.0 2.5 0.5 2.5] [1.5 0.5 1.0 2.0 0.5]], mask = False, fill_value = 1e+20) >>> g.Units **7.** Access and modify a subset of the data values. >>> g = f.subset[0::2, 2:4] >>> g.array masked_array(data = [[ 2. 4.] [ 5. 1.]], mask = False, fill_value = 1e+20) >>> f.subset[0::2, ...] = -10 >>> f.array masked_array(data = [[-10. -10. -10. -10. -10.] [ 1. 2. 3. 4. 3.] [-10. -10. -10. -10. -10.] [ 3. 1. 2. 4. 1.]], mask = False, fill_value = 1e+20) **8.** Write the modified field to disk. >>> cf.write(f, 'newfile.nc') Further examples ================ Reading ------- The :func:`.read` function will read CF-netCDF and PP format files from disk (or netCDF file from an OPeNDAP server) and return their contents as a :ref:`field list `, i.e. an ordered collection of fields stored in a :class:`~.FieldList` object: >>> f = cf.read('data.nc') >>> type(f) >>> f [, , , ] >>> type(f[-1]) >>> f[-1] The :func:`.read` function always returns a field list as opposed to a field. If a single field as a :class:`.Field` object is required, then the :func:`.read` function (or rather its returned field list) may be indexed: >>> f = cf.read('file1.nc')[0] >>> type(f) See the section on :ref:`variable lists ` for more details on fields and list of fields. Multiple files may be read at once by: * Using Unix shell wildcard characters in file names * Providing a list or tuple of files >>> f = cf.read('*.nc') >>> f = cf.read('file[1-9a-c].nc') >>> f = cf.read('dir*/*.pp') >>> f = cf.read(['file1.nc', 'file2.nc']) File name suffices (such as *.nc* and *.pp*) are not required to define a file's format. Writing ------- The :func:`.write` function will write fields to a CF-netCDF file on disk: >>> type(g) >>> cf.write(g, 'newfile.nc') >>> type(f) >>> cf.write(f, 'newfile.nc') A sequence of fields and field lists may be written to the same file: >>> cf.write([f, g], 'newfile.nc') All of the input fields are written to the same output file, but if metadata (such as coordinates) are identical in two or more fields then that metadata is only written once to the output file. Output file names are arbitrary (i.e. they do not require a particular suffix). Attributes ---------- The field's standard and non-standard CF attributes are returned by the :attr:`~cf.Field.attributes` attribute: >>> f.attributes {'_FillValue': 1e+20, 'cell_methods': , 'standard_name': 'air_temperature', 'units': 'K'} Individual attributes recognised by the CF conventions (such as standard_name) may be set, retrieved and deleted as standard python object attributes: >>> f.standard_name = 'air_temperature' >>> f.standard_name 'air_temperature' >>> del f.standard_name >>> setattr(f, 'standard_name', 'air_pressure') >>> getattr(f, 'standard_name') 'air_pressure' >>> delattr(f, 'standard_name') Any attribute (recognised by the CF conventions or not) may be retrieved with the field's :meth:`~cf.Field.getattr` method, which accepts an optional default value argument in the same way as the python built-in getattr function: >>> f.getattr('standard_name') 'air_temperature' >>> f.getattr('non_standard_attribute', None) 3.5 >>> f.hasattr('another_non_standard_attribute') False >>> f.getattr('another_non_standard_attribute', None) None Any attribute (recognised by the CF conventions or not) may be set with the field's :meth:`~cf.Field.setattr` method: >>> f.setattr('standard_name', 'air_temperature') >>> f.setattr('non_standard_attribute', 3.5) Any attribute (recognised by the CF conventions or not) may be deleted with the field's :meth:`~cf.Field.delattr` method. >>> f.delattr('standard_name') >>> f.delattr('non_standard_attribute') Other attributes (called *private* attributes) which do not clash with reserved names (such as 'long_name', 'match', etc.) may be set, retrieved and deleted as usual, but they will not appear in the attributes returned by the :attr:`~cf.Field.attributes` dictionary: >>> f.ncvar = 'tas' >>> f.ncvar >>> 'tas' >>> del f.ncvar >>> f.file 'file.nc' Selecting fields ---------------- Fields may be selected with the :meth:`~cf.Field.match` and :meth:`~cf.Field.extract`. These methods take conditions on field attributes and coordinates as inputs: >>> f [, ] >>> f.match(attr={'standard_name': '.*temperature'}) [False, True] >>> g = f.extract(attr={'standard_name': '.*temperature'}, coord={'longitude': 0}) >>> g [] The data array -------------- The fields data array may be retrieved as an independent numpy array with the field's :attr:`~cf.Field.array` attribute: >>> f.array masked_array(data = [[ 2. 4.] [ 5. 1.]], mask = False, fill_value = 1e+20) The first and last elements of the data array may be retrieved as with the field's :attr:`~cf.Field.first_datum` and :attr:`~cf.Field.last_datum` attributes: >>> f.first_datum 2.0 >>> f.last_datum 1.0 Coordinates ----------- A coordinate of the field's coordinate system is returned by the field's :meth:`~cf.Field.coord` method. A coordinate has the same attribute and data access principles as a field: >>> c = f.coord('time') >>> c >>> c.attributes {'_FillValue': None, 'axis': 'T', 'bounds': , 'calendar': 'noleap', 'long_name': 'time', 'standard_name': 'time', 'units': 'days since 0000-1-1'} >>> c.Units >>> c.array masked_array(data = [ 0 30 60 90 120 150 180 210 240 270 300 330], mask = False, fill_value = 999999) Creating fields ---------------